Advertisement
1lann

firewolf3:1.58beta

Apr 3rd, 2014
228
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 78.42 KB | None | 0 0
  1.  
  2. --
  3. --  Firewolf
  4. --  Made by GravityScore and 1lann
  5. --
  6.  
  7. -- 1.58 Wrapper
  8.  
  9. -- Rednet
  10.  
  11. local rednet = {}
  12.  
  13. rednet.CHANNEL_BROADCAST = 65535
  14. rednet.CHANNEL_REPEAT = 65533
  15.  
  16. local tReceivedMessages = {}
  17. local tReceivedMessageTimeouts = {}
  18. local tHostnames = {}
  19.  
  20. function rednet.open( sModem )
  21.         if type( sModem ) ~= "string" then
  22.                 error( "expected string", 2 )
  23.         end
  24.         if peripheral.getType( sModem ) ~= "modem" then
  25.                 error( "No such modem: "..sModem, 2 )
  26.         end
  27.         peripheral.call( sModem, "open", os.getComputerID() )
  28.         peripheral.call( sModem, "open", rednet.CHANNEL_BROADCAST )
  29. end
  30.  
  31. function rednet.close( sModem )
  32.     if sModem then
  33.         -- Close a specific modem
  34.         if type( sModem ) ~= "string" then
  35.             error( "expected string", 2 )
  36.         end
  37.         if peripheral.getType( sModem ) ~= "modem" then
  38.             error( "No such modem: "..sModem, 2 )
  39.         end
  40.         peripheral.call( sModem, "close", os.getComputerID() )
  41.         peripheral.call( sModem, "close", rednet.CHANNEL_BROADCAST )
  42.     else
  43.         -- Close all modems
  44.         for n,sModem in ipairs( peripheral.getNames() ) do
  45.             if rednet.isOpen( sModem ) then
  46.                 rednet.close( sModem )
  47.             end
  48.         end
  49.     end
  50. end
  51.  
  52. function rednet.isOpen( sModem )
  53.     if sModem then
  54.         -- Check if a specific modem is open
  55.         if type( sModem ) ~= "string" then
  56.             error( "expected string", 2 )
  57.         end
  58.         if peripheral.getType( sModem ) == "modem" then
  59.             return peripheral.call( sModem, "isOpen", os.getComputerID() ) and peripheral.call( sModem, "isOpen", rednet.CHANNEL_BROADCAST )
  60.         end
  61.     else
  62.         -- Check if any modem is open
  63.         for n,sModem in ipairs( peripheral.getNames() ) do
  64.             if rednet.isOpen( sModem ) then
  65.                 return true
  66.             end
  67.         end
  68.     end
  69.         return false
  70. end
  71.  
  72. function rednet.send( nRecipient, message, sProtocol )
  73.     -- Generate a (probably) unique message ID
  74.     -- We could do other things to guarantee uniqueness, but we really don't need to
  75.     -- Store it to ensure we don't get our own messages back
  76.     local nMessageID = math.random( 1, 2147483647 )
  77.     tReceivedMessages[ nMessageID ] = true
  78.     tReceivedMessageTimeouts[ os.startTimer( 30 ) ] = nMessageID
  79.  
  80.     -- Create the message
  81.     local nReplyChannel = os.getComputerID()
  82.     local tMessage = {
  83.         nMessageID = nMessageID,
  84.         nRecipient = nRecipient,
  85.         message = message,
  86.         sProtocol = sProtocol,
  87.     }
  88.  
  89.     if nRecipient == os.getComputerID() then
  90.         -- Loopback to ourselves
  91.         os.queueEvent( "rednet_message", nReplyChannel, message, sProtocol )
  92.  
  93.     else
  94.         -- Send on all open modems, to the target and to repeaters
  95.         local sent = false
  96.         for n,sModem in ipairs( peripheral.getNames() ) do
  97.             if rednet.isOpen( sModem ) then
  98.                 peripheral.call( sModem, "transmit", nRecipient, nReplyChannel, tMessage );
  99.                 peripheral.call( sModem, "transmit", rednet.CHANNEL_REPEAT, nReplyChannel, tMessage );
  100.                 sent = true
  101.             end
  102.         end
  103.     end
  104. end
  105.  
  106. function rednet.broadcast( message, sProtocol )
  107.         rednet.send( rednet.CHANNEL_BROADCAST, message, sProtocol )
  108. end
  109.  
  110. function rednet.receive( sProtocolFilter, nTimeout )
  111.     -- The parameters used to be ( nTimeout ), detect this case for backwards compatibility
  112.     if type(sProtocolFilter) == "number" and nTimeout == nil then
  113.         sProtocolFilter, nTimeout = nil, sProtocolFilter
  114.     end
  115.  
  116.     -- Start the timer
  117.         local timer = nil
  118.         local sFilter = nil
  119.         if nTimeout then
  120.                 timer = os.startTimer( nTimeout )
  121.                 sFilter = nil
  122.         else
  123.                 sFilter = "rednet_message"
  124.         end
  125.  
  126.         -- Wait for events
  127.         while true do
  128.                 local sEvent, p1, p2, p3 = os.pullEvent( sFilter )
  129.                 if sEvent == "rednet_message" then
  130.                     -- Return the first matching rednet_message
  131.                         local nSenderID, message, sProtocol = p1, p2, p3
  132.                         if sProtocolFilter == nil or sProtocol == sProtocolFilter then
  133.                         return nSenderID, message, sProtocol
  134.             end
  135.                 elseif sEvent == "timer" then
  136.                     -- Return nil if we timeout
  137.                     if p1 == timer then
  138.                         return nil
  139.                 end
  140.                 end
  141.         end
  142. end
  143.  
  144. function rednet.host( sProtocol, sHostname )
  145.     if type( sProtocol ) ~= "string" or type( sHostname ) ~= "string" then
  146.         error( "expected string, string", 2 )
  147.     end
  148.     if sHostname == "localhost" then
  149.         error( "Reserved hostname", 2 )
  150.     end
  151.     if tHostnames[ sProtocol ] ~= sHostname then
  152.         if rednet.lookup( sProtocol, sHostname ) ~= nil then
  153.             error( "Hostname in use", 2 )
  154.         end
  155.         tHostnames[ sProtocol ] = sHostname
  156.     end
  157. end
  158.  
  159. function rednet.unhost( sProtocol )
  160.     if type( sProtocol ) ~= "string" then
  161.         error( "expected string", 2 )
  162.     end
  163.     tHostnames[ sProtocol ] = nil
  164. end
  165.  
  166. function rednet.lookup( sProtocol, sHostname )
  167.     if type( sProtocol ) ~= "string" then
  168.         error( "expected string", 2 )
  169.     end
  170.  
  171.     -- Build list of host IDs
  172.     local tResults = nil
  173.     if sHostname == nil then
  174.         tResults = {}
  175.     end
  176.  
  177.     -- Check localhost first
  178.     if tHostnames[ sProtocol ] then
  179.         if sHostname == nil then
  180.             table.insert( tResults, os.getComputerID() )
  181.         elseif sHostname == "localhost" or sHostname == tHostnames[ sProtocol ] then
  182.             return os.getComputerID()
  183.         end
  184.     end
  185.  
  186.     if not rednet.isOpen() then
  187.         if tResults then
  188.             return unpack( tResults )
  189.         end
  190.         return nil
  191.     end
  192.  
  193.     -- Broadcast a lookup packet
  194.     rednet.broadcast( {
  195.         sType = "lookup",
  196.         sProtocol = sProtocol,
  197.         sHostname = sHostname,
  198.     }, "dns" )
  199.  
  200.     -- Start a timer
  201.     local timer = os.startTimer( 2 )
  202.  
  203.     -- Wait for events
  204.     while true do
  205.         local event, p1, p2, p3 = os.pullEvent()
  206.         if event == "rednet_message" then
  207.             -- Got a rednet message, check if it's the response to our request
  208.             local nSenderID, tMessage, sMessageProtocol = p1, p2, p3
  209.             if sMessageProtocol == "dns" and tMessage.sType == "lookup response" then
  210.                 if tMessage.sProtocol == sProtocol then
  211.                     if sHostname == nil then
  212.                         table.insert( tResults, nSenderID )
  213.                     elseif tMessage.sHostname == sHostname then
  214.                         return nSenderID
  215.                     end
  216.                 end
  217.             end
  218.         else
  219.             -- Got a timer event, check it's the end of our timeout
  220.             if p1 == timer then
  221.                 break
  222.             end
  223.         end
  224.     end
  225.     if tResults then
  226.         return unpack( tResults )
  227.     end
  228.     return nil
  229. end
  230.  
  231. local bRunning = false
  232. function rednet.run()
  233.         if bRunning then
  234.                 error( "rednet is already running", 2 )
  235.         end
  236.         bRunning = true
  237.        
  238.         while bRunning do
  239.                 local sEvent, p1, p2, p3, p4 = os.pullEventRaw()
  240.         if sEvent == "modem_message" then
  241.             -- Got a modem message, process it and add it to the rednet event queue
  242.             local sModem, nChannel, nReplyChannel, tMessage = p1, p2, p3, p4
  243.             if rednet.isOpen( sModem ) and ( nChannel == os.getComputerID() or nChannel == rednet.CHANNEL_BROADCAST ) then
  244.                 if type( tMessage ) == "table" and tMessage.nMessageID then
  245.                     if not tReceivedMessages[ tMessage.nMessageID ] then
  246.                         tReceivedMessages[ tMessage.nMessageID ] = true
  247.                         tReceivedMessageTimeouts[ os.startTimer( 30 ) ] = nMessageID
  248.                         os.queueEvent( "rednet_message", nReplyChannel, tMessage.message, tMessage.sProtocol )
  249.                     end
  250.                 end
  251.             end
  252.                 elseif sEvent == "rednet_message" then
  253.                     -- Got a rednet message (queued from above), respond to dns lookup
  254.                     local nSenderID, tMessage, sProtocol = p1, p2, p3
  255.                     if sProtocol == "dns" and tMessage.sType == "lookup" then
  256.                         local sHostname = tHostnames[ tMessage.sProtocol ]
  257.                         if sHostname ~= nil and (tMessage.sHostname == nil or tMessage.sHostname == sHostname) then
  258.                             rednet.send( nSenderID, {
  259.                                 sType = "lookup response",
  260.                                 sHostname = sHostname,
  261.                                 sProtocol = tMessage.sProtocol,
  262.                             }, "dns" )
  263.                         end
  264.                     end
  265.  
  266.                 elseif sEvent == "timer" then
  267.             -- Got a timer event, use it to clear the event queue
  268.             local nTimer = p1
  269.             local nMessage = tReceivedMessageTimeouts[ nTimer ]
  270.             if nMessage then
  271.                 tReceivedMessageTimeouts[ nTimer ] = nil
  272.                 tReceivedMessages[ nMessage ] = nil
  273.             end
  274.                 end
  275.         end
  276. end
  277.  
  278. -- Window Display
  279.  
  280. local native = (term.native)
  281. local redirectTarget = native
  282.  
  283. local function wrap( _sFunction )
  284.         return function( ... )
  285.                 return redirectTarget[ _sFunction ]( ... )
  286.         end
  287. end
  288.  
  289. local term = {}
  290.  
  291. term.redirect = function( target )
  292.         if target == nil or type( target ) ~= "table" then
  293.                 error( "Invalid redirect target", 2 )
  294.         end
  295.     if target == term then
  296.         error( "term is not a recommended redirect target, try term.current() instead", 2 )
  297.     end
  298.         for k,v in pairs( native ) do
  299.                 if type( k ) == "string" and type( v ) == "function" then
  300.                         if type( target[k] ) ~= "function" then
  301.                                 target[k] = function()
  302.                                         error( "Redirect object is missing method "..k..".", 2 )
  303.                                 end
  304.                         end
  305.                 end
  306.         end
  307.         local oldRedirectTarget = redirectTarget
  308.         redirectTarget = target
  309.         return oldRedirectTarget
  310. end
  311.  
  312. term.current = function()
  313.     return redirectTarget
  314. end
  315.  
  316. term.native = function()
  317.     -- NOTE: please don't use this function unless you have to.
  318.     -- If you're running in a redirected or multitasked enviorment, term.native() will NOT be
  319.     -- the current terminal when your program starts up. It is far better to use term.current()
  320.     return native
  321. end
  322.  
  323. for k,v in pairs( native ) do
  324.         if type( k ) == "string" and type( v ) == "function" then
  325.                 if term[k] == nil then
  326.                         term[k] = wrap( k )
  327.                 end
  328.         end
  329. end
  330.        
  331. local env = getfenv()
  332. for k,v in pairs( term ) do
  333.         env[k] = v
  334. end
  335.  
  336.  
  337. local window = {}
  338.  
  339. function window.create( parent, nX, nY, nWidth, nHeight, bStartVisible )
  340.  
  341.     if type( parent ) ~= "table" or
  342.        type( nX ) ~= "number" or
  343.        type( nY ) ~= "number" or
  344.        type( nWidth ) ~= "number" or
  345.        type( nHeight ) ~= "number" or
  346.        (bStartVisible ~= nil and type( bStartVisible ) ~= "boolean") then
  347.         error( "Expected object, number, number, number, number, [boolean]", 2 )
  348.     end
  349.  
  350.     if parent == term then
  351.         error( "term is not a recommended window parent, try term.current() instead", 2 )
  352.     end
  353.  
  354.     -- Setup
  355.     local bVisible = (bStartVisible ~= false)
  356.     local nCursorX = 1
  357.     local nCursorY = 1
  358.     local bCursorBlink = false
  359.     local nTextColor = colors.white
  360.     local nBackgroundColor = colors.black
  361.     local sEmpty = string.rep( " ", nWidth )
  362.     local tLines = {}
  363.     do
  364.         local tEmpty = { { sEmpty, nTextColor, nBackgroundColor } }
  365.         for y=1,nHeight do
  366.             tLines[y] = tEmpty
  367.         end
  368.     end
  369.  
  370.     -- Helper functions
  371.     local function updateCursorPos()
  372.         if nCursorX >= 1 and nCursorY >= 1 and
  373.            nCursorX <= nWidth and nCursorY <= nHeight then
  374.             parent.setCursorPos( nX + nCursorX - 1, nY + nCursorY - 1 )
  375.         else
  376.             parent.setCursorPos( 0, 0 )
  377.         end
  378.     end
  379.    
  380.     local function updateCursorBlink()
  381.         parent.setCursorBlink( bCursorBlink )
  382.     end
  383.    
  384.     local function updateCursorColor()
  385.         parent.setTextColor( nTextColor )
  386.     end
  387.    
  388.     local function redrawLine( n )
  389.         parent.setCursorPos( nX, nY + n - 1 )
  390.         local tLine = tLines[ n ]
  391.         for m=1,#tLine do
  392.             local tBit = tLine[ m ]
  393.             parent.setTextColor( tBit[2] )
  394.             parent.setBackgroundColor( tBit[3] )
  395.             parent.write( tBit[1] )
  396.         end
  397.     end
  398.  
  399.     local function lineLen( tLine )
  400.         local nLength = 0
  401.         for n=1,#tLine do
  402.             nLength = nLength + string.len( tLine[n][1] )
  403.         end
  404.         return nLength
  405.     end
  406.  
  407.     local function lineSub( tLine, nStart, nEnd )
  408.         --assert( math.floor(nStart) == nStart )
  409.         --assert( math.floor(nEnd) == nEnd )
  410.         --assert( nStart >= 1 )
  411.         --assert( nEnd >= nStart )
  412.         --assert( nEnd <= lineLen( tLine ) )
  413.         local tSubLine = {}
  414.         local nBitStart = 1
  415.         for n=1,#tLine do
  416.             local tBit = tLine[n]
  417.             local sBit = tBit[1]
  418.             local nBitEnd = nBitStart + string.len( sBit ) - 1
  419.             if nBitEnd >= nStart and nBitStart <= nEnd then
  420.                 if nBitStart >= nStart and nBitEnd <= nEnd then
  421.                     -- Include bit wholesale
  422.                     table.insert( tSubLine, tBit )
  423.                     --assert( lineLen( tSubLine ) == (math.min(nEnd, nBitEnd) - nStart + 1) )
  424.                 elseif nBitStart < nStart and nBitEnd <= nEnd then
  425.                     -- Include end of bit
  426.                     table.insert( tSubLine, {
  427.                         string.sub( sBit, nStart - nBitStart + 1 ),
  428.                         tBit[2], tBit[3]
  429.                     } )
  430.                     --assert( lineLen( tSubLine ) == (math.min(nEnd, nBitEnd) - nStart + 1) )
  431.                 elseif nBitStart >= nStart and nBitEnd > nEnd then
  432.                     -- Include beginning of bit
  433.                     table.insert( tSubLine, {
  434.                         string.sub( sBit, 1, nEnd - nBitStart + 1 ),
  435.                         tBit[2], tBit[3]
  436.                     } )
  437.                     --assert( lineLen( tSubLine ) == (math.min(nEnd, nBitEnd) - nStart + 1) )
  438.                 else
  439.                     -- Include middle of bit
  440.                     table.insert( tSubLine, {
  441.                         string.sub( sBit, nStart - nBitStart + 1, nEnd - nBitStart + 1 ),
  442.                         tBit[2], tBit[3]
  443.                     } )
  444.                     --assert( lineLen( tSubLine ) == (math.min(nEnd, nBitEnd) - nStart + 1) )
  445.                 end
  446.             end
  447.             nBitStart = nBitEnd + 1
  448.         end
  449.         --assert( lineLen( tSubLine ) == (nEnd - nStart + 1) )
  450.         return tSubLine
  451.     end
  452.  
  453.     local function lineJoin( tLine1, tLine2 )
  454.         local tNewLine = {}
  455.         if tLine1[#tLine1][2] == tLine2[1][2] and
  456.            tLine1[#tLine1][3] == tLine2[1][3] then
  457.             -- Merge middle bits
  458.             for n=1,#tLine1-1 do
  459.                 table.insert( tNewLine, tLine1[n] )
  460.             end
  461.             table.insert( tNewLine, {
  462.                 tLine1[#tLine1][1] .. tLine2[1][1],
  463.                 tLine2[1][2], tLine2[1][3]
  464.             } )
  465.             for n=2,#tLine2 do
  466.                 table.insert( tNewLine, tLine2[n] )
  467.             end
  468.             --assert( lineLen( tNewLine ) == lineLen(tLine1) + lineLen(tLine2) )
  469.         else
  470.             -- Just concatenate
  471.             for n=1,#tLine1 do
  472.                 table.insert( tNewLine, tLine1[n] )
  473.             end
  474.             for n=1,#tLine2 do
  475.                 table.insert( tNewLine, tLine2[n] )
  476.             end
  477.             --assert( lineLen( tNewLine ) == lineLen(tLine1) + lineLen(tLine2) )
  478.         end
  479.         return tNewLine
  480.     end
  481.  
  482.     local function redraw()
  483.         for n=1,nHeight do
  484.             redrawLine( n )
  485.         end
  486.     end
  487.  
  488.     local window = {}
  489.  
  490.     -- Terminal implementation
  491.     function window.write( sText )
  492.         local nLen = string.len( sText )
  493.         local nStart = nCursorX
  494.         local nEnd = nStart + nLen - 1
  495.         if nCursorY >= 1 and nCursorY <= nHeight then
  496.             -- Work out where to put new line
  497.             --assert( math.floor(nStart) == nStart )
  498.             --assert( math.floor(nEnd) == nEnd )
  499.             if nStart <= nWidth and nEnd >= 1 then
  500.                 -- Construct new line
  501.                 local tLine = tLines[ nCursorY ]
  502.                 if nStart == 1 and nEnd == nWidth then
  503.                     -- Exactly replace line
  504.                     tLine = {
  505.                         { sText, nTextColor, nBackgroundColor }
  506.                     }
  507.                     --assert( lineLen( tLine ) == nWidth )
  508.                 elseif nStart <= 1 and nEnd >= nWidth then
  509.                     -- Overwrite line with subset
  510.                     tLine = {
  511.                         { string.sub( sText, 1 - nStart + 1, nWidth - nStart + 1 ), nTextColor, nBackgroundColor }
  512.                     }
  513.                     --assert( lineLen( tLine ) == nWidth )
  514.                 elseif nStart <= 1 then
  515.                     -- Overwrite beginning of line
  516.                     tLine = lineJoin(
  517.                         { { string.sub( sText, 1 - nStart + 1 ), nTextColor, nBackgroundColor } },
  518.                         lineSub( tLine, nEnd + 1, nWidth )
  519.                     )
  520.                     --assert( lineLen( tLine ) == nWidth )
  521.                 elseif nEnd >= nWidth then
  522.                     -- Overwrite end of line
  523.                     tLine = lineJoin(
  524.                         lineSub( tLine, 1, nStart - 1 ),
  525.                         { { string.sub( sText, 1, nWidth - nStart + 1 ), nTextColor, nBackgroundColor } }
  526.                     )
  527.                     --assert( lineLen( tLine ) == nWidth )
  528.                 else
  529.                     -- Overwrite middle of line
  530.                     tLine = lineJoin(
  531.                         lineJoin(
  532.                             lineSub( tLine, 1, nStart - 1 ),
  533.                             { { sText, nTextColor, nBackgroundColor } }
  534.                         ),
  535.                         lineSub( tLine, nEnd + 1, nWidth )
  536.                     )
  537.                     --assert( lineLen( tLine ) == nWidth )
  538.                 end
  539.  
  540.                 -- Store and redraw new line
  541.                 tLines[ nCursorY ] = tLine
  542.                 if bVisible then
  543.                     redrawLine( nCursorY )
  544.                 end
  545.             end
  546.         end
  547.  
  548.         -- Move and redraw cursor
  549.         nCursorX = nEnd + 1
  550.         if bVisible then
  551.             updateCursorColor()
  552.             updateCursorPos()
  553.         end
  554.     end
  555.  
  556.     function window.clear()
  557.         local tEmpty = { { sEmpty, nTextColor, nBackgroundColor } }
  558.         for y=1,nHeight do
  559.             tLines[y] = tEmpty
  560.         end
  561.         if bVisible then
  562.             redraw()
  563.             updateCursorColor()
  564.             updateCursorPos()
  565.         end
  566.     end
  567.  
  568.     function window.clearLine()
  569.         if nCursorY >= 1 and nCursorY <= nHeight then
  570.             tLines[ nCursorY ] = { { sEmpty, nTextColor, nBackgroundColor } }
  571.             if bVisible then
  572.                 redrawLine( nCursorY )
  573.                 updateCursorColor()
  574.                 updateCursorPos()
  575.             end
  576.         end
  577.     end
  578.  
  579.     function window.getCursorPos()
  580.         return nCursorX, nCursorY
  581.     end
  582.  
  583.     function window.setCursorPos( x, y )
  584.         nCursorX = math.floor( x )
  585.         nCursorY = math.floor( y )
  586.         if bVisible then
  587.             updateCursorPos()
  588.         end
  589.     end
  590.  
  591.     function window.setCursorBlink( blink )
  592.         bCursorBlink = blink
  593.         if bVisible then
  594.             updateCursorBlink()
  595.         end
  596.     end
  597.  
  598.     function window.isColor()
  599.         return parent.isColor()
  600.     end
  601.  
  602.     function window.isColour()
  603.         return parent.isColor()
  604.     end
  605.  
  606.     local function setTextColor( color )
  607.         if not parent.isColor() then
  608.             if color ~= colors.white and color ~= colors.black then
  609.                 error( "Colour not supported", 3 )
  610.             end
  611.         end
  612.         nTextColor = color
  613.         if bVisible then
  614.             updateCursorColor()
  615.         end
  616.     end
  617.  
  618.     function window.setTextColor( color )
  619.         setTextColor( color )
  620.     end
  621.  
  622.     function window.setTextColour( color )
  623.         setTextColor( color )
  624.     end
  625.  
  626.     local function setBackgroundColor( color )
  627.         if not parent.isColor() then
  628.             if color ~= colors.white and color ~= colors.black then
  629.                 error( "Colour not supported", 3 )
  630.             end
  631.         end
  632.         nBackgroundColor = color
  633.     end
  634.  
  635.     function window.setBackgroundColor( color )
  636.         setBackgroundColor( color )
  637.     end
  638.  
  639.     function window.setBackgroundColour( color )
  640.         setBackgroundColor( color )
  641.     end
  642.  
  643.     function window.getSize()
  644.         return nWidth, nHeight
  645.     end
  646.  
  647.     function window.scroll( n )
  648.         if n ~= 0 then
  649.             local tNewLines = {}
  650.             local tEmpty = { { sEmpty, nTextColor, nBackgroundColor } }
  651.             for newY=1,nHeight do
  652.                 local y = newY + n
  653.                 if y >= 1 and y <= nHeight then
  654.                     tNewLines[newY] = tLines[y]
  655.                 else
  656.                     tNewLines[newY] = tEmpty
  657.                 end
  658.             end
  659.             tLines = tNewLines
  660.             if bVisible then
  661.                 redraw()
  662.                 updateCursorColor()
  663.                 updateCursorPos()
  664.             end
  665.         end
  666.     end
  667.  
  668.     -- Other functions
  669.     function window.setVisible( bVis )
  670.         if bVisible ~= bVis then
  671.             bVisible = bVis
  672.             if bVisible then
  673.                 window.redraw()
  674.             end
  675.         end
  676.     end
  677.  
  678.     function window.redraw()
  679.         if bVisible then
  680.             redraw()
  681.             updateCursorBlink()
  682.             updateCursorColor()
  683.             updateCursorPos()
  684.         end
  685.     end
  686.  
  687.     function window.restoreCursor()
  688.         if bVisible then
  689.             updateCursorBlink()
  690.             updateCursorColor()
  691.             updateCursorPos()
  692.         end
  693.     end
  694.  
  695.     function window.getPosition()
  696.         return nX, nY
  697.     end
  698.  
  699.     function window.reposition( nNewX, nNewY, nNewWidth, nNewHeight )
  700.         nX = nNewX
  701.         nY = nNewY
  702.         if nNewWidth and nNewHeight then
  703.             sEmpty = string.rep( " ", nNewWidth )
  704.             local tNewLines = {}
  705.             local tEmpty = { { sEmpty, nTextColor, nBackgroundColor } }
  706.             for y=1,nNewHeight do
  707.                 if y > nHeight then
  708.                     tNewLines[y] = tEmpty
  709.                 else
  710.                     if nNewWidth == nWidth then
  711.                         tNewLines[y] = tLines[y]
  712.                     elseif nNewWidth < nWidth then
  713.                         tNewLines[y] = lineSub( tLines[y], 1, nNewWidth )
  714.                     else
  715.                         tNewLines[y] = lineJoin( tLines[y], { { string.sub( sEmpty, nWidth + 1, nNewWidth ), nTextColor, nBackgroundColor } } )
  716.                     end
  717.                 end
  718.             end
  719.             nWidth = nNewWidth
  720.             nHeight = nNewHeight
  721.             tLines = tNewLines
  722.         end
  723.         if bVisible then
  724.             window.redraw()
  725.         end
  726.     end
  727.  
  728.     if bVisible then
  729.         window.redraw()
  730.     end
  731.     return window
  732. end
  733.  
  734.  
  735. --    Variables
  736.  
  737.  
  738. local version = "3.0"
  739. local build = 2
  740.  
  741. local w, h = term.getSize()
  742.  
  743. local isMenubarOpen = true
  744. local menubarWindow = nil
  745.  
  746. local allowUnencryptedConnections = true
  747. local enableTabBar = true
  748.  
  749. local currentWebsiteURL = ""
  750. local builtInSites = {}
  751.  
  752. local currentProtocol = ""
  753. local protocols = {}
  754.  
  755. local currentTab = 1
  756. local maxTabs = 5
  757. local maxTabNameWidth = 8
  758. local tabs = {}
  759.  
  760. local languages = {}
  761.  
  762. local history = {}
  763.  
  764. local sides = {}
  765. local publicDNSChannel = 9999
  766. local publicResponseChannel = 9998
  767. local responseID = 41738
  768.  
  769. local httpTimeout = 10
  770. local searchResultTimeout = 0.5
  771. local initiationTimeout = 0.2
  772. local animationInterval = 0.125
  773. local fetchTimeout = 1
  774. local serverLimitPerComputer = 3
  775.  
  776. local listToken = "--@!FIREWOLF-LIST!@--"
  777. local initiateToken = "--@!FIREWOLF-INITIATE!@--"
  778. local fetchToken = "--@!FIREWOLF-FETCH!@--"
  779. local disconnectToken = "--@!FIREWOLF-DISCONNECT!@--"
  780. local protocolToken = "--@!FIREWOLF-REDNET-PROTOCOL!@--"
  781.  
  782. local connectToken = "^%-%-@!FIREWOLF%-CONNECT!@%-%-(.+)"
  783. local DNSToken = "^%-%-@!FIREWOLF%-DNSRESP!@%-%-(.+)"
  784. local receiveToken = "^%-%-@!FIREWOLF%-HEAD!@%-%-(.+)%-%-@!FIREWOLF%-BODY!@%-%-(.+)$"
  785.  
  786. local websiteErrorEvent = "firewolf_websiteErrorEvent"
  787. local redirectEvent = "firewolf_redirectEvent"
  788.  
  789. local baseURL = "https://raw.githubusercontent.com/1lann/Firewolf/master/src/1.58"
  790. local buildURL = baseURL .. "/build.txt"
  791. local firewolfURL = baseURL .. "/client.lua"
  792. local serverURL = baseURL .. "/server.lua"
  793.  
  794. local firewolfLocation = "/" .. shell.getRunningProgram()
  795.  
  796.  
  797. local theme = {}
  798.  
  799. local colorTheme = {
  800.     background = colors.gray,
  801.     accent = colors.red,
  802.     subtle = colors.orange,
  803.  
  804.     lightText = colors.gray,
  805.     text = colors.white,
  806.     errorText = colors.red,
  807. }
  808.  
  809. local grayscaleTheme = {
  810.     background = colors.black,
  811.     accent = colors.black,
  812.     subtle = colors.black,
  813.  
  814.     lightText = colors.white,
  815.     text = colors.white,
  816.     errorText = colors.white,
  817. }
  818.  
  819.  
  820.  
  821. --    Utilities
  822.  
  823.  
  824. local modifiedRead = function(properties)
  825.     local text = ""
  826.     local startX, startY = term.getCursorPos()
  827.     local pos = 0
  828.  
  829.     local previousText = ""
  830.     local readHistory = nil
  831.     local historyPos = 0
  832.  
  833.     if not properties then
  834.         properties = {}
  835.     end
  836.  
  837.     if properties.displayLength then
  838.         properties.displayLength = math.min(properties.displayLength, w - 2)
  839.     else
  840.         properties.displayLength = w - startX - 1
  841.     end
  842.  
  843.     if properties.startingText then
  844.         text = properties.startingText
  845.         pos = text:len()
  846.     end
  847.  
  848.     if properties.history then
  849.         readHistory = {}
  850.         for k, v in pairs(properties.history) do
  851.             readHistory[k] = v
  852.         end
  853.     end
  854.  
  855.     if readHistory[1] == text then
  856.         table.remove(readHistory, 1)
  857.     end
  858.  
  859.     local draw = function(replaceCharacter)
  860.         local scroll = 0
  861.         if properties.displayLength and pos > properties.displayLength then
  862.             scroll = pos - properties.displayLength
  863.         end
  864.  
  865.         local repl = replaceCharacter or properties.replaceCharacter
  866.         term.setTextColor(theme.text)
  867.         term.setCursorPos(startX, startY)
  868.         if repl then
  869.             term.write(string.rep(repl:sub(1, 1), text:len() - scroll))
  870.         else
  871.             term.write(text:sub(scroll + 1))
  872.         end
  873.  
  874.         term.setCursorPos(startX + pos - scroll, startY)
  875.     end
  876.  
  877.     term.setCursorBlink(true)
  878.     draw()
  879.     while true do
  880.         local event, key, x, y, param4, param5 = os.pullEvent()
  881.  
  882.         if properties.onEvent then
  883.             -- Actions:
  884.             -- - exit (bool)
  885.             -- - text
  886.             -- - nullifyText
  887.  
  888.             term.setCursorBlink(false)
  889.             local action = properties.onEvent(text, event, key, x, y, param4, param5)
  890.             if action then
  891.                 if action.text then
  892.                     draw(" ")
  893.                     text = action.text
  894.                     pos = text:len()
  895.                 end if action.nullifyText then
  896.                     text = nil
  897.                     action.exit = true
  898.                 end if action.exit then
  899.                     break
  900.                 end
  901.             end
  902.             draw()
  903.         end
  904.  
  905.         term.setCursorBlink(true)
  906.         if event == "char" then
  907.             local canType = true
  908.             if properties.maxLength and text:len() >= properties.maxLength then
  909.                 canType = false
  910.             end
  911.  
  912.             if canType then
  913.                 text = text:sub(1, pos) .. key .. text:sub(pos + 1, -1)
  914.                 pos = pos + 1
  915.                 draw()
  916.             end
  917.         elseif event == "key" then
  918.             if key == keys.enter then
  919.                 break
  920.             elseif key == keys.left and pos > 0 then
  921.                 pos = pos - 1
  922.                 draw()
  923.             elseif key == keys.right and pos < text:len() then
  924.                 pos = pos + 1
  925.                 draw()
  926.             elseif key == keys.backspace and pos > 0 then
  927.                 draw(" ")
  928.                 text = text:sub(1, pos - 1) .. text:sub(pos + 1, -1)
  929.                 pos = pos - 1
  930.                 draw()
  931.             elseif key == keys.delete and pos < text:len() then
  932.                 draw(" ")
  933.                 text = text:sub(1, pos) .. text:sub(pos + 2, -1)
  934.                 draw()
  935.             elseif key == keys.home then
  936.                 pos = 0
  937.                 draw()
  938.             elseif key == keys["end"] then
  939.                 pos = text:len()
  940.                 draw()
  941.             elseif (key == keys.up or key == keys.down) and readHistory then
  942.                 local shouldDraw = false
  943.                 if historyPos == 0 then
  944.                     previousText = text
  945.                 elseif historyPos > 0 then
  946.                     readHistory[historyPos] = text
  947.                 end
  948.  
  949.                 if key == keys.up then
  950.                     if historyPos < #readHistory then
  951.                         historyPos = historyPos + 1
  952.                         shouldDraw = true
  953.                     end
  954.                 else
  955.                     if historyPos > 0 then
  956.                         historyPos = historyPos - 1
  957.                         shouldDraw = true
  958.                     end
  959.                 end
  960.  
  961.                 if shouldDraw then
  962.                     draw(" ")
  963.                     if historyPos > 0 then
  964.                         text = readHistory[historyPos]
  965.                     else
  966.                         text = previousText
  967.                     end
  968.                     pos = text:len()
  969.                     draw()
  970.                 end
  971.             end
  972.         elseif event == "mouse_click" then
  973.             local scroll = 0
  974.             if properties.displayLength and pos > properties.displayLength then
  975.                 scroll = pos - properties.displayLength
  976.             end
  977.  
  978.             if y == startY and x >= startX and x <= math.min(startX + text:len(), startX + (properties.displayLength or 10000)) then
  979.                 pos = x - startX + scroll
  980.                 draw()
  981.             elseif y == startY then
  982.                 if x < startX then
  983.                     pos = scroll
  984.                     draw()
  985.                 elseif x > math.min(startX + text:len(), startX + (properties.displayLength or 10000)) then
  986.                     pos = text:len()
  987.                     draw()
  988.                 end
  989.             end
  990.         end
  991.     end
  992.  
  993.     term.setCursorBlink(false)
  994.     print("")
  995.     return text
  996. end
  997.  
  998.  
  999. local prompt = function(items, x, y, w, h)
  1000.     local selected = 1
  1001.     local scroll = 0
  1002.  
  1003.     local draw = function()
  1004.         for i = scroll + 1, scroll + h do
  1005.             local item = items[i]
  1006.             if item then
  1007.                 term.setCursorPos(x, y + i - 1)
  1008.                 term.setBackgroundColor(theme.background)
  1009.                 term.setTextColor(theme.lightText)
  1010.  
  1011.                 if scroll + selected == i then
  1012.                     term.setTextColor(theme.text)
  1013.                     term.write(" > ")
  1014.                 else
  1015.                     term.write(" - ")
  1016.                 end
  1017.  
  1018.                 term.write(item)
  1019.             end
  1020.         end
  1021.     end
  1022.  
  1023.     draw()
  1024.     while true do
  1025.         local event, key, x, y = os.pullEvent()
  1026.  
  1027.         if event == "key" then
  1028.             if key == keys.up and selected > 1 then
  1029.                 selected = selected - 1
  1030.  
  1031.                 if selected - scroll == 0 then
  1032.                     scroll = scroll - 1
  1033.                 end
  1034.             elseif key == keys.down and selected < #items then
  1035.                 selected = selected + 1
  1036.             end
  1037.  
  1038.             draw()
  1039.         elseif event == "mouse_click" then
  1040.  
  1041.         elseif event == "mouse_scroll" then
  1042.             if key > 0 then
  1043.                 os.queueEvent("key", keys.down)
  1044.             else
  1045.                 os.queueEvent("key", keys.up)
  1046.             end
  1047.         end
  1048.     end
  1049. end
  1050.  
  1051.  
  1052.  
  1053. --    RC4
  1054. --    Implementation by AgentE382
  1055.  
  1056.  
  1057. local cryptWrapper = function(plaintext, salt)
  1058.     local key = type(salt) == "table" and {unpack(salt)} or {string.byte(salt, 1, #salt)}
  1059.     local S = {}
  1060.     for i = 0, 255 do
  1061.         S[i] = i
  1062.     end
  1063.  
  1064.     local j, keylength = 0, #key
  1065.     for i = 0, 255 do
  1066.         j = (j + S[i] + key[i % keylength + 1]) % 256
  1067.         S[i], S[j] = S[j], S[i]
  1068.     end
  1069.  
  1070.     local i = 0
  1071.     j = 0
  1072.     local chars, astable = type(plaintext) == "table" and {unpack(plaintext)} or {string.byte(plaintext, 1, #plaintext)}, false
  1073.  
  1074.     for n = 1, #chars do
  1075.         i = (i + 1) % 256
  1076.         j = (j + S[i]) % 256
  1077.         S[i], S[j] = S[j], S[i]
  1078.         chars[n] = bit.bxor(S[(S[i] + S[j]) % 256], chars[n])
  1079.         if chars[n] > 127 or chars[n] == 13 then
  1080.             astable = true
  1081.         end
  1082.     end
  1083.  
  1084.     return astable and chars or string.char(unpack(chars))
  1085. end
  1086.  
  1087.  
  1088. local crypt = function(plaintext, salt)
  1089.     local resp, msg = pcall(cryptWrapper, plaintext, salt)
  1090.     if resp then
  1091.         if type(msg) == "table" then
  1092.             return textutils.serialize(msg)
  1093.         else
  1094.             return msg
  1095.         end
  1096.     else
  1097.         return nil
  1098.     end
  1099. end
  1100.  
  1101.  
  1102.  
  1103. --    GUI
  1104.  
  1105.  
  1106. local clear = function(bg, fg)
  1107.     term.setTextColor(fg)
  1108.     term.setBackgroundColor(bg)
  1109.     term.clear()
  1110.     term.setCursorPos(1, 1)
  1111. end
  1112.  
  1113.  
  1114. local fill = function(x, y, width, height, bg)
  1115.     term.setBackgroundColor(bg)
  1116.     for i = y, y + height - 1 do
  1117.         term.setCursorPos(x, i)
  1118.         term.write(string.rep(" ", width))
  1119.     end
  1120. end
  1121.  
  1122.  
  1123. local center = function(text)
  1124.     local x, y = term.getCursorPos()
  1125.     term.setCursorPos(math.floor(w / 2 - text:len() / 2) + (text:len() % 2 == 0 and 1 or 0), y)
  1126.     term.write(text)
  1127.     term.setCursorPos(1, y + 1)
  1128. end
  1129.  
  1130.  
  1131. local centerSplit = function(text, width)
  1132.     local words = {}
  1133.     for word in text:gmatch("[^ \t]+") do
  1134.         table.insert(words, word)
  1135.     end
  1136.  
  1137.     local lines = {""}
  1138.     while lines[#lines]:len() < width do
  1139.         lines[#lines] = lines[#lines] .. words[1] .. " "
  1140.         table.remove(words, 1)
  1141.  
  1142.         if #words == 0 then
  1143.             break
  1144.         end
  1145.  
  1146.         if lines[#lines]:len() + words[1]:len() >= width then
  1147.             table.insert(lines, "")
  1148.         end
  1149.     end
  1150.  
  1151.     for _, line in pairs(lines) do
  1152.         center(line)
  1153.     end
  1154. end
  1155.  
  1156.  
  1157.  
  1158. --    Updating
  1159.  
  1160.  
  1161. local download = function(url)
  1162.     local timeoutID = os.startTimer(httpTimeout)
  1163.     while true do
  1164.         local event, fetchedURL, response = os.pullEvent()
  1165.         if (event == "timer" and fetchedURL == timeoutID) or event == "http_failure" then
  1166.             return false
  1167.         elseif event == "http_success" and fetchedURL == url then
  1168.             local contents = response.readAll()
  1169.             response.close()
  1170.             return contents
  1171.         end
  1172.     end
  1173. end
  1174.  
  1175.  
  1176. local downloadAndSave = function(url, path)
  1177.     local contents = download(url)
  1178.     if contents and not fs.isReadOnly(path) and not fs.isDir(path) then
  1179.         local f = io.open(path, "w")
  1180.         f:write(contents)
  1181.         f:close()
  1182.         return false
  1183.     end
  1184.     return true
  1185. end
  1186.  
  1187.  
  1188. local updateAvailable = function()
  1189.     local number = download(buildURL)
  1190.     if not number then
  1191.         return false, true
  1192.     end
  1193.  
  1194.     if number and tonumber(number) and tonumber(number) > build then
  1195.         return true, false
  1196.     end
  1197.  
  1198.     return false, false
  1199. end
  1200.  
  1201.  
  1202. local redownloadBrowser = function()
  1203.     return downloadAndSave(firewolfURL, firewolfLocation)
  1204. end
  1205.  
  1206.  
  1207.  
  1208. --    Display Websites
  1209.  
  1210.  
  1211. builtInSites["display"] = {}
  1212.  
  1213.  
  1214. builtInSites["display"]["firewolf"] = function()
  1215.     local logo = {
  1216.         "______                         _  __ ",
  1217.         "|  ___|                       | |/ _|",
  1218.         "| |_  _ ____ _____      _____ | | |_ ",
  1219.         "|  _|| |  __/ _ \\ \\ /\\ / / _ \\| |  _|",
  1220.         "| |  | | | |  __/\\ V  V / <_> | | |  ",
  1221.         "\\_|  |_|_|  \\___| \\_/\\_/ \\___/|_|_|  ",
  1222.     }
  1223.  
  1224.     clear(theme.background, theme.text)
  1225.     fill(1, 3, w, 9, theme.subtle)
  1226.  
  1227.     term.setCursorPos(1, 3)
  1228.     for _, line in pairs(logo) do
  1229.         center(line)
  1230.     end
  1231.  
  1232.     term.setCursorPos(1, 10)
  1233.     center(version)
  1234.  
  1235.     term.setBackgroundColor(theme.background)
  1236.     term.setTextColor(theme.text)
  1237.     term.setCursorPos(1, 14)
  1238.     center("Search using the Query Box above")
  1239.     center("Visit rdnt://help for help using Firewolf.")
  1240.  
  1241.     term.setCursorPos(1, h - 2)
  1242.     center("Made by GravityScore and 1lann")
  1243. end
  1244.  
  1245.  
  1246. builtInSites["display"]["credits"] = function()
  1247.     clear(theme.background, theme.text)
  1248.  
  1249.     fill(1, 6, w, 3, theme.subtle)
  1250.     term.setCursorPos(1, 7)
  1251.     center("Credits")
  1252.  
  1253.     term.setBackgroundColor(theme.background)
  1254.     term.setCursorPos(1, 11)
  1255.     center("Written by GravityScore and 1lann")
  1256.     print("")
  1257.     center("RC4 Implementation by AgentE382")
  1258. end
  1259.  
  1260.  
  1261. builtInSites["display"]["help"] = function()
  1262.     clear(theme.background, theme.text)
  1263.  
  1264.     fill(1, 3, w, 3, theme.subtle)
  1265.     term.setCursorPos(1, 4)
  1266.     center("Help")
  1267.  
  1268.     term.setBackgroundColor(theme.background)
  1269.     term.setCursorPos(1, 7)
  1270.     center("Click on the URL bar or press control to")
  1271.     center("open the query box")
  1272.     print("")
  1273.     center("Type in a search query or website URL")
  1274.     center("into the query box.")
  1275.     print("")
  1276.     center("Search for nothing to see all available")
  1277.     center("websites.")
  1278.     print("")
  1279.     center("Visit rdnt://server to setup a server.")
  1280.     center("Visit rdnt://update to update Firewolf.")
  1281. end
  1282.  
  1283.  
  1284. builtInSites["display"]["server"] = function()
  1285.     clear(theme.background, theme.text)
  1286.  
  1287.     fill(1, 6, w, 3, theme.subtle)
  1288.     term.setCursorPos(1, 7)
  1289.     center("Server Software")
  1290.  
  1291.     term.setBackgroundColor(theme.background)
  1292.     term.setCursorPos(1, 11)
  1293.     if not http then
  1294.         center("HTTP is not enabled!")
  1295.         print("")
  1296.         center("Please enable it in your config file")
  1297.         center("to download Firewolf Server.")
  1298.     else
  1299.         center("Press space to download")
  1300.         center("Firewolf Server to:")
  1301.         print("")
  1302.         center("/fwserver")
  1303.  
  1304.         while true do
  1305.             local event, key = os.pullEvent()
  1306.             if event == "key" and key == 57 then
  1307.                 fill(1, 11, w, 4, theme.background)
  1308.                 term.setCursorPos(1, 11)
  1309.                 center("Downloading...")
  1310.  
  1311.                 local err = downloadAndSave(serverURL, "/fwserver")
  1312.  
  1313.                 fill(1, 11, w, 4, theme.background)
  1314.                 term.setCursorPos(1, 11)
  1315.                 center(err and "Download failed!" or "Download successful!")
  1316.             end
  1317.         end
  1318.     end
  1319. end
  1320.  
  1321.  
  1322. builtInSites["display"]["update"] = function()
  1323.     clear(theme.background, theme.text)
  1324.  
  1325.     fill(1, 3, w, 3, theme.subtle)
  1326.     term.setCursorPos(1, 4)
  1327.     center("Update")
  1328.  
  1329.     term.setBackgroundColor(theme.background)
  1330.     if not http then
  1331.         term.setCursorPos(1, 9)
  1332.         center("HTTP is not enabled!")
  1333.         print("")
  1334.         center("Please enable it in your config")
  1335.         center("file to download Firewolf updates.")
  1336.     else
  1337.         term.setCursorPos(1, 10)
  1338.         center("Checking for updates...")
  1339.  
  1340.         local available, err = updateAvailable()
  1341.  
  1342.         term.setCursorPos(1, 10)
  1343.         if available then
  1344.             term.clearLine()
  1345.             center("Update found!")
  1346.             center("Press enter to download.")
  1347.  
  1348.             while true do
  1349.                 local event, key = os.pullEvent()
  1350.                 if event == "key" and key == keys.enter then
  1351.                     break
  1352.                 end
  1353.             end
  1354.  
  1355.             fill(1, 10, w, 2, theme.background)
  1356.             term.setCursorPos(1, 10)
  1357.             center("Downloading...")
  1358.  
  1359.             local err = redownloadBrowser()
  1360.  
  1361.             term.setCursorPos(1, 10)
  1362.             term.clearLine()
  1363.             if err then
  1364.                 center("Download failed!")
  1365.             else
  1366.                 center("Download succeeded!")
  1367.                 center("Please restart Firewolf...")
  1368.             end
  1369.         elseif err then
  1370.             term.clearLine()
  1371.             center("Checking failed!")
  1372.         else
  1373.             term.clearLine()
  1374.             center("No updates found.")
  1375.         end
  1376.     end
  1377. end
  1378.  
  1379.  
  1380.  
  1381. --    Built In Websites
  1382.  
  1383.  
  1384. builtInSites["error"] = function(err)
  1385.     fill(1, 3, w, 3, theme.subtle)
  1386.     term.setCursorPos(1, 4)
  1387.     center("Failed to load page!")
  1388.  
  1389.     term.setBackgroundColor(theme.background)
  1390.     term.setCursorPos(1, 9)
  1391.     center(err)
  1392.     print("")
  1393.     center("Please try again.")
  1394. end
  1395.  
  1396.  
  1397. builtInSites["noresults"] = function()
  1398.     fill(1, 3, w, 3, theme.subtle)
  1399.     term.setCursorPos(1, 4)
  1400.     center("No results!")
  1401.  
  1402.     term.setBackgroundColor(theme.background)
  1403.     term.setCursorPos(1, 9)
  1404.     center("Your search didn't return")
  1405.     center("any results!")
  1406.  
  1407.     os.pullEvent("key")
  1408.     os.queueEvent("")
  1409.     os.pullEvent()
  1410. end
  1411.  
  1412.  
  1413. builtInSites["search advanced"] = function(results)
  1414.     local startY = 6
  1415.     local height = h - startY - 1
  1416.     local scroll = 0
  1417.  
  1418.     local draw = function()
  1419.         fill(1, startY, w, height + 1, theme.background)
  1420.  
  1421.         for i = scroll + 1, scroll + height do
  1422.             if results[i] then
  1423.                 term.setCursorPos(5, (i - scroll) + startY)
  1424.                 term.write(currentProtocol .. "://" .. results[i])
  1425.             end
  1426.         end
  1427.     end
  1428.  
  1429.     draw()
  1430.     while true do
  1431.         local event, but, x, y = os.pullEvent()
  1432.  
  1433.         if event == "mouse_click" and y >= startY and y <= startY + height then
  1434.             local item = results[y - startY + scroll]
  1435.             if item then
  1436.                 os.queueEvent(redirectEvent, item)
  1437.                 coroutine.yield()
  1438.             end
  1439.         elseif event == "key" then
  1440.             if but == keys.up then
  1441.                 scroll = math.max(0, scroll - 1)
  1442.             elseif but == keys.down and #results > height then
  1443.                 scroll = math.min(scroll + 1, #results - height)
  1444.             end
  1445.  
  1446.             draw()
  1447.         end
  1448.     end
  1449. end
  1450.  
  1451.  
  1452. builtInSites["search basic"] = function(results)
  1453.     local startY = 6
  1454.     local height = h - startY - 1
  1455.     local scroll = 0
  1456.     local selected = 1
  1457.  
  1458.     local draw = function()
  1459.         fill(1, startY, w, height + 1, theme.background)
  1460.  
  1461.         for i = scroll + 1, scroll + height do
  1462.             if results[i] then
  1463.                 if i == selected + scroll then
  1464.                     term.setCursorPos(3, (i - scroll) + startY)
  1465.                     term.write("> " .. currentProtocol .. "://" .. results[i])
  1466.                 else
  1467.                     term.setCursorPos(5, (i - scroll) + startY)
  1468.                     term.write(currentProtocol .. "://" .. results[i])
  1469.                 end
  1470.             end
  1471.         end
  1472.     end
  1473.  
  1474.     draw()
  1475.     while true do
  1476.         local event, but, x, y = os.pullEvent()
  1477.  
  1478.         if event == "key" then
  1479.             if but == keys.up and selected + scroll > 1 then
  1480.                 if selected > 1 then
  1481.                     selected = selected - 1
  1482.                 else
  1483.                     scroll = math.max(0, scroll - 1)
  1484.                 end
  1485.             elseif but == keys.down and selected + scroll < #results then
  1486.                 if selected < height then
  1487.                     selected = selected + 1
  1488.                 else
  1489.                     scroll = math.min(scroll + 1, #results - height)
  1490.                 end
  1491.             elseif but == keys.enter then
  1492.                 local item = results[scroll + selected]
  1493.                 if item then
  1494.                     os.queueEvent(redirectEvent, item)
  1495.                     coroutine.yield()
  1496.                 end
  1497.             end
  1498.  
  1499.             draw()
  1500.         end
  1501.     end
  1502. end
  1503.  
  1504.  
  1505. builtInSites["search"] = function(results)
  1506.     clear(theme.background, theme.text)
  1507.  
  1508.     fill(1, 3, w, 3, theme.subtle)
  1509.     term.setCursorPos(1, 4)
  1510.     center(#results .. " Search " .. (#results == 1 and "Result" or "Results"))
  1511.  
  1512.     term.setBackgroundColor(theme.background)
  1513.  
  1514.     if term.isColor() then
  1515.         builtInSites["search advanced"](results)
  1516.     else
  1517.         builtInSites["search basic"](results)
  1518.     end
  1519. end
  1520.  
  1521.  
  1522. builtInSites["crash"] = function(err)
  1523.     fill(1, 3, w, 3, theme.subtle)
  1524.     term.setCursorPos(1, 4)
  1525.     center("The website crashed!")
  1526.  
  1527.     term.setBackgroundColor(theme.background)
  1528.     term.setCursorPos(1, 8)
  1529.     centerSplit(err, w - 4)
  1530.     print("\n")
  1531.     center("Please report this error to")
  1532.     center("the website creator.")
  1533. end
  1534.  
  1535.  
  1536.  
  1537. --    Menubar
  1538.  
  1539.  
  1540. local getTabName = function(url)
  1541.     local name = url:match("^[^/]+")
  1542.  
  1543.     if not name then
  1544.         name = "Search"
  1545.     end
  1546.  
  1547.     if name:sub(1, 3) == "www" then
  1548.         name = name:sub(5):gsub("^%s*(.-)%s*$", "%1")
  1549.     end
  1550.  
  1551.     if name:len() > maxTabNameWidth then
  1552.         name = name:sub(1, maxTabNameWidth):gsub("^%s*(.-)%s*$", "%1")
  1553.     end
  1554.  
  1555.     if name:sub(-1, -1) == "." then
  1556.         name = name:sub(1, -2):gsub("^%s*(.-)%s*$", "%1")
  1557.     end
  1558.  
  1559.     return name:gsub("^%s*(.-)%s*$", "%1")
  1560. end
  1561.  
  1562.  
  1563. local determineClickedTab = function(x, y)
  1564.     if y == 2 then
  1565.         local minx = 2
  1566.         for i, tab in pairs(tabs) do
  1567.             local name = getTabName(tab.url)
  1568.  
  1569.             if x >= minx and x <= minx + name:len() - 1 then
  1570.                 return i
  1571.             elseif x == minx + name:len() and i == currentTab and #tabs > 1 then
  1572.                 return "close"
  1573.             else
  1574.                 minx = minx + name:len() + 2
  1575.             end
  1576.         end
  1577.  
  1578.         if x == minx and #tabs < maxTabs then
  1579.             return "new"
  1580.         end
  1581.     end
  1582.  
  1583.     return nil
  1584. end
  1585.  
  1586.  
  1587. local setupMenubar = function()
  1588.     if enableTabBar then
  1589.         menubarWindow = window.create(term.native(), 1, 1, w, 2, false)
  1590.     else
  1591.         menubarWindow = window.create(term.native(), 1, 1, w, 1, false)
  1592.     end
  1593. end
  1594.  
  1595.  
  1596. local drawMenubar = function()
  1597.     if isMenubarOpen then
  1598.         term.redirect(menubarWindow)
  1599.         menubarWindow.setVisible(true)
  1600.  
  1601.         fill(1, 1, w, 1, theme.accent)
  1602.         term.setTextColor(theme.text)
  1603.  
  1604.         term.setBackgroundColor(theme.accent)
  1605.         term.setCursorPos(2, 1)
  1606.         term.write(currentProtocol .. "://" .. currentWebsiteURL)
  1607.  
  1608.         term.setCursorPos(w - 5, 1)
  1609.         term.write("[===]")
  1610.  
  1611.         if enableTabBar then
  1612.             fill(1, 2, w, 1, theme.subtle)
  1613.  
  1614.             term.setCursorPos(1, 2)
  1615.             for i, tab in pairs(tabs) do
  1616.                 term.setBackgroundColor(theme.subtle)
  1617.                 term.setTextColor(theme.lightText)
  1618.                 if i == currentTab then
  1619.                     term.setTextColor(theme.text)
  1620.                 end
  1621.  
  1622.                 local tabName = getTabName(tab.url)
  1623.                 term.write(" " .. tabName)
  1624.  
  1625.                 if i == currentTab and #tabs > 1 then
  1626.                     term.setTextColor(theme.errorText)
  1627.                     term.write("x")
  1628.                 else
  1629.                     term.write(" ")
  1630.                 end
  1631.             end
  1632.  
  1633.             if #tabs < maxTabs then
  1634.                 term.setTextColor(theme.lightText)
  1635.                 term.setBackgroundColor(theme.subtle)
  1636.                 term.write(" + ")
  1637.             end
  1638.         end
  1639.     else
  1640.         menubarWindow.setVisible(false)
  1641.     end
  1642. end
  1643.  
  1644.  
  1645.  
  1646. --    RDNT Protocol
  1647.  
  1648.  
  1649. protocols["rdnt"] = {}
  1650.  
  1651.  
  1652. local calculateChannel = function(domain, distance, id)
  1653.     local total = 1
  1654.  
  1655.     if distance then
  1656.         id = (id + 3642 * math.pi) % 100000
  1657.         if tostring(distance):find("%.") then
  1658.             local distProc = (tostring(distance):sub(1, tostring(distance):find("%.") + 1)):gsub("%.", "")
  1659.             total = tonumber(distProc..id)
  1660.         else
  1661.             total = tonumber(distance..id)
  1662.         end
  1663.     end
  1664.  
  1665.     for i = 1, #domain do
  1666.         total = total * string.byte(domain:sub(i, i))
  1667.         if total > 10000000000 then
  1668.             total = tonumber(tostring(total):sub(-5, -1))
  1669.         end
  1670.         while tostring(total):sub(-1, -1) == "0" do
  1671.             total = tonumber(tostring(total):sub(1, -2))
  1672.         end
  1673.     end
  1674.  
  1675.     return (total % 50000) + 10000
  1676. end
  1677.  
  1678.  
  1679. protocols["rdnt"]["setup"] = function()
  1680.     for _, v in pairs(redstone.getSides()) do
  1681.         if peripheral.getType(v) == "modem" then
  1682.             table.insert(sides, v)
  1683.         end
  1684.     end
  1685.  
  1686.     if #sides <= 0 then
  1687.         error("No modem found!")
  1688.     end
  1689. end
  1690.  
  1691.  
  1692. protocols["rdnt"]["modem"] = function(func, ...)
  1693.     for _, side in pairs(sides) do
  1694.         if peripheral.getType(side) == "modem" then
  1695.             peripheral.call(side, func, ...)
  1696.         end
  1697.     end
  1698.  
  1699.     return true
  1700. end
  1701.  
  1702.  
  1703. protocols["rdnt"]["fetchAllSearchResults"] = function()
  1704.     local results = {}
  1705.     local toDelete = {}
  1706.  
  1707.     local checkResults = function(distance)
  1708.         local repeatedResults = {}
  1709.         for k, result in pairs(results) do
  1710.             if result == distance then
  1711.                 if not repeatedResults[tostring(result)] then
  1712.                     repeatedResults[tostring(result)] = 1
  1713.                 elseif repeatedResults[tostring(result)] >= serverLimitPerComputer - 1 then
  1714.                     table.insert(toDelete, result)
  1715.                     return false
  1716.                 else
  1717.                     repeatedResults[tostring(result)] = repeatedResults[tostring(result)] + 1
  1718.                 end
  1719.             end
  1720.         end
  1721.  
  1722.         return true
  1723.     end
  1724.  
  1725.     protocols.rdnt.modem("open", publicResponseChannel)
  1726.     protocols.rdnt.modem("open", publicDNSChannel)
  1727.  
  1728.     if allowUnencryptedConnections then
  1729.         for _, side in pairs(sides) do
  1730.             if peripheral.getType(side) == "modem" then
  1731.                 rednet.open(side)
  1732.             end
  1733.         end
  1734.  
  1735.         rednet.broadcast(listToken, listToken)
  1736.     end
  1737.  
  1738.     protocols.rdnt.modem("transmit", publicDNSChannel, responseID, listToken)
  1739.     protocols.rdnt.modem("close", publicDNSChannel)
  1740.  
  1741.     local timer = os.startTimer(searchResultTimeout)
  1742.     while true do
  1743.         local event, connectionSide, channel, verify, msg, distance = os.pullEvent()
  1744.  
  1745.         if event == "modem_message" and channel == publicResponseChannel and verify == responseID then
  1746.             if msg:match(DNSToken) and #msg:match(DNSToken) >= 4 and #msg:match(DNSToken) <= 30 then
  1747.                 if checkResults(distance) then
  1748.                     results[msg:match(DNSToken)] = distance
  1749.                 end
  1750.             end
  1751.         elseif event == "rednet_message" and verify == listToken and allowUnencryptedConnections then
  1752.             if channel:match(DNSToken) and #channel:match(DNSToken) >= 4 and #channel:match(DNSToken) <= 30 then
  1753.                 results[channel:match(DNSToken)] = -1
  1754.             end
  1755.         elseif event == "timer" and connectionSide == timer then
  1756.             local finalResult = {}
  1757.             for k, v in pairs(results) do
  1758.                 local shouldDelete = false
  1759.                 for b, n in pairs(toDelete) do
  1760.                     if v > 0 and tostring(n) == tostring(v) then
  1761.                         shouldDelete = true
  1762.                     end
  1763.                 end
  1764.  
  1765.                 if not shouldDelete then
  1766.                     table.insert(finalResult, k:lower())
  1767.                 end
  1768.             end
  1769.  
  1770.             if allowUnencryptedConnections then
  1771.                 for _, side in pairs(sides) do
  1772.                     if peripheral.getType(side) == "modem" then
  1773.                         rednet.close(side)
  1774.                     end
  1775.                 end
  1776.             end
  1777.  
  1778.             protocols.rdnt.modem("close", publicResponseChannel)
  1779.  
  1780.             return finalResult
  1781.         end
  1782.     end
  1783. end
  1784.  
  1785.  
  1786. protocols["rdnt"]["fetchConnectionObject"] = function(url)
  1787.     local channel = calculateChannel(url)
  1788.     local results = {}
  1789.     local unencryptedResults = {}
  1790.  
  1791.     local checkDuplicate = function(distance)
  1792.         for k, v in pairs(results) do
  1793.             if v.dist == distance then
  1794.                 return true
  1795.             end
  1796.         end
  1797.  
  1798.         return false
  1799.     end
  1800.  
  1801.     local checkRednetDuplicate = function(id)
  1802.         for k, v in pairs(unencryptedResults) do
  1803.             if v.id == id then
  1804.                 return true
  1805.             end
  1806.         end
  1807.  
  1808.         return false
  1809.     end
  1810.  
  1811.     protocols.rdnt.modem("closeAll")
  1812.     protocols.rdnt.modem("open", channel)
  1813.     protocols.rdnt.modem("transmit", channel, os.getComputerID(), initiateToken .. url)
  1814.  
  1815.     local timer = os.startTimer(initiationTimeout)
  1816.     while true do
  1817.         local event, connectionSide, connectionChannel, verify, msg, distance = os.pullEvent()
  1818.  
  1819.         if event == "modem_message" and connectionChannel == channel and verify == responseID then
  1820.             local decrypt = crypt(textutils.unserialize(msg), url .. tostring(distance) .. os.getComputerID())
  1821.             if decrypt and decrypt:match(connectToken) == url and
  1822.                     not checkDuplicate(distance) then
  1823.                 local calculatedChannel = calculateChannel(url, distance, os.getComputerID())
  1824.  
  1825.                 table.insert(results, {
  1826.                     dist = distance,
  1827.                     channel = calculatedChannel,
  1828.                     url = url,
  1829.                     wired = peripheral.call(connectionSide, "isWireless"),
  1830.                     encrypted = true,
  1831.  
  1832.                     fetchPage = function(page)
  1833.                         protocols.rdnt.modem("open", calculatedChannel)
  1834.  
  1835.                         local fetchTimer = os.startTimer(fetchTimeout)
  1836.                         protocols.rdnt.modem("transmit", calculatedChannel, os.getComputerID(), crypt(fetchToken .. url .. page, url .. tostring(distance) .. os.getComputerID()))
  1837.  
  1838.                         while true do
  1839.                             local event, fetchSide, fetchChannel, fetchVerify, fetchMessage, fetchDistance = os.pullEvent()
  1840.  
  1841.                             if event == "modem_message" and fetchChannel == calculatedChannel and
  1842.                                     fetchVerify == responseID and fetchDistance == distance then
  1843.                                 local rawHeader, data = crypt(textutils.unserialize(fetchMessage), url .. tostring(fetchDistance) .. os.getComputerID()):match(receiveToken)
  1844.                                 local header = textutils.unserialize(rawHeader)
  1845.  
  1846.                                 if data and header then
  1847.                                     protocols.rdnt.modem("close", calculatedChannel)
  1848.                                     return data, header
  1849.                                 end
  1850.                             elseif event == "timer" and fetchSide == fetchTimer then
  1851.                                 protocols.rdnt.modem("close", calculatedChannel)
  1852.                                 return nil
  1853.                             end
  1854.                         end
  1855.                     end,
  1856.  
  1857.                     close = function()
  1858.                         protocols.rdnt.modem("open", calculatedChannel)
  1859.                         protocols.rdnt.modem("transmit", calculatedChannel, os.getComputerID(), crypt(disconnectToken, url .. tostring(distance) .. os.getComputerID()))
  1860.                         protocols.rdnt.modem("close", calculatedChannel)
  1861.                     end
  1862.                 })
  1863.             end
  1864.         elseif event == "timer" and connectionSide == timer then
  1865.             protocols.rdnt.modem("close", channel)
  1866.  
  1867.             if #results == 0 then
  1868.                 break
  1869.             elseif #results == 1 then
  1870.                 return results[1]
  1871.             else
  1872.                 local wiredResults = {}
  1873.                 for k, v in pairs(results) do
  1874.                     if v.wired then
  1875.                         table.insert(wiredResults, v)
  1876.                     end
  1877.                 end
  1878.                 if #wiredResults == 0 then
  1879.                     local finalResult = {multipleServers = true, servers = results}
  1880.                     return finalResult
  1881.                 elseif #wiredResults == 1 then
  1882.                     return wiredResults[1]
  1883.                 else
  1884.                     local finalResult = {multipleServers = true, servers = wiredResults}
  1885.                     return finalResult
  1886.                 end
  1887.             end
  1888.         end
  1889.     end
  1890.  
  1891.     if allowUnencryptedConnections then
  1892.         for _, side in pairs(sides) do
  1893.             if peripheral.getType(side) == "modem" then
  1894.                 rednet.open(side)
  1895.             end
  1896.         end
  1897.  
  1898.         local ret = {rednet.lookup(protocolToken .. url)}
  1899.  
  1900.         for _, v in pairs(ret) do
  1901.             table.insert(unencryptedResults, {
  1902.                 dist = v,
  1903.                 channel = -1,
  1904.                 url = url,
  1905.                 encrypted = false,
  1906.                 wired = false,
  1907.                 id = v,
  1908.  
  1909.                 fetchPage = function(page)
  1910.                     for _, side in pairs(sides) do
  1911.                         if peripheral.getType(side) == "modem" then
  1912.                             rednet.open(side)
  1913.                         end
  1914.                     end
  1915.  
  1916.                     local fetchTimer = os.startTimer(fetchTimeout)
  1917.                     rednet.send(v, crypt(fetchToken .. url .. page, url .. tostring(os.getComputerID())), protocolToken .. url)
  1918.  
  1919.                     while true do
  1920.                         local event, fetchId, fetchMessage, fetchProtocol = os.pullEvent()
  1921.                         if event == "rednet_message" and fetchId == v and fetchProtocol == (protocolToken .. url) then
  1922.                             local decrypt = crypt(textutils.unserialize(fetchMessage), url .. tostring(os.getComputerID()))
  1923.                             if decrypt then
  1924.                                 local rawHeader, data = decrypt:match(receiveToken)
  1925.                                 local header = textutils.unserialize(rawHeader)
  1926.                                 if data and header then
  1927.                                     for _, side in pairs(sides) do
  1928.                                         if peripheral.getType(side) == "modem" then
  1929.                                             rednet.close(side)
  1930.                                         end
  1931.                                     end
  1932.  
  1933.                                     return data, header
  1934.                                 end
  1935.                             end
  1936.                         elseif event == "timer" and fetchId == fetchTimer then
  1937.                             for _, side in pairs(sides) do
  1938.                                 if peripheral.getType(side) == "modem" then
  1939.                                     rednet.close(side)
  1940.                                 end
  1941.                             end
  1942.  
  1943.                             return nil
  1944.                         end
  1945.                     end
  1946.                 end,
  1947.  
  1948.                 close = function()
  1949.                     for _, side in pairs(sides) do
  1950.                         if peripheral.getType(side) == "modem" then
  1951.                             rednet.open(side)
  1952.                         end
  1953.                     end
  1954.  
  1955.                     rednet.send(v, crypt(disconnectToken, url .. tostring(os.getComputerID())), protocolToken .. url)
  1956.  
  1957.                     for _, side in pairs(sides) do
  1958.                         if peripheral.getType(side) == "modem" then
  1959.                             rednet.close(side)
  1960.                         end
  1961.                     end
  1962.                 end
  1963.             })
  1964.         end
  1965.         if #unencryptedResults == 0 then
  1966.             return nil
  1967.         elseif #unencryptedResults == 1 then
  1968.             return unencryptedResults[1]
  1969.         else
  1970.             local finalResult = {multipleServers = true, servers = unencryptedResults}
  1971.             return finalResult
  1972.         end
  1973.     end
  1974.  
  1975.     return nil
  1976. end
  1977.  
  1978.  
  1979.  
  1980. --    Fetching Raw Data
  1981.  
  1982.  
  1983. local fetchSearchResultsForQuery = function(query)
  1984.     local all = protocols[currentProtocol]["fetchAllSearchResults"]()
  1985.     local results = {}
  1986.     if query and query:len() > 0 then
  1987.         for _, v in pairs(all) do
  1988.             if v:find(query:lower()) then
  1989.                 table.insert(results, v)
  1990.             end
  1991.         end
  1992.     else
  1993.         results = all
  1994.     end
  1995.  
  1996.     table.sort(results)
  1997.     return results
  1998. end
  1999.  
  2000.  
  2001. local getConnectionObjectFromURL = function(url)
  2002.     local domain = url:match("^([^/]+)")
  2003.     return protocols[currentProtocol]["fetchConnectionObject"](domain)
  2004. end
  2005.  
  2006.  
  2007. local determineLanguage = function(header)
  2008.     if type(header) == "table" then
  2009.         if header.language and header.language == "Firewolf Markup" then
  2010.             return "fwml"
  2011.         else
  2012.             return "lua"
  2013.         end
  2014.     else
  2015.         return "lua"
  2016.     end
  2017. end
  2018.  
  2019.  
  2020.  
  2021. --    History
  2022.  
  2023.  
  2024. local appendToHistory = function(url)
  2025.     if history[1] ~= url then
  2026.         table.insert(history, 1, url)
  2027.     end
  2028. end
  2029.  
  2030.  
  2031.  
  2032. --    Fetch Websites
  2033.  
  2034.  
  2035. local loadingAnimation = function()
  2036.     local state = -2
  2037.  
  2038.     term.setTextColor(theme.text)
  2039.     term.setBackgroundColor(theme.accent)
  2040.  
  2041.     term.setCursorPos(w - 5, 1)
  2042.     term.write("[=  ]")
  2043.  
  2044.     local timer = os.startTimer(animationInterval)
  2045.  
  2046.     while true do
  2047.         local event, timerID = os.pullEvent()
  2048.         if event == "timer" and timerID == timer then
  2049.             term.setTextColor(theme.text)
  2050.             term.setBackgroundColor(theme.accent)
  2051.  
  2052.             state = state + 1
  2053.  
  2054.             term.setCursorPos(w - 5, 1)
  2055.             term.write("[   ]")
  2056.             term.setCursorPos(w - 2 - math.abs(state), 1)
  2057.             term.write("=")
  2058.  
  2059.             if state == 2 then
  2060.                 state = -2
  2061.             end
  2062.  
  2063.             timer = os.startTimer(animationInterval)
  2064.         end
  2065.     end
  2066. end
  2067.  
  2068.  
  2069. local normalizeURL = function(url)
  2070.     url = url:lower():gsub(" ", "")
  2071.     if url == "home" or url == "homepage" then
  2072.         url = "firewolf"
  2073.     end
  2074.  
  2075.     return url
  2076. end
  2077.  
  2078.  
  2079. local normalizePage = function(page)
  2080.     page = page:lower()
  2081.     if page == "" then
  2082.         page = "/"
  2083.     end
  2084.     return page
  2085. end
  2086.  
  2087.  
  2088. local determineActionForURL = function(url)
  2089.     if url:len() > 0 and url:gsub("/", ""):len() == 0 then
  2090.         return "none"
  2091.     end
  2092.  
  2093.     if url == "exit" then
  2094.         return "exit"
  2095.     elseif builtInSites["display"][url] then
  2096.         return "internal website"
  2097.     elseif url == "" then
  2098.         local results = fetchSearchResultsForQuery()
  2099.         if #results > 0 then
  2100.             return "search", results
  2101.         else
  2102.             return "none"
  2103.         end
  2104.     else
  2105.         local connection = getConnectionObjectFromURL(url)
  2106.         if connection then
  2107.             return "external website", connection
  2108.         else
  2109.             local results = fetchSearchResultsForQuery(url)
  2110.             if #results > 0 then
  2111.                 return "search", results
  2112.             else
  2113.                 return "none"
  2114.             end
  2115.         end
  2116.     end
  2117. end
  2118.  
  2119.  
  2120. local fetchSearch = function(url, results)
  2121.     return languages["lua"]["runWithoutAntivirus"](builtInSites["search"], results)
  2122. end
  2123.  
  2124.  
  2125. local fetchInternal = function(url)
  2126.     return languages["lua"]["runWithoutAntivirus"](builtInSites["display"][url])
  2127. end
  2128.  
  2129.  
  2130. local fetchError = function(err)
  2131.     return languages["lua"]["runWithoutAntivirus"](builtInSites["error"], err)
  2132. end
  2133.  
  2134.  
  2135. local fetchExternal = function(url, connection)
  2136.  
  2137.     if connection.multipleServers then
  2138.         -- Please forgive me
  2139.         -- GravityScore forced me to do it like this
  2140.         -- I don't mean it, I really don't.
  2141.         connection = connection.servers[1]
  2142.     end
  2143.  
  2144.     local page = normalizePage(url:match("^[^/]+(.+)"))
  2145.     local contents, header = connection.fetchPage(page)
  2146.     connection.close()
  2147.     if contents then
  2148.         if type(contents) ~= "string" then
  2149.             return fetchNone()
  2150.         else
  2151.             local language = determineLanguage(header)
  2152.             return languages[language]["run"](contents, page)
  2153.         end
  2154.     else
  2155.         return fetchError("A connection timeout occurred!")
  2156.     end
  2157. end
  2158.  
  2159.  
  2160. local fetchNone = function()
  2161.     return languages["lua"]["runWithoutAntivirus"](builtInSites["noresults"])
  2162. end
  2163.  
  2164.  
  2165. local fetchURL = function(url)
  2166.     url = normalizeURL(url)
  2167.     currentWebsiteURL = url
  2168.  
  2169.     local action, connection = determineActionForURL(url)
  2170.  
  2171.     if action == "search" then
  2172.         return fetchSearch(url, connection), true
  2173.     elseif action == "internal website" then
  2174.         return fetchInternal(url), true
  2175.     elseif action == "external website" then
  2176.         return fetchExternal(url, connection), false
  2177.     elseif action == "none" then
  2178.         return fetchNone(), true
  2179.     elseif action == "exit" then
  2180.         os.queueEvent("terminate")
  2181.     end
  2182.  
  2183.     return nil
  2184. end
  2185.  
  2186.  
  2187.  
  2188. --    Tabs
  2189.  
  2190.  
  2191. local switchTab = function(index, shouldntResume)
  2192.     if not tabs[index] then
  2193.         return
  2194.     end
  2195.  
  2196.     if tabs[currentTab].win then
  2197.         tabs[currentTab].win.setVisible(false)
  2198.     end
  2199.  
  2200.     currentTab = index
  2201.     isMenubarOpen = tabs[currentTab].isMenubarOpen
  2202.     currentWebsiteURL = tabs[currentTab].url
  2203.  
  2204.     term.redirect(term.native())
  2205.     clear(theme.background, theme.text)
  2206.     drawMenubar()
  2207.  
  2208.     term.redirect(tabs[currentTab].win)
  2209.     term.setCursorPos(1, 1)
  2210.     tabs[currentTab].win.setVisible(true)
  2211.     tabs[currentTab].win.redraw()
  2212.  
  2213.     if not shouldntResume then
  2214.         coroutine.resume(tabs[currentTab].thread)
  2215.     end
  2216. end
  2217.  
  2218.  
  2219. local closeCurrentTab = function()
  2220.     if #tabs <= 0 then
  2221.         return
  2222.     end
  2223.  
  2224.     table.remove(tabs, currentTab)
  2225.  
  2226.     currentTab = math.max(currentTab - 1, 1)
  2227.     switchTab(currentTab, true)
  2228. end
  2229.  
  2230.  
  2231. local loadTab = function(index, url, givenFunc)
  2232.     url = normalizeURL(url)
  2233.  
  2234.     local func = nil
  2235.     local isOpen = true
  2236.  
  2237.     isMenubarOpen = true
  2238.     currentWebsiteURL = url
  2239.     drawMenubar()
  2240.  
  2241.     if givenFunc then
  2242.         func = givenFunc
  2243.     else
  2244.         parallel.waitForAny(function()
  2245.             func, isOpen = fetchURL(url)
  2246.         end, function()
  2247.             while true do
  2248.                 local event, key = os.pullEvent()
  2249.                 if event == "key" and (key == 29 or key == 157) then
  2250.                     break
  2251.                 end
  2252.             end
  2253.         end, loadingAnimation)
  2254.     end
  2255.  
  2256.     if func then
  2257.         appendToHistory(url)
  2258.  
  2259.         tabs[index] = {}
  2260.         tabs[index].url = url
  2261.         tabs[index].win = window.create(term.native(), 1, 1, w, h, false)
  2262.  
  2263.         tabs[index].thread = coroutine.create(func)
  2264.         tabs[index].isMenubarOpen = isOpen
  2265.         tabs[index].isMenubarPermanent = isOpen
  2266.  
  2267.         tabs[index].ox = 1
  2268.         tabs[index].oy = 1
  2269.  
  2270.         term.redirect(tabs[index].win)
  2271.         clear(theme.background, theme.text)
  2272.  
  2273.         switchTab(index)
  2274.     end
  2275. end
  2276.  
  2277.  
  2278.  
  2279. --    Website Environments
  2280.  
  2281.  
  2282. local getWhitelistedEnvironment = function()
  2283.     local env = {}
  2284.  
  2285.     local function copy(source, destination, key)
  2286.         destination[key] = {}
  2287.         for k, v in pairs(source) do
  2288.             destination[key][k] = v
  2289.         end
  2290.     end
  2291.  
  2292.     copy(bit, env, "bit")
  2293.     copy(colors, env, "colors")
  2294.     copy(colours, env, "colours")
  2295.     copy(coroutine, env, "coroutine")
  2296.  
  2297.     copy(disk, env, "disk")
  2298.     env["disk"]["setLabel"] = nil
  2299.     env["disk"]["eject"] = nil
  2300.  
  2301.     copy(gps, env, "gps")
  2302.     copy(help, env, "help")
  2303.     copy(keys, env, "keys")
  2304.     copy(math, env, "math")
  2305.  
  2306.     copy(os, env, "os")
  2307.     env["os"]["run"] = nil
  2308.     env["os"]["shutdown"] = nil
  2309.     env["os"]["reboot"] = nil
  2310.     env["os"]["setComputerLabel"] = nil
  2311.     env["os"]["queueEvent"] = nil
  2312.     env["os"]["pullEventRaw"] = os.pullEvent
  2313.  
  2314.     copy(paintutils, env, "paintutils")
  2315.     copy(parallel, env, "parallel")
  2316.     copy(peripheral, env, "peripheral")
  2317.     copy(rednet, env, "rednet")
  2318.     copy(redstone, env, "redstone")
  2319.     copy(redstone, env, "rs")
  2320.  
  2321.     copy(shell, env, "shell")
  2322.     env["shell"]["run"] = nil
  2323.     env["shell"]["exit"] = nil
  2324.     env["shell"]["setDir"] = nil
  2325.     env["shell"]["setAlias"] = nil
  2326.     env["shell"]["clearAlias"] = nil
  2327.     env["shell"]["setPath"] = nil
  2328.  
  2329.     copy(string, env, "string")
  2330.     copy(table, env, "table")
  2331.  
  2332.     copy(term, env, "term")
  2333.     env["term"]["redirect"] = nil
  2334.     env["term"]["restore"] = nil
  2335.  
  2336.     copy(textutils, env, "textutils")
  2337.     copy(vector, env, "vector")
  2338.  
  2339.     if turtle then
  2340.         copy(turtle, env, "turtle")
  2341.     end
  2342.  
  2343.     if http then
  2344.         copy(http, env, "http")
  2345.     end
  2346.  
  2347.     env["assert"] = assert
  2348.     env["printError"] = printError
  2349.     env["tonumber"] = tonumber
  2350.     env["tostring"] = tostring
  2351.     env["type"] = type
  2352.     env["next"] = next
  2353.     env["unpack"] = unpack
  2354.     env["pcall"] = pcall
  2355.     env["xpcall"] = xpcall
  2356.     env["sleep"] = sleep
  2357.     env["pairs"] = pairs
  2358.     env["ipairs"] = ipairs
  2359.     env["read"] = read
  2360.     env["write"] = write
  2361.     env["select"] = select
  2362.     env["print"] = print
  2363.     env["setmetatable"] = setmetatable
  2364.     env["getmetatable"] = getmetatable
  2365.  
  2366.     env["_G"] = env
  2367.     return env
  2368. end
  2369.  
  2370.  
  2371. local overrideEnvironment = function(env)
  2372.     local localTerm = {}
  2373.     for k, v in pairs(term) do
  2374.         localTerm[k] = v
  2375.     end
  2376.  
  2377.     env["term"]["clear"] = function()
  2378.         localTerm.clear()
  2379.         drawMenubar()
  2380.     end
  2381.  
  2382.     env["term"]["scroll"] = function(n)
  2383.         localTerm.scroll(n)
  2384.         drawMenubar()
  2385.     end
  2386.  
  2387.  
  2388.     env["shell"]["getRunningProgram"] = function()
  2389.         return currentWebsiteURL
  2390.     end
  2391. end
  2392.  
  2393.  
  2394. local applyAPIFunctions = function(env)
  2395.     env["firewolf"] = {}
  2396.     env["firewolf"]["version"] = version
  2397.  
  2398.     env["firewolf"]["redirect"] = function(url)
  2399.         if not url then
  2400.             error("string expected, got nil", 2)
  2401.         end
  2402.  
  2403.         os.queueEvent(redirectEvent, url)
  2404.         coroutine.yield()
  2405.     end
  2406.  
  2407.     env["center"] = center
  2408.     env["fill"] = fill
  2409. end
  2410.  
  2411.  
  2412. local getWebsiteEnvironment = function(antivirus)
  2413.     local env = {}
  2414.  
  2415.     if antivirus then
  2416.         env = getWhitelistedEnvironment()
  2417.         overrideEnvironment(env)
  2418.     else
  2419.         setmetatable(env, {__index = _G})
  2420.     end
  2421.  
  2422.     applyAPIFunctions(env)
  2423.  
  2424.     return env
  2425. end
  2426.  
  2427.  
  2428.  
  2429. --    FWML Execution
  2430.  
  2431.  
  2432. local render = {}
  2433.  
  2434. render["functions"] = {}
  2435. render["functions"]["public"] = {}
  2436. render["alignations"] = {}
  2437.  
  2438. render["variables"] = {
  2439.     scroll,
  2440.     maxScroll,
  2441.     align,
  2442.     linkData = {},
  2443.     blockLength,
  2444.     link,
  2445.     linkStart,
  2446.     markers,
  2447.     currentOffset,
  2448. }
  2449.  
  2450.  
  2451. local function getLine(loc, data)
  2452.     local _, changes = data:sub(1, loc):gsub("\n", "")
  2453.     if not changes then
  2454.         return 1
  2455.     else
  2456.         return changes + 1
  2457.     end
  2458. end
  2459.  
  2460.  
  2461. local function parseData(data)
  2462.     local commands = {}
  2463.     local searchPos = 1
  2464.  
  2465.     while #data > 0 do
  2466.         local sCmd, eCmd = data:find("%[[^%]]+%]", searchPos)
  2467.         if sCmd then
  2468.             sCmd = sCmd + 1
  2469.             eCmd = eCmd - 1
  2470.  
  2471.             if (sCmd > 2) then
  2472.                 if data:sub(sCmd - 2, sCmd - 2) == "\\" then
  2473.                     local t = data:sub(searchPos, sCmd - 1):gsub("\n", ""):gsub("\\%[", "%["):gsub("\\%]", "%]")
  2474.                     if #t > 0 then
  2475.                         if #commands > 0 and type(commands[#commands][1]) == "string" then
  2476.                             commands[#commands][1] = commands[#commands][1] .. t
  2477.                         else
  2478.                             table.insert(commands, {t})
  2479.                         end
  2480.                     end
  2481.                     searchPos = sCmd
  2482.                 else
  2483.                     local t = data:sub(searchPos, sCmd - 2):gsub("\n", ""):gsub("\\%[", "%["):gsub("\\%]", "%]")
  2484.                     if #t > 0 then
  2485.                         if #commands > 0 and type(commands[#commands][1]) == "string" then
  2486.                             commands[#commands][1] = commands[#commands][1] .. t
  2487.                         else
  2488.                             table.insert(commands, {t})
  2489.                         end
  2490.                     end
  2491.  
  2492.                     t = data:sub(sCmd, eCmd):gsub("\n", "")
  2493.                     table.insert(commands, {getLine(sCmd, data), t})
  2494.                     searchPos = eCmd + 2
  2495.                 end
  2496.             else
  2497.                 local t = data:sub(sCmd, eCmd):gsub("\n", "")
  2498.                 table.insert(commands, {getLine(sCmd, data), t})
  2499.                 searchPos = eCmd + 2
  2500.             end
  2501.         else
  2502.             local t = data:sub(searchPos, -1):gsub("\n", ""):gsub("\\%[", "%["):gsub("\\%]", "%]")
  2503.             if #t > 0 then
  2504.                 if #commands > 0 and type(commands[#commands][1]) == "string" then
  2505.                     commands[#commands][1] = commands[#commands][1] .. t
  2506.                 else
  2507.                     table.insert(commands, {t})
  2508.                 end
  2509.             end
  2510.  
  2511.             break
  2512.         end
  2513.     end
  2514.  
  2515.     return commands
  2516. end
  2517.  
  2518.  
  2519. local function proccessData(commands)
  2520.     searchIndex = 0
  2521.  
  2522.     while searchIndex < #commands do
  2523.         searchIndex = searchIndex + 1
  2524.  
  2525.         local length = 0
  2526.         local origin = searchIndex
  2527.  
  2528.         if type(commands[searchIndex][1]) == "string" then
  2529.             length = length + #commands[searchIndex][1]
  2530.             local endIndex = origin
  2531.             for i = origin + 1, #commands do
  2532.                 if commands[i][2] then
  2533.                     local command = commands[i][2]:match("^(%w+)%s-")
  2534.                     if not (command == "c" or command == "color" or command == "bg"
  2535.                             or command == "background" or command == "newlink" or command == "endlink") then
  2536.                         endIndex = i
  2537.                         break
  2538.                     end
  2539.                 elseif commands[i][2] then
  2540.  
  2541.                 else
  2542.                     length = length + #commands[i][1]
  2543.                 end
  2544.                 if i == #commands then
  2545.                     endIndex = i
  2546.                 end
  2547.             end
  2548.  
  2549.             commands[origin][2] = length
  2550.             searchIndex = endIndex
  2551.             length = 0
  2552.         end
  2553.     end
  2554.  
  2555.     return commands
  2556. end
  2557.  
  2558.  
  2559. local function parse(original)
  2560.     return proccessData(parseData(original))
  2561. end
  2562.  
  2563.  
  2564. render["functions"]["display"] = function(text, length, offset, center)
  2565.     if not offset then
  2566.         offset = 0
  2567.     end
  2568.  
  2569.     return render.variables.align(text, length, w, offset, center);
  2570. end
  2571.  
  2572.  
  2573. render["functions"]["displayText"] = function(source)
  2574.     if source[2] then
  2575.         render.variables.blockLength = source[2]
  2576.         if render.variables.link and not render.variables.linkStart then
  2577.             render.variables.linkStart = render.functions.display(
  2578.                 source[1], render.variables.blockLength, render.variables.currentOffset, w / 2)
  2579.         else
  2580.             render.functions.display(source[1], render.variables.blockLength, render.variables.currentOffset, w / 2)
  2581.         end
  2582.     else
  2583.         if render.variables.link and not render.variables.linkStart then
  2584.             render.variables.linkStart = render.functions.display(source[1], nil, render.variables.currentOffset, w / 2)
  2585.         else
  2586.             render.functions.display(source[1], nil, render.variables.currentOffset, w / 2)
  2587.         end
  2588.     end
  2589. end
  2590.  
  2591.  
  2592. render["functions"]["public"]["br"] = function(source)
  2593.     if render.variables.link then
  2594.         return "Cannot insert new line within a link on line " .. source[1]
  2595.     end
  2596.  
  2597.     render.variables.scroll = render.variables.scroll + 1
  2598.     render.variables.maxScroll = math.max(render.variables.scroll, render.variables.maxScroll)
  2599. end
  2600.  
  2601.  
  2602. render["functions"]["public"]["c "] = function(source)
  2603.     local sColor = source[2]:match("^%w+%s+(.+)$") or ""
  2604.     if colors[sColor] then
  2605.         term.setTextColor(colors[sColor])
  2606.     else
  2607.         return "Invalid color: \"" .. sColor .. "\" on line " .. source[1]
  2608.     end
  2609. end
  2610.  
  2611.  
  2612. render["functions"]["public"]["color "] = render["functions"]["public"]["c "]
  2613.  
  2614.  
  2615. render["functions"]["public"]["bg "] = function(source)
  2616.     local sColor = source[2]:match("^%w+%s+(.+)$") or ""
  2617.     if colors[sColor] then
  2618.         term.setBackgroundColor(colors[sColor])
  2619.     else
  2620.         return "Invalid color: \"" .. sColor .. "\" on line " .. source[1]
  2621.     end
  2622. end
  2623.  
  2624.  
  2625. render["functions"]["public"]["background "] = render["functions"]["public"]["bg "]
  2626.  
  2627.  
  2628. render["functions"]["public"]["newlink "] = function(source)
  2629.     if render.variables.link then
  2630.         return "Cannot nest links on line " .. source[1]
  2631.     end
  2632.  
  2633.     render.variables.link = source[2]:match("^%w+%s+(.+)$") or ""
  2634.     render.variables.linkStart = false
  2635. end
  2636.  
  2637.  
  2638. render["functions"]["public"]["endlink"] = function(source)
  2639.     if not render.variables.link then
  2640.         return "Cannot end a link without a link on line " .. source[1]
  2641.     end
  2642.  
  2643.     local linkEnd = term.getCursorPos()-1
  2644.     table.insert(render.variables.linkData, {render.variables.linkStart,
  2645.         linkEnd, render.variables.scroll, render.variables.link})
  2646.     render.variables.link = false
  2647.     render.variables.linkStart = false
  2648. end
  2649.  
  2650.  
  2651. render["functions"]["public"]["offset "] = function(source)
  2652.     local offset = tonumber((source[2]:match("^%w+%s+(.+)$") or ""))
  2653.     if offset then
  2654.         render.variables.currentOffset = offset
  2655.     else
  2656.         return "Invalid offset value: \"" .. (source[2]:match("^%w+%s+(.+)$") or "") .. "\" on line " .. source[1]
  2657.     end
  2658. end
  2659.  
  2660.  
  2661. render["functions"]["public"]["marker "] = function(source)
  2662.     render.variables.markers[(source[2]:match("^%w+%s+(.+)$") or "")] = render.variables.scroll
  2663. end
  2664.  
  2665.  
  2666. render["functions"]["public"]["goto "] = function(source)
  2667.     local location = source[2]:match("%w+%s+(.+)$")
  2668.     if render.variables.markers[location] then
  2669.         render.variables.scroll = render.variables.markers[location]
  2670.     else
  2671.         return "No such location: \"" .. (source[2]:match("%w+%s+(.+)$") or "") .. "\" on line " .. source[1]
  2672.     end
  2673. end
  2674.  
  2675.  
  2676. render["functions"]["public"]["box "] = function(source)
  2677.     local sColor, align, height, width, offset, url = source[2]:match("^box (%a+) (%a+) (%-?%d+) (%-?%d+) (%-?%d+) ?([^ ]*)")
  2678.     if not sColor then
  2679.         return "Invalid box syntax on line " .. source[1]
  2680.     end
  2681.  
  2682.     local x, y = term.getCursorPos()
  2683.     local startX
  2684.  
  2685.     if align == "center" or align == "centre" then
  2686.         startX = math.ceil((w / 2) - width / 2) + offset
  2687.     elseif align == "left" then
  2688.         startX = 1 + offset
  2689.     elseif align == "right" then
  2690.         startX = (w - width + 1) + offset
  2691.     else
  2692.         return "Invalid align option for box on line " .. source[1]
  2693.     end
  2694.  
  2695.     if not colors[sColor] then
  2696.         return "Invalid color: \"" .. sColor .. "\" for box on line " .. source[1]
  2697.     end
  2698.  
  2699.     term.setBackgroundColor(colors[sColor])
  2700.     for i = 0, height - 1 do
  2701.         term.setCursorPos(startX, render.variables.scroll + i)
  2702.         term.write(string.rep(" ", width))
  2703.         if url:len() > 3 then
  2704.             table.insert(render.variables.linkData, {startX, startX + width - 1, render.variables.scroll + i, url})
  2705.         end
  2706.     end
  2707.  
  2708.     render.variables.maxScroll = math.max(render.variables.scroll + height - 1, render.variables.maxScroll)
  2709.     term.setCursorPos(x, y)
  2710. end
  2711.  
  2712.  
  2713. render["alignations"]["left"] = function(text, length, _, offset)
  2714.     local x, y = term.getCursorPos()
  2715.     if length then
  2716.         term.setCursorPos(1 + offset, render.variables.scroll)
  2717.         term.write(text)
  2718.         return 1 + offset
  2719.     else
  2720.         term.setCursorPos(x, render.variables.scroll)
  2721.         term.write(text)
  2722.         return x
  2723.     end
  2724. end
  2725.  
  2726.  
  2727. render["alignations"]["right"] = function(text, length, width, offset)
  2728.     local x, y = term.getCursorPos()
  2729.     if length then
  2730.         term.setCursorPos((width - length + 1) + offset, render.variables.scroll)
  2731.         term.write(text)
  2732.         return (width - length + 1) + offset
  2733.     else
  2734.         term.setCursorPos(x, render.variables.scroll)
  2735.         term.write(text)
  2736.         return x
  2737.     end
  2738. end
  2739.  
  2740.  
  2741. render["alignations"]["center"] = function(text, length, _, offset, center)
  2742.     local x, y = term.getCursorPos()
  2743.     if length then
  2744.         term.setCursorPos(math.ceil(center - length / 2) + offset, render.variables.scroll)
  2745.         term.write(text)
  2746.         return math.ceil(center - length / 2) + offset
  2747.     else
  2748.         term.setCursorPos(x, render.variables.scroll)
  2749.         term.write(text)
  2750.         return x
  2751.     end
  2752. end
  2753.  
  2754.  
  2755. render["render"] = function(data, startScroll)
  2756.     if startScroll == nil then
  2757.         render.variables.startScroll = 0
  2758.     else
  2759.         render.variables.startScroll = startScroll
  2760.     end
  2761.  
  2762.     render.variables.scroll = startScroll + 1
  2763.     render.variables.maxScroll = render.variables.scroll
  2764.  
  2765.     render.variables.linkData = {}
  2766.  
  2767.     render.variables.align = render.alignations.left
  2768.  
  2769.     render.variables.blockLength = 0
  2770.     render.variables.link = false
  2771.     render.variables.linkStart = false
  2772.     render.variables.markers = {}
  2773.     render.variables.currentOffset = 0
  2774.  
  2775.     for k, v in pairs(data) do
  2776.         if type(v[2]) ~= "string" then
  2777.             render.functions.displayText(v)
  2778.         elseif v[2] == "<" or v[2] == "left" then
  2779.             render.variables.align = render.alignations.left
  2780.         elseif v[2] == ">" or v[2] == "right" then
  2781.             render.variables.align = render.alignations.right
  2782.         elseif v[2] == "=" or v[2] == "center" then
  2783.             render.variables.align = render.alignations.center
  2784.         else
  2785.             local existentFunction = false
  2786.  
  2787.             for name, func in pairs(render.functions.public) do
  2788.                 if v[2]:find(name) == 1 then
  2789.                     existentFunction = true
  2790.                     local ret = func(v)
  2791.                     if ret then
  2792.                         return ret
  2793.                     end
  2794.                 end
  2795.             end
  2796.  
  2797.             if not existentFunction then
  2798.                 return "Non-existent tag: \"" .. v[2] .. "\" on line " .. v[1]
  2799.             end
  2800.         end
  2801.     end
  2802.  
  2803.     return render.variables.linkData, render.variables.maxScroll - render.variables.startScroll
  2804. end
  2805.  
  2806.  
  2807.  
  2808. --    Lua Execution
  2809.  
  2810.  
  2811. languages["lua"] = {}
  2812. languages["fwml"] = {}
  2813.  
  2814.  
  2815. languages["lua"]["runWithErrorCatching"] = function(func, ...)
  2816.     local _, err = pcall(func, ...)
  2817.     if err then
  2818.         os.queueEvent(websiteErrorEvent, err)
  2819.     end
  2820. end
  2821.  
  2822.  
  2823. languages["lua"]["runWithoutAntivirus"] = function(func, ...)
  2824.     local args = {...}
  2825.     local env = getWebsiteEnvironment(false)
  2826.     setfenv(func, env)
  2827.     return function()
  2828.         languages["lua"]["runWithErrorCatching"](func, unpack(args))
  2829.     end
  2830. end
  2831.  
  2832.  
  2833. languages["lua"]["run"] = function(contents, page, ...)
  2834.     local func, err = loadstring(contents, page)
  2835.     if err then
  2836.         return languages["lua"]["runWithoutAntivirus"](builtInSites["crash"], err)
  2837.     else
  2838.         local args = {...}
  2839.         local env = getWebsiteEnvironment(true)
  2840.         setfenv(func, env)
  2841.         return function()
  2842.             languages["lua"]["runWithErrorCatching"](func, unpack(args))
  2843.         end
  2844.     end
  2845. end
  2846.  
  2847.  
  2848. languages["fwml"]["run"] = function(contents, page, ...)
  2849.     local err, data = pcall(parse, contents)
  2850.     if not err then
  2851.         return languages["lua"]["runWithoutAntivirus"](builtInSites["crash"], data)
  2852.     end
  2853.  
  2854.     return function()
  2855.         local currentScroll = 0
  2856.         local err, links, pageHeight = pcall(render.render, data, currentScroll)
  2857.         if type(links) == "string" or not err then
  2858.             term.clear()
  2859.             os.queueEvent(websiteErrorEvent, links)
  2860.         else
  2861.             while true do
  2862.                 local e, scroll, x, y = os.pullEvent()
  2863.                 if e == "mouse_click" then
  2864.                     for k, v in pairs(links) do
  2865.                         if x >= math.min(v[1], v[2]) and x <= math.max(v[1], v[2]) and y == v[3] then
  2866.                             os.queueEvent(redirectEvent, v[4])
  2867.                             coroutine.yield()
  2868.                         end
  2869.                     end
  2870.                 elseif e == "mouse_scroll" then
  2871.                     if currentScroll - scroll - h >= -pageHeight and currentScroll - scroll <= 0 then
  2872.                         currentScroll = currentScroll - scroll
  2873.                         clear(theme.background, theme.text)
  2874.                         links = render.render(data, currentScroll)
  2875.                     end
  2876.                 elseif e == "key" and (scroll == keys.up or scroll == keys.down) then
  2877.                     local scrollAmount
  2878.  
  2879.                     if scroll == keys.up then
  2880.                         scrollAmount = 1
  2881.                     elseif scroll == keys.down then
  2882.                         scrollAmount = -1
  2883.                     end
  2884.  
  2885.                     if currentScroll + scrollAmount - h >= -pageHeight and currentScroll + scrollAmount <= 0 then
  2886.                         currentScroll = currentScroll + scrollAmount
  2887.                         clear(theme.background, theme.text)
  2888.                         links = render.render(data, currentScroll)
  2889.                     end
  2890.                 end
  2891.             end
  2892.         end
  2893.     end
  2894. end
  2895.  
  2896.  
  2897.  
  2898. --    Query Bar
  2899.  
  2900.  
  2901. local readNewWebsiteURL = function()
  2902.     local onEvent = function(text, event, key, x, y)
  2903.         if event == "mouse_click" then
  2904.             if y == 2 then
  2905.                 local index = determineClickedTab(x, y)
  2906.                 if index == "new" and #tabs < maxTabs then
  2907.                     loadTab(#tabs + 1, "firewolf")
  2908.                 elseif index == "close" then
  2909.                     closeCurrentTab()
  2910.                 elseif index then
  2911.                     switchTab(index)
  2912.                 end
  2913.  
  2914.                 return {["nullifyText"] = true, ["exit"] = true}
  2915.             elseif y > 2 then
  2916.                 return {["nullifyText"] = true, ["exit"] = true}
  2917.             end
  2918.         elseif event == "key" then
  2919.             if key == 29 or key == 157 then
  2920.                 return {["nullifyText"] = true, ["exit"] = true}
  2921.             end
  2922.         end
  2923.     end
  2924.  
  2925.     isMenubarOpen = true
  2926.     drawMenubar()
  2927.     term.setCursorPos(2, 1)
  2928.     term.setTextColor(theme.text)
  2929.     term.setBackgroundColor(theme.accent)
  2930.     term.clearLine()
  2931.     term.write(currentProtocol .. "://")
  2932.  
  2933.     local website = modifiedRead({
  2934.         ["onEvent"] = onEvent,
  2935.         ["displayLength"] = w - 9,
  2936.         ["history"] = history,
  2937.     })
  2938.  
  2939.     if not website then
  2940.         if not tabs[currentTab].isMenubarPermanent then
  2941.             isMenubarOpen = false
  2942.             menubarWindow.setVisible(false)
  2943.         else
  2944.             isMenubarOpen = true
  2945.             menubarWindow.setVisible(true)
  2946.         end
  2947.  
  2948.         term.redirect(tabs[currentTab].win)
  2949.         tabs[currentTab].win.setVisible(true)
  2950.         tabs[currentTab].win.redraw()
  2951.  
  2952.         return
  2953.     elseif website == "exit" then
  2954.         error()
  2955.     end
  2956.  
  2957.     loadTab(currentTab, website)
  2958. end
  2959.  
  2960.  
  2961.  
  2962. --    Event Management
  2963.  
  2964.  
  2965. local handleKeyDown = function(event)
  2966.     if event[2] == 29 or event[2] == 157 then
  2967.         readNewWebsiteURL()
  2968.         return true
  2969.     end
  2970.  
  2971.     return false
  2972. end
  2973.  
  2974.  
  2975. local handleMouseDown = function(event)
  2976.     if isMenubarOpen then
  2977.         if event[4] == 1 then
  2978.             readNewWebsiteURL()
  2979.             return true
  2980.         elseif event[4] == 2 then
  2981.             local index = determineClickedTab(event[3], event[4])
  2982.             if index == "new" and #tabs < maxTabs then
  2983.                 loadTab(#tabs + 1, "firewolf")
  2984.             elseif index == "close" then
  2985.                 closeCurrentTab()
  2986.             elseif index then
  2987.                 switchTab(index)
  2988.             end
  2989.  
  2990.             return true
  2991.         end
  2992.     end
  2993.  
  2994.     return false
  2995. end
  2996.  
  2997.  
  2998. local handleEvents = function()
  2999.     loadTab(1, "firewolf")
  3000.     currentTab = 1
  3001.  
  3002.     while true do
  3003.         drawMenubar()
  3004.         local event = {os.pullEventRaw()}
  3005.         drawMenubar()
  3006.  
  3007.         local cancelEvent = false
  3008.         if event[1] == "terminate" then
  3009.             break
  3010.         elseif event[1] == "key" then
  3011.             cancelEvent = handleKeyDown(event)
  3012.         elseif event[1] == "mouse_click" then
  3013.             cancelEvent = handleMouseDown(event)
  3014.         elseif event[1] == websiteErrorEvent then
  3015.             cancelEvent = true
  3016.  
  3017.             loadTab(currentTab, tabs[currentTab].url, function()
  3018.                 builtInSites["crash"](event[2])
  3019.             end)
  3020.         elseif event[1] == redirectEvent then
  3021.             cancelEvent = true
  3022.  
  3023.             if (event[2]:match("^rdnt://(.+)$")) then
  3024.                 event[2] = event[2]:match("^rdnt://(.+)$")
  3025.             end
  3026.  
  3027.             loadTab(currentTab, event[2])
  3028.         end
  3029.  
  3030.         if not cancelEvent then
  3031.             term.redirect(tabs[currentTab].win)
  3032.             term.setCursorPos(tabs[currentTab].ox, tabs[currentTab].oy)
  3033.  
  3034.             coroutine.resume(tabs[currentTab].thread, unpack(event))
  3035.  
  3036.             local ox, oy = term.getCursorPos()
  3037.             tabs[currentTab].ox = ox
  3038.             tabs[currentTab].oy = oy
  3039.         end
  3040.     end
  3041. end
  3042.  
  3043.  
  3044.  
  3045. --    Main
  3046.  
  3047.  
  3048. local main = function()
  3049.     currentProtocol = "rdnt"
  3050.     currentTab = 1
  3051.  
  3052.     if term.isColor() then
  3053.         theme = colorTheme
  3054.         enableTabBar = true
  3055.     else
  3056.         theme = grayscaleTheme
  3057.         enableTabBar = false
  3058.     end
  3059.  
  3060.     setupMenubar()
  3061.     protocols[currentProtocol]["setup"]()
  3062.  
  3063.     clear(theme.background, theme.text)
  3064.     handleEvents()
  3065. end
  3066.  
  3067.  
  3068. local handleError = function(err)
  3069.     clear(theme.background, theme.text)
  3070.  
  3071.     fill(1, 3, w, 3, theme.subtle)
  3072.     term.setCursorPos(1, 4)
  3073.     center("Firewolf has crashed!")
  3074.  
  3075.     term.setBackgroundColor(theme.background)
  3076.     term.setCursorPos(1, 8)
  3077.     centerSplit(err, w - 4)
  3078.     print("\n")
  3079.     center("Please report this error to")
  3080.     center("GravityScore or 1lann.")
  3081.     print("")
  3082.     center("Press any key to exit.")
  3083.  
  3084.     os.pullEvent("key")
  3085.     os.queueEvent("")
  3086.     os.pullEvent()
  3087. end
  3088.  
  3089.  
  3090. local originalTerminal = term.current()
  3091. local _, err = pcall(main)
  3092. term.redirect(originalTerminal)
  3093.  
  3094. protocols.rdnt.modem("closeAll")
  3095.  
  3096. if err and not err:lower():find("terminate") then
  3097.     handleError(err)
  3098. end
  3099.  
  3100.  
  3101. clear(colors.black, colors.white)
  3102. center("Thanks for using Firewolf " .. version)
  3103. center("Made by GravityScore and 1lann")
  3104. print("")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement