Advertisement
1lann

firewolf3:1.58 server beta

Apr 13th, 2014
239
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 51.70 KB | None | 0 0
  1. --
  2. --  Firewolf Server
  3. --  Made by GravityScore and 1lann
  4. --
  5.  
  6. -- 1.58 Wrapper
  7.  
  8. -- Rednet
  9.  
  10. local rednet = {}
  11.  
  12. rednet.CHANNEL_BROADCAST = 65535
  13. rednet.CHANNEL_REPEAT = 65533
  14.  
  15. local tReceivedMessages = {}
  16. local tReceivedMessageTimeouts = {}
  17. local tHostnames = {}
  18.  
  19. function rednet.open( sModem )
  20.         if type( sModem ) ~= "string" then
  21.                 error( "expected string", 2 )
  22.         end
  23.         if peripheral.getType( sModem ) ~= "modem" then
  24.                 error( "No such modem: "..sModem, 2 )
  25.         end
  26.         peripheral.call( sModem, "open", os.getComputerID() )
  27.         peripheral.call( sModem, "open", rednet.CHANNEL_BROADCAST )
  28. end
  29.  
  30. function rednet.close( sModem )
  31.     if sModem then
  32.         -- Close a specific modem
  33.         if type( sModem ) ~= "string" then
  34.             error( "expected string", 2 )
  35.         end
  36.         if peripheral.getType( sModem ) ~= "modem" then
  37.             error( "No such modem: "..sModem, 2 )
  38.         end
  39.         peripheral.call( sModem, "close", os.getComputerID() )
  40.         peripheral.call( sModem, "close", rednet.CHANNEL_BROADCAST )
  41.     else
  42.         -- Close all modems
  43.         for n,sModem in ipairs( peripheral.getNames() ) do
  44.             if rednet.isOpen( sModem ) then
  45.                 rednet.close( sModem )
  46.             end
  47.         end
  48.     end
  49. end
  50.  
  51. function rednet.isOpen( sModem )
  52.     if sModem then
  53.         -- Check if a specific modem is open
  54.         if type( sModem ) ~= "string" then
  55.             error( "expected string", 2 )
  56.         end
  57.         if peripheral.getType( sModem ) == "modem" then
  58.             return peripheral.call( sModem, "isOpen", os.getComputerID() ) and peripheral.call( sModem, "isOpen", rednet.CHANNEL_BROADCAST )
  59.         end
  60.     else
  61.         -- Check if any modem is open
  62.         for n,sModem in ipairs( peripheral.getNames() ) do
  63.             if rednet.isOpen( sModem ) then
  64.                 return true
  65.             end
  66.         end
  67.     end
  68.         return false
  69. end
  70.  
  71. function rednet.send( nRecipient, message, sProtocol )
  72.     -- Generate a (probably) unique message ID
  73.     -- We could do other things to guarantee uniqueness, but we really don't need to
  74.     -- Store it to ensure we don't get our own messages back
  75.     local nMessageID = math.random( 1, 2147483647 )
  76.     tReceivedMessages[ nMessageID ] = true
  77.     tReceivedMessageTimeouts[ os.startTimer( 30 ) ] = nMessageID
  78.  
  79.     -- Create the message
  80.     local nReplyChannel = os.getComputerID()
  81.     local tMessage = {
  82.         nMessageID = nMessageID,
  83.         nRecipient = nRecipient,
  84.         message = message,
  85.         sProtocol = sProtocol,
  86.     }
  87.  
  88.     if nRecipient == os.getComputerID() then
  89.         -- Loopback to ourselves
  90.         os.queueEvent( "rednet_message", nReplyChannel, message, sProtocol )
  91.  
  92.     else
  93.         -- Send on all open modems, to the target and to repeaters
  94.         local sent = false
  95.         for n,sModem in ipairs( peripheral.getNames() ) do
  96.             if rednet.isOpen( sModem ) then
  97.                 peripheral.call( sModem, "transmit", nRecipient, nReplyChannel, tMessage );
  98.                 peripheral.call( sModem, "transmit", rednet.CHANNEL_REPEAT, nReplyChannel, tMessage );
  99.                 sent = true
  100.             end
  101.         end
  102.     end
  103. end
  104.  
  105. function rednet.broadcast( message, sProtocol )
  106.         rednet.send( rednet.CHANNEL_BROADCAST, message, sProtocol )
  107. end
  108.  
  109. function rednet.receive( sProtocolFilter, nTimeout )
  110.     -- The parameters used to be ( nTimeout ), detect this case for backwards compatibility
  111.     if type(sProtocolFilter) == "number" and nTimeout == nil then
  112.         sProtocolFilter, nTimeout = nil, sProtocolFilter
  113.     end
  114.  
  115.     -- Start the timer
  116.         local timer = nil
  117.         local sFilter = nil
  118.         if nTimeout then
  119.                 timer = os.startTimer( nTimeout )
  120.                 sFilter = nil
  121.         else
  122.                 sFilter = "rednet_message"
  123.         end
  124.  
  125.         -- Wait for events
  126.         while true do
  127.                 local sEvent, p1, p2, p3 = os.pullEvent( sFilter )
  128.                 if sEvent == "rednet_message" then
  129.                     -- Return the first matching rednet_message
  130.                         local nSenderID, message, sProtocol = p1, p2, p3
  131.                         if sProtocolFilter == nil or sProtocol == sProtocolFilter then
  132.                         return nSenderID, message, sProtocol
  133.             end
  134.                 elseif sEvent == "timer" then
  135.                     -- Return nil if we timeout
  136.                     if p1 == timer then
  137.                         return nil
  138.                 end
  139.                 end
  140.         end
  141. end
  142.  
  143. function rednet.host( sProtocol, sHostname )
  144.     if type( sProtocol ) ~= "string" or type( sHostname ) ~= "string" then
  145.         error( "expected string, string", 2 )
  146.     end
  147.     if sHostname == "localhost" then
  148.         error( "Reserved hostname", 2 )
  149.     end
  150.     if tHostnames[ sProtocol ] ~= sHostname then
  151.         if rednet.lookup( sProtocol, sHostname ) ~= nil then
  152.             error( "Hostname in use", 2 )
  153.         end
  154.         tHostnames[ sProtocol ] = sHostname
  155.     end
  156. end
  157.  
  158. function rednet.unhost( sProtocol )
  159.     if type( sProtocol ) ~= "string" then
  160.         error( "expected string", 2 )
  161.     end
  162.     tHostnames[ sProtocol ] = nil
  163. end
  164.  
  165. function rednet.lookup( sProtocol, sHostname )
  166.     if type( sProtocol ) ~= "string" then
  167.         error( "expected string", 2 )
  168.     end
  169.  
  170.     -- Build list of host IDs
  171.     local tResults = nil
  172.     if sHostname == nil then
  173.         tResults = {}
  174.     end
  175.  
  176.     -- Check localhost first
  177.     if tHostnames[ sProtocol ] then
  178.         if sHostname == nil then
  179.             table.insert( tResults, os.getComputerID() )
  180.         elseif sHostname == "localhost" or sHostname == tHostnames[ sProtocol ] then
  181.             return os.getComputerID()
  182.         end
  183.     end
  184.  
  185.     if not rednet.isOpen() then
  186.         if tResults then
  187.             return unpack( tResults )
  188.         end
  189.         return nil
  190.     end
  191.  
  192.     -- Broadcast a lookup packet
  193.     rednet.broadcast( {
  194.         sType = "lookup",
  195.         sProtocol = sProtocol,
  196.         sHostname = sHostname,
  197.     }, "dns" )
  198.  
  199.     -- Start a timer
  200.     local timer = os.startTimer( 2 )
  201.  
  202.     -- Wait for events
  203.     while true do
  204.         local event, p1, p2, p3 = os.pullEvent()
  205.         if event == "rednet_message" then
  206.             -- Got a rednet message, check if it's the response to our request
  207.             local nSenderID, tMessage, sMessageProtocol = p1, p2, p3
  208.             if sMessageProtocol == "dns" and tMessage.sType == "lookup response" then
  209.                 if tMessage.sProtocol == sProtocol then
  210.                     if sHostname == nil then
  211.                         table.insert( tResults, nSenderID )
  212.                     elseif tMessage.sHostname == sHostname then
  213.                         return nSenderID
  214.                     end
  215.                 end
  216.             end
  217.         else
  218.             -- Got a timer event, check it's the end of our timeout
  219.             if p1 == timer then
  220.                 break
  221.             end
  222.         end
  223.     end
  224.     if tResults then
  225.         return unpack( tResults )
  226.     end
  227.     return nil
  228. end
  229.  
  230. local bRunning = false
  231. function rednet.run()
  232.         if bRunning then
  233.                 error( "rednet is already running", 2 )
  234.         end
  235.         bRunning = true
  236.        
  237.         while bRunning do
  238.                 local sEvent, p1, p2, p3, p4 = os.pullEventRaw()
  239.         if sEvent == "modem_message" then
  240.             -- Got a modem message, process it and add it to the rednet event queue
  241.             local sModem, nChannel, nReplyChannel, tMessage = p1, p2, p3, p4
  242.             if rednet.isOpen( sModem ) and ( nChannel == os.getComputerID() or nChannel == rednet.CHANNEL_BROADCAST ) then
  243.                 if type( tMessage ) == "table" and tMessage.nMessageID then
  244.                     if not tReceivedMessages[ tMessage.nMessageID ] then
  245.                         tReceivedMessages[ tMessage.nMessageID ] = true
  246.                         tReceivedMessageTimeouts[ os.startTimer( 30 ) ] = nMessageID
  247.                         os.queueEvent( "rednet_message", nReplyChannel, tMessage.message, tMessage.sProtocol )
  248.                     end
  249.                 end
  250.             end
  251.                 elseif sEvent == "rednet_message" then
  252.                     -- Got a rednet message (queued from above), respond to dns lookup
  253.                     local nSenderID, tMessage, sProtocol = p1, p2, p3
  254.                     if sProtocol == "dns" and tMessage.sType == "lookup" then
  255.                         local sHostname = tHostnames[ tMessage.sProtocol ]
  256.                         if sHostname ~= nil and (tMessage.sHostname == nil or tMessage.sHostname == sHostname) then
  257.                             rednet.send( nSenderID, {
  258.                                 sType = "lookup response",
  259.                                 sHostname = sHostname,
  260.                                 sProtocol = tMessage.sProtocol,
  261.                             }, "dns" )
  262.                         end
  263.                     end
  264.  
  265.                 elseif sEvent == "timer" then
  266.             -- Got a timer event, use it to clear the event queue
  267.             local nTimer = p1
  268.             local nMessage = tReceivedMessageTimeouts[ nTimer ]
  269.             if nMessage then
  270.                 tReceivedMessageTimeouts[ nTimer ] = nil
  271.                 tReceivedMessages[ nMessage ] = nil
  272.             end
  273.                 end
  274.         end
  275. end
  276.  
  277. -- Window Display
  278.  
  279. local native = (term.native)
  280. local redirectTarget = native
  281.  
  282. local function wrap( _sFunction )
  283.         return function( ... )
  284.                 return redirectTarget[ _sFunction ]( ... )
  285.         end
  286. end
  287.  
  288. local term = {}
  289.  
  290. term.redirect = function( target )
  291.         if target == nil or type( target ) ~= "table" then
  292.                 error( "Invalid redirect target", 2 )
  293.         end
  294.     if target == term then
  295.         error( "term is not a recommended redirect target, try term.current() instead", 2 )
  296.     end
  297.         for k,v in pairs( native ) do
  298.                 if type( k ) == "string" and type( v ) == "function" then
  299.                         if type( target[k] ) ~= "function" then
  300.                                 target[k] = function()
  301.                                         error( "Redirect object is missing method "..k..".", 2 )
  302.                                 end
  303.                         end
  304.                 end
  305.         end
  306.         local oldRedirectTarget = redirectTarget
  307.         redirectTarget = target
  308.         return oldRedirectTarget
  309. end
  310.  
  311. term.current = function()
  312.     return redirectTarget
  313. end
  314.  
  315. term.native = function()
  316.     -- NOTE: please don't use this function unless you have to.
  317.     -- If you're running in a redirected or multitasked enviorment, term.native() will NOT be
  318.     -- the current terminal when your program starts up. It is far better to use term.current()
  319.     return native
  320. end
  321.  
  322. for k,v in pairs( native ) do
  323.         if type( k ) == "string" and type( v ) == "function" then
  324.                 if term[k] == nil then
  325.                         term[k] = wrap( k )
  326.                 end
  327.         end
  328. end
  329.        
  330. local env = getfenv()
  331. for k,v in pairs( term ) do
  332.         env[k] = v
  333. end
  334.  
  335.  
  336. local window = {}
  337.  
  338. function window.create( parent, nX, nY, nWidth, nHeight, bStartVisible )
  339.  
  340.     if type( parent ) ~= "table" or
  341.        type( nX ) ~= "number" or
  342.        type( nY ) ~= "number" or
  343.        type( nWidth ) ~= "number" or
  344.        type( nHeight ) ~= "number" or
  345.        (bStartVisible ~= nil and type( bStartVisible ) ~= "boolean") then
  346.         error( "Expected object, number, number, number, number, [boolean]", 2 )
  347.     end
  348.  
  349.     if parent == term then
  350.         error( "term is not a recommended window parent, try term.current() instead", 2 )
  351.     end
  352.  
  353.     -- Setup
  354.     local bVisible = (bStartVisible ~= false)
  355.     local nCursorX = 1
  356.     local nCursorY = 1
  357.     local bCursorBlink = false
  358.     local nTextColor = colors.white
  359.     local nBackgroundColor = colors.black
  360.     local sEmpty = string.rep( " ", nWidth )
  361.     local tLines = {}
  362.     do
  363.         local tEmpty = { { sEmpty, nTextColor, nBackgroundColor } }
  364.         for y=1,nHeight do
  365.             tLines[y] = tEmpty
  366.         end
  367.     end
  368.  
  369.     -- Helper functions
  370.     local function updateCursorPos()
  371.         if nCursorX >= 1 and nCursorY >= 1 and
  372.            nCursorX <= nWidth and nCursorY <= nHeight then
  373.             parent.setCursorPos( nX + nCursorX - 1, nY + nCursorY - 1 )
  374.         else
  375.             parent.setCursorPos( 0, 0 )
  376.         end
  377.     end
  378.    
  379.     local function updateCursorBlink()
  380.         parent.setCursorBlink( bCursorBlink )
  381.     end
  382.    
  383.     local function updateCursorColor()
  384.         parent.setTextColor( nTextColor )
  385.     end
  386.    
  387.     local function redrawLine( n )
  388.         parent.setCursorPos( nX, nY + n - 1 )
  389.         local tLine = tLines[ n ]
  390.         for m=1,#tLine do
  391.             local tBit = tLine[ m ]
  392.             parent.setTextColor( tBit[2] )
  393.             parent.setBackgroundColor( tBit[3] )
  394.             parent.write( tBit[1] )
  395.         end
  396.     end
  397.  
  398.     local function lineLen( tLine )
  399.         local nLength = 0
  400.         for n=1,#tLine do
  401.             nLength = nLength + string.len( tLine[n][1] )
  402.         end
  403.         return nLength
  404.     end
  405.  
  406.     local function lineSub( tLine, nStart, nEnd )
  407.         --assert( math.floor(nStart) == nStart )
  408.         --assert( math.floor(nEnd) == nEnd )
  409.         --assert( nStart >= 1 )
  410.         --assert( nEnd >= nStart )
  411.         --assert( nEnd <= lineLen( tLine ) )
  412.         local tSubLine = {}
  413.         local nBitStart = 1
  414.         for n=1,#tLine do
  415.             local tBit = tLine[n]
  416.             local sBit = tBit[1]
  417.             local nBitEnd = nBitStart + string.len( sBit ) - 1
  418.             if nBitEnd >= nStart and nBitStart <= nEnd then
  419.                 if nBitStart >= nStart and nBitEnd <= nEnd then
  420.                     -- Include bit wholesale
  421.                     table.insert( tSubLine, tBit )
  422.                     --assert( lineLen( tSubLine ) == (math.min(nEnd, nBitEnd) - nStart + 1) )
  423.                 elseif nBitStart < nStart and nBitEnd <= nEnd then
  424.                     -- Include end of bit
  425.                     table.insert( tSubLine, {
  426.                         string.sub( sBit, nStart - nBitStart + 1 ),
  427.                         tBit[2], tBit[3]
  428.                     } )
  429.                     --assert( lineLen( tSubLine ) == (math.min(nEnd, nBitEnd) - nStart + 1) )
  430.                 elseif nBitStart >= nStart and nBitEnd > nEnd then
  431.                     -- Include beginning of bit
  432.                     table.insert( tSubLine, {
  433.                         string.sub( sBit, 1, nEnd - nBitStart + 1 ),
  434.                         tBit[2], tBit[3]
  435.                     } )
  436.                     --assert( lineLen( tSubLine ) == (math.min(nEnd, nBitEnd) - nStart + 1) )
  437.                 else
  438.                     -- Include middle of bit
  439.                     table.insert( tSubLine, {
  440.                         string.sub( sBit, nStart - nBitStart + 1, nEnd - nBitStart + 1 ),
  441.                         tBit[2], tBit[3]
  442.                     } )
  443.                     --assert( lineLen( tSubLine ) == (math.min(nEnd, nBitEnd) - nStart + 1) )
  444.                 end
  445.             end
  446.             nBitStart = nBitEnd + 1
  447.         end
  448.         --assert( lineLen( tSubLine ) == (nEnd - nStart + 1) )
  449.         return tSubLine
  450.     end
  451.  
  452.     local function lineJoin( tLine1, tLine2 )
  453.         local tNewLine = {}
  454.         if tLine1[#tLine1][2] == tLine2[1][2] and
  455.            tLine1[#tLine1][3] == tLine2[1][3] then
  456.             -- Merge middle bits
  457.             for n=1,#tLine1-1 do
  458.                 table.insert( tNewLine, tLine1[n] )
  459.             end
  460.             table.insert( tNewLine, {
  461.                 tLine1[#tLine1][1] .. tLine2[1][1],
  462.                 tLine2[1][2], tLine2[1][3]
  463.             } )
  464.             for n=2,#tLine2 do
  465.                 table.insert( tNewLine, tLine2[n] )
  466.             end
  467.             --assert( lineLen( tNewLine ) == lineLen(tLine1) + lineLen(tLine2) )
  468.         else
  469.             -- Just concatenate
  470.             for n=1,#tLine1 do
  471.                 table.insert( tNewLine, tLine1[n] )
  472.             end
  473.             for n=1,#tLine2 do
  474.                 table.insert( tNewLine, tLine2[n] )
  475.             end
  476.             --assert( lineLen( tNewLine ) == lineLen(tLine1) + lineLen(tLine2) )
  477.         end
  478.         return tNewLine
  479.     end
  480.  
  481.     local function redraw()
  482.         for n=1,nHeight do
  483.             redrawLine( n )
  484.         end
  485.     end
  486.  
  487.     local window = {}
  488.  
  489.     -- Terminal implementation
  490.     function window.write( sText )
  491.         local nLen = string.len( sText )
  492.         local nStart = nCursorX
  493.         local nEnd = nStart + nLen - 1
  494.         if nCursorY >= 1 and nCursorY <= nHeight then
  495.             -- Work out where to put new line
  496.             --assert( math.floor(nStart) == nStart )
  497.             --assert( math.floor(nEnd) == nEnd )
  498.             if nStart <= nWidth and nEnd >= 1 then
  499.                 -- Construct new line
  500.                 local tLine = tLines[ nCursorY ]
  501.                 if nStart == 1 and nEnd == nWidth then
  502.                     -- Exactly replace line
  503.                     tLine = {
  504.                         { sText, nTextColor, nBackgroundColor }
  505.                     }
  506.                     --assert( lineLen( tLine ) == nWidth )
  507.                 elseif nStart <= 1 and nEnd >= nWidth then
  508.                     -- Overwrite line with subset
  509.                     tLine = {
  510.                         { string.sub( sText, 1 - nStart + 1, nWidth - nStart + 1 ), nTextColor, nBackgroundColor }
  511.                     }
  512.                     --assert( lineLen( tLine ) == nWidth )
  513.                 elseif nStart <= 1 then
  514.                     -- Overwrite beginning of line
  515.                     tLine = lineJoin(
  516.                         { { string.sub( sText, 1 - nStart + 1 ), nTextColor, nBackgroundColor } },
  517.                         lineSub( tLine, nEnd + 1, nWidth )
  518.                     )
  519.                     --assert( lineLen( tLine ) == nWidth )
  520.                 elseif nEnd >= nWidth then
  521.                     -- Overwrite end of line
  522.                     tLine = lineJoin(
  523.                         lineSub( tLine, 1, nStart - 1 ),
  524.                         { { string.sub( sText, 1, nWidth - nStart + 1 ), nTextColor, nBackgroundColor } }
  525.                     )
  526.                     --assert( lineLen( tLine ) == nWidth )
  527.                 else
  528.                     -- Overwrite middle of line
  529.                     tLine = lineJoin(
  530.                         lineJoin(
  531.                             lineSub( tLine, 1, nStart - 1 ),
  532.                             { { sText, nTextColor, nBackgroundColor } }
  533.                         ),
  534.                         lineSub( tLine, nEnd + 1, nWidth )
  535.                     )
  536.                     --assert( lineLen( tLine ) == nWidth )
  537.                 end
  538.  
  539.                 -- Store and redraw new line
  540.                 tLines[ nCursorY ] = tLine
  541.                 if bVisible then
  542.                     redrawLine( nCursorY )
  543.                 end
  544.             end
  545.         end
  546.  
  547.         -- Move and redraw cursor
  548.         nCursorX = nEnd + 1
  549.         if bVisible then
  550.             updateCursorColor()
  551.             updateCursorPos()
  552.         end
  553.     end
  554.  
  555.     function window.clear()
  556.         local tEmpty = { { sEmpty, nTextColor, nBackgroundColor } }
  557.         for y=1,nHeight do
  558.             tLines[y] = tEmpty
  559.         end
  560.         if bVisible then
  561.             redraw()
  562.             updateCursorColor()
  563.             updateCursorPos()
  564.         end
  565.     end
  566.  
  567.     function window.clearLine()
  568.         if nCursorY >= 1 and nCursorY <= nHeight then
  569.             tLines[ nCursorY ] = { { sEmpty, nTextColor, nBackgroundColor } }
  570.             if bVisible then
  571.                 redrawLine( nCursorY )
  572.                 updateCursorColor()
  573.                 updateCursorPos()
  574.             end
  575.         end
  576.     end
  577.  
  578.     function window.getCursorPos()
  579.         return nCursorX, nCursorY
  580.     end
  581.  
  582.     function window.setCursorPos( x, y )
  583.         nCursorX = math.floor( x )
  584.         nCursorY = math.floor( y )
  585.         if bVisible then
  586.             updateCursorPos()
  587.         end
  588.     end
  589.  
  590.     function window.setCursorBlink( blink )
  591.         bCursorBlink = blink
  592.         if bVisible then
  593.             updateCursorBlink()
  594.         end
  595.     end
  596.  
  597.     function window.isColor()
  598.         return parent.isColor()
  599.     end
  600.  
  601.     function window.isColour()
  602.         return parent.isColor()
  603.     end
  604.  
  605.     local function setTextColor( color )
  606.         if not parent.isColor() then
  607.             if color ~= colors.white and color ~= colors.black then
  608.                 error( "Colour not supported", 3 )
  609.             end
  610.         end
  611.         nTextColor = color
  612.         if bVisible then
  613.             updateCursorColor()
  614.         end
  615.     end
  616.  
  617.     function window.setTextColor( color )
  618.         setTextColor( color )
  619.     end
  620.  
  621.     function window.setTextColour( color )
  622.         setTextColor( color )
  623.     end
  624.  
  625.     local function setBackgroundColor( color )
  626.         if not parent.isColor() then
  627.             if color ~= colors.white and color ~= colors.black then
  628.                 error( "Colour not supported", 3 )
  629.             end
  630.         end
  631.         nBackgroundColor = color
  632.     end
  633.  
  634.     function window.setBackgroundColor( color )
  635.         setBackgroundColor( color )
  636.     end
  637.  
  638.     function window.setBackgroundColour( color )
  639.         setBackgroundColor( color )
  640.     end
  641.  
  642.     function window.getSize()
  643.         return nWidth, nHeight
  644.     end
  645.  
  646.     function window.scroll( n )
  647.         if n ~= 0 then
  648.             local tNewLines = {}
  649.             local tEmpty = { { sEmpty, nTextColor, nBackgroundColor } }
  650.             for newY=1,nHeight do
  651.                 local y = newY + n
  652.                 if y >= 1 and y <= nHeight then
  653.                     tNewLines[newY] = tLines[y]
  654.                 else
  655.                     tNewLines[newY] = tEmpty
  656.                 end
  657.             end
  658.             tLines = tNewLines
  659.             if bVisible then
  660.                 redraw()
  661.                 updateCursorColor()
  662.                 updateCursorPos()
  663.             end
  664.         end
  665.     end
  666.  
  667.     -- Other functions
  668.     function window.setVisible( bVis )
  669.         if bVisible ~= bVis then
  670.             bVisible = bVis
  671.             if bVisible then
  672.                 window.redraw()
  673.             end
  674.         end
  675.     end
  676.  
  677.     function window.redraw()
  678.         if bVisible then
  679.             redraw()
  680.             updateCursorBlink()
  681.             updateCursorColor()
  682.             updateCursorPos()
  683.         end
  684.     end
  685.  
  686.     function window.restoreCursor()
  687.         if bVisible then
  688.             updateCursorBlink()
  689.             updateCursorColor()
  690.             updateCursorPos()
  691.         end
  692.     end
  693.  
  694.     function window.getPosition()
  695.         return nX, nY
  696.     end
  697.  
  698.     function window.reposition( nNewX, nNewY, nNewWidth, nNewHeight )
  699.         nX = nNewX
  700.         nY = nNewY
  701.         if nNewWidth and nNewHeight then
  702.             sEmpty = string.rep( " ", nNewWidth )
  703.             local tNewLines = {}
  704.             local tEmpty = { { sEmpty, nTextColor, nBackgroundColor } }
  705.             for y=1,nNewHeight do
  706.                 if y > nHeight then
  707.                     tNewLines[y] = tEmpty
  708.                 else
  709.                     if nNewWidth == nWidth then
  710.                         tNewLines[y] = tLines[y]
  711.                     elseif nNewWidth < nWidth then
  712.                         tNewLines[y] = lineSub( tLines[y], 1, nNewWidth )
  713.                     else
  714.                         tNewLines[y] = lineJoin( tLines[y], { { string.sub( sEmpty, nWidth + 1, nNewWidth ), nTextColor, nBackgroundColor } } )
  715.                     end
  716.                 end
  717.             end
  718.             nWidth = nNewWidth
  719.             nHeight = nNewHeight
  720.             tLines = tNewLines
  721.         end
  722.         if bVisible then
  723.             window.redraw()
  724.         end
  725.     end
  726.  
  727.     if bVisible then
  728.         window.redraw()
  729.     end
  730.     return window
  731. end
  732.  
  733.  
  734.  
  735. --    Variables
  736.  
  737.  
  738. local version = "3.0"
  739. local build = 0
  740. local args = {...}
  741.  
  742. local w, h = term.getSize()
  743.  
  744. local serversFolder = "/fw_servers"
  745. local indexFileName = "index"
  746.  
  747. local sides = {}
  748.  
  749. local menubarWindow = nil
  750. local updateMenubarEvent = "firewolfServer_updateMenubarEvent"
  751. local triggerErrorEvent = "firewolfServer_triggerErrorEvent"
  752. local tabSwitchEvent = "firewolfServer_tabSwitchEvent"
  753.  
  754. local maxTabs = 3
  755. local maxTabNameWidth = 14
  756. local currentTab = 1
  757. local tabs = {}
  758.  
  759. local publicDnsChannel = 9999
  760. local publicRespChannel = 9998
  761. local responseID = 41738
  762.  
  763. local DNSRequestTag = "--@!FIREWOLF-LIST!@--"
  764. local DNSResponseTag = "--@!FIREWOLF-DNSRESP!@--"
  765. local connectTag = "--@!FIREWOLF-CONNECT!@--"
  766. local disconnectTag = "--@!FIREWOLF-DISCONNECT!@--"
  767. local receiveTag = "--@!FIREWOLF-RECEIVE!@--"
  768. local headTag = "--@!FIREWOLF-HEAD!@--"
  769. local bodyTag = "--@!FIREWOLF-BODY!@--"
  770. local initiateTag = "--@!FIREWOLF-INITIATE!@--"
  771. local protocolTag = "--@!FIREWOLF-REDNET-PROTOCOL!@--"
  772.  
  773. local initiatePattern = "^%-%-@!FIREWOLF%-INITIATE!@%-%-(.+)"
  774. local retrievePattern = "^%-%-@!FIREWOLF%-FETCH!@%-%-(.+)"
  775.  
  776.  
  777. local theme = {}
  778.  
  779. local colorTheme = {
  780.     background = colors.gray,
  781.     accent = colors.red,
  782.     subtle = colors.orange,
  783.  
  784.     lightText = colors.gray,
  785.     text = colors.white,
  786.     errorText = colors.red,
  787.  
  788.     yellow = colors.yellow,
  789. }
  790.  
  791. local grayscaleTheme = {
  792.     background = colors.black,
  793.     accent = colors.black,
  794.     subtle = colors.black,
  795.  
  796.     lightText = colors.white,
  797.     text = colors.white,
  798.     errorText = colors.white,
  799.  
  800.     yellow = colors.white
  801. }
  802.  
  803.  
  804.  
  805. --    Default Pages
  806.  
  807.  
  808. local defaultPages = {}
  809.  
  810.  
  811. defaultPages["404"] = [[
  812. local function center(text)
  813.     local w, h = term.getSize()
  814.     local x, y = term.getCursorPos()
  815.     term.setCursorPos(math.floor(w / 2 - text:len() / 2) + (text:len() % 2 == 0 and 1 or 0), y)
  816.     term.write(text)
  817.     term.setCursorPos(1, y + 1)
  818. end
  819.  
  820. term.setTextColor(colors.white)
  821. term.setBackgroundColor(colors.gray)
  822. term.clear()
  823.  
  824. term.setCursorPos(1, 4)
  825. center("Error 404")
  826. print("\n")
  827. center("The page could not be found.")
  828. ]]
  829.  
  830.  
  831. defaultPages["index"] = [[
  832. term.setCursorPos(3, 5)
  833. print("Welcome to ${DOMAIN}")
  834. ]]
  835.  
  836.  
  837.  
  838. --    Modified Read
  839.  
  840.  
  841. local modifiedRead = function(properties)
  842.     local text = ""
  843.     local startX, startY = term.getCursorPos()
  844.     local pos = 0
  845.  
  846.     local previousText = ""
  847.     local readHistory = nil
  848.     local historyPos = 0
  849.  
  850.     if not properties then
  851.         properties = {}
  852.     end
  853.  
  854.     if properties.displayLength then
  855.         properties.displayLength = math.min(properties.displayLength, w - 2)
  856.     else
  857.         properties.displayLength = w - startX - 1
  858.     end
  859.  
  860.     if properties.startingText then
  861.         text = properties.startingText
  862.         pos = text:len()
  863.     end
  864.  
  865.     if properties.history then
  866.         readHistory = {}
  867.         for k, v in pairs(properties.history) do
  868.             readHistory[k] = v
  869.         end
  870.     end
  871.  
  872.     if readHistory and readHistory[1] == text then
  873.         table.remove(readHistory, 1)
  874.     end
  875.  
  876.     local draw = function(replaceCharacter)
  877.         local scroll = 0
  878.         if properties.displayLength and pos > properties.displayLength then
  879.             scroll = pos - properties.displayLength
  880.         end
  881.  
  882.         local repl = replaceCharacter or properties.replaceCharacter
  883.         term.setTextColor(theme.text)
  884.         term.setCursorPos(startX, startY)
  885.         if repl then
  886.             term.write(string.rep(repl:sub(1, 1), text:len() - scroll))
  887.         else
  888.             term.write(text:sub(scroll + 1))
  889.         end
  890.  
  891.         term.setCursorPos(startX + pos - scroll, startY)
  892.     end
  893.  
  894.     term.setCursorBlink(true)
  895.     draw()
  896.     while true do
  897.         local event, key, x, y, param4, param5 = os.pullEvent()
  898.  
  899.         if properties.onEvent then
  900.             -- Actions:
  901.             -- - exit (bool)
  902.             -- - text
  903.             -- - nullifyText
  904.  
  905.             term.setCursorBlink(false)
  906.             local action = properties.onEvent(text, event, key, x, y, param4, param5)
  907.             if action then
  908.                 if action.text then
  909.                     draw(" ")
  910.                     text = action.text
  911.                     pos = text:len()
  912.                 end if action.nullifyText then
  913.                     text = nil
  914.                     action.exit = true
  915.                 end if action.exit then
  916.                     break
  917.                 end
  918.             end
  919.             draw()
  920.         end
  921.  
  922.         term.setCursorBlink(true)
  923.         if event == "char" then
  924.             local canType = true
  925.             if properties.maxLength and text:len() >= properties.maxLength then
  926.                 canType = false
  927.             end
  928.  
  929.             if canType then
  930.                 text = text:sub(1, pos) .. key .. text:sub(pos + 1, -1)
  931.                 pos = pos + 1
  932.                 draw()
  933.             end
  934.         elseif event == "key" then
  935.             if key == keys.enter then
  936.                 break
  937.             elseif key == keys.left and pos > 0 then
  938.                 pos = pos - 1
  939.                 draw()
  940.             elseif key == keys.right and pos < text:len() then
  941.                 pos = pos + 1
  942.                 draw()
  943.             elseif key == keys.backspace and pos > 0 then
  944.                 draw(" ")
  945.                 text = text:sub(1, pos - 1) .. text:sub(pos + 1, -1)
  946.                 pos = pos - 1
  947.                 draw()
  948.             elseif key == keys.delete and pos < text:len() then
  949.                 draw(" ")
  950.                 text = text:sub(1, pos) .. text:sub(pos + 2, -1)
  951.                 draw()
  952.             elseif key == keys.home then
  953.                 pos = 0
  954.                 draw()
  955.             elseif key == keys["end"] then
  956.                 pos = text:len()
  957.                 draw()
  958.             elseif (key == keys.up or key == keys.down) and readHistory then
  959.                 local shouldDraw = false
  960.                 if historyPos == 0 then
  961.                     previousText = text
  962.                 elseif historyPos > 0 then
  963.                     readHistory[historyPos] = text
  964.                 end
  965.  
  966.                 if key == keys.up then
  967.                     if historyPos < #readHistory then
  968.                         historyPos = historyPos + 1
  969.                         shouldDraw = true
  970.                     end
  971.                 else
  972.                     if historyPos > 0 then
  973.                         historyPos = historyPos - 1
  974.                         shouldDraw = true
  975.                     end
  976.                 end
  977.  
  978.                 if shouldDraw then
  979.                     draw(" ")
  980.                     if historyPos > 0 then
  981.                         text = readHistory[historyPos]
  982.                     else
  983.                         text = previousText
  984.                     end
  985.                     pos = text:len()
  986.                     draw()
  987.                 end
  988.             end
  989.         elseif event == "mouse_click" then
  990.             local scroll = 0
  991.             if properties.displayLength and pos > properties.displayLength then
  992.                 scroll = pos - properties.displayLength
  993.             end
  994.  
  995.             if y == startY and x >= startX and x <= math.min(startX + text:len(), startX + (properties.displayLength or 10000)) then
  996.                 pos = x - startX + scroll
  997.                 draw()
  998.             elseif y == startY then
  999.                 if x < startX then
  1000.                     pos = scroll
  1001.                     draw()
  1002.                 elseif x > math.min(startX + text:len(), startX + (properties.displayLength or 10000)) then
  1003.                     pos = text:len()
  1004.                     draw()
  1005.                 end
  1006.             end
  1007.         end
  1008.     end
  1009.  
  1010.     term.setCursorBlink(false)
  1011.     print("")
  1012.     return text
  1013. end
  1014.  
  1015.  
  1016.  
  1017. --    RC4
  1018. --    Implementation by AgentE382
  1019.  
  1020.  
  1021. local cryptWrapper = function(plaintext, salt)
  1022.     local key = type(salt) == "table" and {unpack(salt)} or {string.byte(salt, 1, #salt)}
  1023.     local S = {}
  1024.     for i = 0, 255 do
  1025.         S[i] = i
  1026.     end
  1027.  
  1028.     local j, keylength = 0, #key
  1029.     for i = 0, 255 do
  1030.         j = (j + S[i] + key[i % keylength + 1]) % 256
  1031.         S[i], S[j] = S[j], S[i]
  1032.     end
  1033.  
  1034.     local i = 0
  1035.     j = 0
  1036.     local chars, astable = type(plaintext) == "table" and {unpack(plaintext)} or {string.byte(plaintext, 1, #plaintext)}, false
  1037.  
  1038.     for n = 1, #chars do
  1039.         i = (i + 1) % 256
  1040.         j = (j + S[i]) % 256
  1041.         S[i], S[j] = S[j], S[i]
  1042.         chars[n] = bit.bxor(S[(S[i] + S[j]) % 256], chars[n])
  1043.         if chars[n] > 127 or chars[n] == 13 then
  1044.             astable = true
  1045.         end
  1046.     end
  1047.  
  1048.     return astable and chars or string.char(unpack(chars))
  1049. end
  1050.  
  1051.  
  1052. local crypt = function(plaintext, salt)
  1053.     local resp, msg = pcall(cryptWrapper, plaintext, salt)
  1054.     if resp then
  1055.         if type(msg) == "table" then
  1056.             return textutils.serialize(msg)
  1057.         else
  1058.             return msg
  1059.         end
  1060.     else
  1061.         return nil
  1062.     end
  1063. end
  1064.  
  1065.  
  1066.  
  1067. --    GUI
  1068.  
  1069.  
  1070. local clear = function(bg, fg)
  1071.     term.setTextColor(fg)
  1072.     term.setBackgroundColor(bg)
  1073.     term.clear()
  1074.     term.setCursorPos(1, 1)
  1075. end
  1076.  
  1077.  
  1078. local fill = function(x, y, width, height, bg)
  1079.     term.setBackgroundColor(bg)
  1080.     for i = y, y + height - 1 do
  1081.         term.setCursorPos(x, i)
  1082.         term.write(string.rep(" ", width))
  1083.     end
  1084. end
  1085.  
  1086.  
  1087. local center = function(text)
  1088.     local x, y = term.getCursorPos()
  1089.     term.setCursorPos(math.floor(w / 2 - text:len() / 2) + (text:len() % 2 == 0 and 1 or 0), y)
  1090.     term.write(text)
  1091.     term.setCursorPos(1, y + 1)
  1092. end
  1093.  
  1094.  
  1095. local title = function(text)
  1096.     fill(1, 1, w, 1, theme.accent)
  1097.     term.setCursorPos(2, 1)
  1098.     term.write(text)
  1099.  
  1100.     term.setCursorPos(w, 1)
  1101.     term.write("x")
  1102.  
  1103.     term.setBackgroundColor(theme.background)
  1104. end
  1105.  
  1106.  
  1107. local centerSplit = function(text, width)
  1108.     local words = {}
  1109.     for word in text:gmatch("[^ \t]+") do
  1110.         table.insert(words, word)
  1111.     end
  1112.  
  1113.     local lines = {""}
  1114.     while lines[#lines]:len() < width do
  1115.         lines[#lines] = lines[#lines] .. words[1] .. " "
  1116.         table.remove(words, 1)
  1117.  
  1118.         if #words == 0 then
  1119.             break
  1120.         end
  1121.  
  1122.         if lines[#lines]:len() + words[1]:len() >= width then
  1123.             table.insert(lines, "")
  1124.         end
  1125.     end
  1126.  
  1127.     for _, line in pairs(lines) do
  1128.         center(line)
  1129.     end
  1130. end
  1131.  
  1132.  
  1133.  
  1134. --    Backend
  1135.  
  1136.  
  1137. local setupModem = function()
  1138.     for _, v in pairs(redstone.getSides()) do
  1139.         if peripheral.getType(v) == "modem" then
  1140.             table.insert(sides, v)
  1141.         end
  1142.     end
  1143.  
  1144.     if #sides <= 0 then
  1145.         error("No modem found!")
  1146.     end
  1147. end
  1148.  
  1149.  
  1150. local modem = function(func, ...)
  1151.     for _, side in pairs(sides) do
  1152.         if peripheral.getType(side) == "modem" then
  1153.             peripheral.call(side, func, ...)
  1154.         end
  1155.     end
  1156.  
  1157.     return true
  1158. end
  1159.  
  1160.  
  1161. local calculateChannel = function(domain, distance, id)
  1162.     local total = 1
  1163.  
  1164.     if distance then
  1165.         id = (id + 3642 * math.pi) % 100000
  1166.         if tostring(distance):find("%.") then
  1167.             local distProc = (tostring(distance):sub(1, tostring(distance):find("%.") + 1)):gsub("%.", "")
  1168.             total = tonumber(distProc..id)
  1169.         else
  1170.             total = tonumber(distance..id)
  1171.         end
  1172.     end
  1173.  
  1174.     for i = 1, #domain do
  1175.         total = total * string.byte(domain:sub(i, i))
  1176.         if total > 10000000000 then
  1177.             total = tonumber(tostring(total):sub(-5, -1))
  1178.         end
  1179.         while tostring(total):sub(-1, -1) == "0" do
  1180.             total = tonumber(tostring(total):sub(1, -2))
  1181.         end
  1182.     end
  1183.  
  1184.     return (total % 50000) + 10000
  1185. end
  1186.  
  1187.  
  1188. local isSession = function(sessions, channel, distance, id)
  1189.     for k, v in pairs(sessions) do
  1190.         if v[1] == distance and v[2] == id and v[3] == channel then
  1191.             return true
  1192.         end
  1193.     end
  1194.  
  1195.     return false
  1196. end
  1197.  
  1198.  
  1199. local fetchPage = function(domain, page)
  1200.     if (page:match("(.+)%.fwml$")) then
  1201.         page = page:match("(.+)%.fwml$")
  1202.     end
  1203.  
  1204.     local path = serversFolder .. "/" .. domain .. "/" .. page
  1205.     if fs.exists(path) and not fs.isDir(path) then
  1206.         local f = io.open(path, "r")
  1207.         local contents = f:read("*a")
  1208.         f:close()
  1209.  
  1210.         return contents, "lua"
  1211.     else
  1212.         if fs.exists(path..".fwml") and not fs.isDir(path..".fwml") then
  1213.             local f = io.open(path..".fwml", "r")
  1214.             local contents = f:read("*a")
  1215.             f:close()
  1216.  
  1217.             return contents, "fwml"
  1218.         end
  1219.     end
  1220.  
  1221.     return nil
  1222. end
  1223.  
  1224.  
  1225. local fetch404 = function(domain)
  1226.     local path = serversFolder .. "/" .. domain .. "/404"
  1227.     if fs.exists(path) and not fs.isDir(path) then
  1228.         local f = io.open(path, "r")
  1229.         local contents = f:read("*a")
  1230.         f:close()
  1231.  
  1232.         return contents
  1233.     else
  1234.         return defaultPages["404"]
  1235.     end
  1236. end
  1237.  
  1238.  
  1239. local backend = function(serverURL, onEvent, onMessage)
  1240.     local serverChannel = calculateChannel(serverURL)
  1241.     local sessions = {}
  1242.  
  1243.     local receivedMessages = {}
  1244.     local receivedMessageTimeouts = {}
  1245.  
  1246.     onMessage("Hosting rdnt://" .. serverURL)
  1247.     onMessage("Listening for incoming requests...")
  1248.  
  1249.     modem("closeAll")
  1250.     modem("open", publicDnsChannel)
  1251.     modem("open", serverChannel)
  1252.     modem("open", rednet.CHANNEL_REPEAT)
  1253.  
  1254.     for _, side in pairs(sides) do
  1255.         if peripheral.getType(side) == "modem" then
  1256.             rednet.open(side)
  1257.         end
  1258.     end
  1259.  
  1260.     rednet.host(protocolTag .. serverURL, initiateTag .. serverURL)
  1261.  
  1262.     while true do
  1263.         local eventArgs = {os.pullEvent()}
  1264.         local event, givenSide, givenChannel, givenID, givenMessage, givenDistance = unpack(eventArgs)
  1265.         if event == "modem_message" then
  1266.             if givenChannel == publicDnsChannel and givenMessage == DNSRequestTag and givenID == responseID then
  1267.                 modem("open", publicRespChannel)
  1268.                 modem("transmit", publicRespChannel, responseID, DNSResponseTag .. serverURL)
  1269.                 modem("close", publicRespChannel)
  1270.             elseif givenChannel == serverChannel and givenMessage:match(initiatePattern) == serverURL then
  1271.                 modem("transmit", serverChannel, responseID, crypt(connectTag .. serverURL, serverURL .. tostring(givenDistance) .. givenID))
  1272.  
  1273.                 if #sessions > 50 then
  1274.                     modem("close", sessions[#sessions][3])
  1275.                     table.remove(sessions)
  1276.                 end
  1277.  
  1278.                 local isInSessions = false
  1279.                 for k, v in pairs(sessions) do
  1280.                     if v[1] == givenDistance and v[3] == givenID then
  1281.                         isInSessions = true
  1282.                     end
  1283.                 end
  1284.  
  1285.                 local userChannel = calculateChannel(serverURL, givenDistance, givenID)
  1286.                 if not isInSessions then
  1287.                     onMessage("[DIRECT] Starting encrypted connection: " .. userChannel)
  1288.                     table.insert(sessions, {givenDistance, givenID, userChannel})
  1289.                     modem("open", userChannel)
  1290.                 else
  1291.                     modem("open", userChannel)
  1292.                 end
  1293.             elseif isSession(sessions, givenChannel, givenDistance, givenID) then
  1294.                 local request = crypt(textutils.unserialize(givenMessage), serverURL .. tostring(givenDistance) .. givenID)
  1295.                 if request then
  1296.                     local domain = request:match(retrievePattern)
  1297.                     if domain then
  1298.                         local page = domain:match("^[^/]+/(.+)")
  1299.                         if not page then
  1300.                             page = "index"
  1301.                         end
  1302.  
  1303.                         onMessage("[DIRECT] Requested: /" .. page)
  1304.  
  1305.                         local contents, language = fetchPage(serverURL, page)
  1306.                         if not contents then
  1307.                             contents = fetch404(serverURL)
  1308.                         end
  1309.  
  1310.                         local header
  1311.                         if language == "fwml" then
  1312.                             header = {language = "Firewolf Markup"}
  1313.                         else
  1314.                             header = {language = "Lua"}
  1315.                         end
  1316.  
  1317.                         modem("transmit", givenChannel, responseID, crypt(headTag .. textutils.serialize(header) .. bodyTag .. contents, serverURL .. tostring(givenDistance) .. givenID))
  1318.                     elseif request == disconnectTag then
  1319.                         for k, v in pairs(sessions) do
  1320.                             if v[2] == givenChannel then
  1321.                                 sessions[k] = nil
  1322.                                 break
  1323.                             end
  1324.                         end
  1325.  
  1326.                         modem("close", givenChannel)
  1327.                         onMessage("[DIRECT] Connection closed: " .. givenChannel)
  1328.                     end
  1329.                 end
  1330.             elseif givenChannel == rednet.CHANNEL_REPEAT and type(givenMessage) == "table"
  1331.             and givenMessage.nMessageID and givenMessage.nRecipient and
  1332.             not receivedMessages[givenMessage.nMessageID] then
  1333.                 receivedMessages[givenMessage.nMessageID] = true
  1334.                 receivedMessageTimeouts[os.startTimer(30)] = givenMessage.nMessageID
  1335.  
  1336.                 modem("transmit", rednet.CHANNEL_REPEAT, givenID, givenMessage)
  1337.                 modem("transmit", givenMessage.nRecipient, givenID, givenMessage)
  1338.             end
  1339.         elseif event == "timer" then
  1340.             local messageID = receivedMessageTimeouts[givenSide]
  1341.             if messageID then
  1342.                 receivedMessageTimeouts[givenSide] = nil
  1343.                 receivedMessages[messageID] = nil
  1344.             end
  1345.         elseif event == "rednet_message" then
  1346.             if givenID == DNSRequestTag and givenChannel == DNSRequestTag then
  1347.                 rednet.send(givenSide, DNSResponseTag .. serverURL, DNSRequestTag)
  1348.             elseif givenID == protocolTag .. serverURL then
  1349.                 local id = givenSide
  1350.                 local decrypt = crypt(textutils.unserialize(givenChannel), serverURL .. id)
  1351.                 if decrypt then
  1352.                     local domain = decrypt:match(retrievePattern)
  1353.                     if domain then
  1354.                         local page = domain:match("^[^/]+/(.+)")
  1355.                         if not page then
  1356.                             page = "index"
  1357.                         end
  1358.  
  1359.                         onMessage("[REDNET] Requested: /" .. page .. " from " .. id)
  1360.  
  1361.                         local contents, language = fetchPage(serverURL, page)
  1362.                         if not contents then
  1363.                             contents = fetch404(serverURL)
  1364.                         end
  1365.  
  1366.                         local header
  1367.                         if language == "fwml" then
  1368.                             header = {language = "Firewolf Markup"}
  1369.                         else
  1370.                             header = {language = "Lua"}
  1371.                         end
  1372.  
  1373.                         rednet.send(id, crypt(headTag .. textutils.serialize(header) .. bodyTag .. contents, serverURL .. givenSide), protocolTag .. serverURL)
  1374.                     end
  1375.                 end
  1376.             end
  1377.         end
  1378.  
  1379.         local shouldExit = onEvent(unpack(eventArgs))
  1380.         if shouldExit then
  1381.             rednet.unhost(protocolTag .. serverURL, initiateTag .. serverURL)
  1382.             break
  1383.         end
  1384.     end
  1385. end
  1386.  
  1387.  
  1388.  
  1389. --    Modification
  1390.  
  1391.  
  1392. local isValidDomain = function(domain)
  1393.     local success = domain:match("^([a-zA-Z0-9_%-%.]+)$")
  1394.     if success and domain:sub(1, 1) ~= "-" and domain:len() > 3 and domain:len() < 30 then
  1395.         return true
  1396.     else
  1397.         return false
  1398.     end
  1399. end
  1400.  
  1401.  
  1402. local serverExists = function(domain)
  1403.     local path = serversFolder .. "/" .. domain
  1404.     if fs.exists(path) and fs.exists(path .. "/" .. indexFileName) then
  1405.         return true
  1406.     end
  1407.  
  1408.     return false
  1409. end
  1410.  
  1411.  
  1412. local listServers = function()
  1413.     local servers = {}
  1414.     local contents = fs.list(serversFolder)
  1415.  
  1416.     for k, name in pairs(contents) do
  1417.         local path = serversFolder .. "/" .. name
  1418.         if fs.isDir(path) and not fs.isDir(path .. "/" .. indexFileName) then
  1419.             table.insert(servers, "rdnt://" .. name)
  1420.         end
  1421.     end
  1422.  
  1423.     return servers
  1424. end
  1425.  
  1426.  
  1427. local newServer = function(domain)
  1428.     if not isValidDomain(domain) then
  1429.         return
  1430.     end
  1431.  
  1432.     local path = serversFolder .. "/" .. domain
  1433.     fs.makeDir(path)
  1434.  
  1435.     local indexPath = path .. "/index"
  1436.     local indexContent = defaultPages["index"]:gsub("${DOMAIN}", domain)
  1437.  
  1438.     local f = io.open(indexPath, "w")
  1439.     f:write(indexContent)
  1440.     f:close()
  1441. end
  1442.  
  1443.  
  1444. local deleteServer = function(domain)
  1445.     if not isValidDomain(domain) then
  1446.         return
  1447.     end
  1448.  
  1449.     local path = serversFolder .. "/" .. domain
  1450.     fs.delete(path)
  1451. end
  1452.  
  1453. --    Menu Bar
  1454.  
  1455.  
  1456. local getTabName = function(domain)
  1457.     local name = domain
  1458.     if name:len() > maxTabNameWidth then
  1459.         name = name:sub(1, maxTabNameWidth)
  1460.     end
  1461.  
  1462.     if name:sub(-1, -1) == "." then
  1463.         name = name:sub(1, -2)
  1464.     end
  1465.  
  1466.     return name
  1467. end
  1468.  
  1469.  
  1470. local determineClickedTab = function(x, y)
  1471.     if y == 2 then
  1472.         local minx = 2
  1473.         for i, tab in pairs(tabs) do
  1474.             local name = getTabName(tab.domain)
  1475.  
  1476.             if x >= minx and x <= minx + name:len() - 1 then
  1477.                 return i
  1478.             elseif x == minx + name:len() and i == currentTab and #tabs > 1 then
  1479.                 return "close"
  1480.             else
  1481.                 minx = minx + name:len() + 2
  1482.             end
  1483.         end
  1484.  
  1485.         if x == minx and #tabs < maxTabs then
  1486.             return "new"
  1487.         end
  1488.     end
  1489.  
  1490.     return nil
  1491. end
  1492.  
  1493.  
  1494. local setupMenubar = function()
  1495.     menubarWindow = window.create(term.native(), 1, 1, w, 2, false)
  1496. end
  1497.  
  1498.  
  1499. local drawMenubar = function()
  1500.     term.redirect(menubarWindow)
  1501.     menubarWindow.setVisible(true)
  1502.  
  1503.     clear(theme.background, theme.text)
  1504.  
  1505.     fill(1, 1, w, 1, theme.accent)
  1506.     term.setCursorPos(2, 1)
  1507.     term.write("Firewolf Server " .. version)
  1508.     term.setCursorPos(w, 1)
  1509.     term.write("x")
  1510.  
  1511.     fill(1, 2, w, 1, theme.subtle)
  1512.     term.setCursorPos(1, 2)
  1513.  
  1514.     for i, tab in pairs(tabs) do
  1515.         term.setTextColor(theme.lightText)
  1516.         if i == currentTab then
  1517.             term.setTextColor(theme.text)
  1518.         end
  1519.  
  1520.         local name = getTabName(tab.domain)
  1521.         term.write(" " .. name)
  1522.  
  1523.         if i == currentTab then
  1524.             term.setTextColor(theme.errorText)
  1525.             term.write("x")
  1526.         else
  1527.             term.write(" ")
  1528.         end
  1529.     end
  1530.  
  1531.     if #tabs < maxTabs then
  1532.         term.setTextColor(theme.lightText)
  1533.         term.write(" + ")
  1534.     end
  1535. end
  1536.  
  1537.  
  1538.  
  1539. --    Hosting Interface
  1540.  
  1541.  
  1542. local hostInterface = function(index, domain)
  1543.     local log = {}
  1544.     local height = h - 4
  1545.     local buttons = {}
  1546.  
  1547.     local draw = function()
  1548.         os.queueEvent(updateMenubarEvent)
  1549.  
  1550.         clear(theme.background, theme.text)
  1551.         term.setCursorPos(1, 2)
  1552.  
  1553.         for i, button in pairs(buttons) do
  1554.             local x, y = term.getCursorPos()
  1555.             buttons[i].startX = x + 1
  1556.             term.write(" [" .. button.text .. "] ")
  1557.  
  1558.             x, y = term.getCursorPos()
  1559.             buttons[i].endX = x - 1
  1560.             buttons[i].y = y
  1561.         end
  1562.  
  1563.         term.setCursorPos(1, 4)
  1564.         for i = 1, height do
  1565.             local message = log[height - i]
  1566.             if message then
  1567.                 print(" " .. message)
  1568.             end
  1569.         end
  1570.     end
  1571.  
  1572.     local onMessage = function(message)
  1573.         table.insert(log, 1, message)
  1574.         if index == currentTab then
  1575.             draw()
  1576.         end
  1577.     end
  1578.  
  1579.     local editServer = function()
  1580.         clear(colors.black, colors.white)
  1581.  
  1582.         local path = serversFolder .. "/" .. domain
  1583.         local oldDir = shell.dir()
  1584.         shell.setDir(path)
  1585.  
  1586.         term.setCursorPos(1, 1)
  1587.         while true do
  1588.             term.setTextColor(theme.yellow)
  1589.             term.write("> ")
  1590.             term.setTextColor(colors.white)
  1591.             local command = modifiedRead()
  1592.             if command == "exit" then
  1593.                 break
  1594.             else
  1595.                 shell.run(command)
  1596.             end
  1597.         end
  1598.  
  1599.         shell.setDir(oldDir)
  1600.     end
  1601.  
  1602.     local hostOnStartup = function()
  1603.         local path = serversFolder .. "/" .. domain
  1604.         if fs.isDir(path) and fs.exists(path .. "/" .. indexFileName) then
  1605.             if fs.exists("/startup") then
  1606.                 if not fs.isDir("/startup") then
  1607.                     local f = io.open("/startup", "r")
  1608.                     local firstLine = f:read("*l")
  1609.                     if firstLine ~= "-- Launch Firewolf Server" then
  1610.                         fs.move("/startup", "/old-startup")
  1611.                     end
  1612.  
  1613.                     f:close()
  1614.                 else
  1615.                     fs.move("/startup", "/old-startup")
  1616.                 end
  1617.             end
  1618.  
  1619.             local f = io.open("/startup", "w")
  1620.             f:write("-- Launch Firewolf Server\n\nterm.clear()\nsleep(0.1)\nshell.run(\"/" .. shell.getRunningProgram() .. "\", \"" .. domain .. "\")")
  1621.             f:close()
  1622.  
  1623.             onMessage("Will run on startup!")
  1624.         end
  1625.     end
  1626.  
  1627.     local onEvent = function(...)
  1628.         local event = {...}
  1629.  
  1630.         if currentTab == index then
  1631.             if event[1] == "mouse_click" then
  1632.                 local clicked = nil
  1633.                 for i, button in pairs(buttons) do
  1634.                     if event[3] >= button.startX and event[3] <= button.endX and event[4] == button.y then
  1635.                         button.action()
  1636.                     end
  1637.                 end
  1638.             end
  1639.         end
  1640.  
  1641.         return false
  1642.     end
  1643.  
  1644.     buttons = {
  1645.         {text = "Edit Files", action = function()
  1646.             editServer(domain)
  1647.         end},
  1648.         {text = "Run on Startup", action = function()
  1649.             hostOnStartup()
  1650.         end},
  1651.     }
  1652.  
  1653.     clear(theme.background, theme.text)
  1654.     term.setCursorPos(1, 2)
  1655.     draw()
  1656.  
  1657.     backend(domain, onEvent, onMessage)
  1658. end
  1659.  
  1660.  
  1661.  
  1662. --    Tab Interface
  1663.  
  1664.  
  1665. local newServerInterface = function()
  1666.     clear(theme.background, theme.text)
  1667.  
  1668.     term.setCursorPos(4, 3)
  1669.     term.write("Domain name: rdnt://")
  1670.  
  1671.     local domain = modifiedRead()
  1672.  
  1673.     term.setCursorPos(1, 5)
  1674.     if not isValidDomain(domain) then
  1675.         print("   Invalid domain name!")
  1676.         print("")
  1677.         print("   Domain names must be 3 - 30 letters")
  1678.         print("   and only contain a to z, 0 to 9, -, ., and _")
  1679.     else
  1680.         print("   Server created successfully!")
  1681.  
  1682.         newServer(domain)
  1683.     end
  1684.  
  1685.     sleep(2)
  1686. end
  1687.  
  1688.  
  1689. local serverSelectionInterface = function()
  1690.     local servers = listServers()
  1691.     table.insert(servers, 1, "New Server")
  1692.  
  1693.     local startY = 1
  1694.     local height = h - startY - 1
  1695.     local scroll = 0
  1696.  
  1697.     local draw = function()
  1698.         fill(1, startY, w, height + 1, theme.background)
  1699.  
  1700.         for i = scroll + 1, scroll + height do
  1701.             if servers[i] then
  1702.                 term.setCursorPos(3, (i - scroll) + startY)
  1703.  
  1704.                 if servers[i]:find("rdnt://") then
  1705.                     term.setTextColor(theme.errorText)
  1706.                     term.write("x ")
  1707.                     term.setTextColor(theme.text)
  1708.                 else
  1709.                     term.write("  ")
  1710.                 end
  1711.  
  1712.                 term.write(servers[i])
  1713.             end
  1714.         end
  1715.     end
  1716.  
  1717.     draw()
  1718.  
  1719.     while true do
  1720.         local event, but, x, y = os.pullEvent()
  1721.  
  1722.         if event == "mouse_click" and y >= startY and y <= startY + height then
  1723.             local item = servers[y - startY + scroll]
  1724.             if item then
  1725.                 item = item:gsub("rdnt://", "")
  1726.                 if x == 3 then
  1727.                     deleteServer(item)
  1728.                     servers = listServers()
  1729.                     table.insert(servers, 1, "New Server")
  1730.                     draw()
  1731.                 elseif x > 3 then
  1732.                     if item == "New Server" then
  1733.                         newServerInterface()
  1734.                         servers = listServers()
  1735.                         table.insert(servers, 1, "New Server")
  1736.                         draw()
  1737.                     else
  1738.                         return item
  1739.                     end
  1740.                 end
  1741.             end
  1742.         elseif event == "key" then
  1743.             if but == keys.up then
  1744.                 scroll = math.max(0, scroll - 1)
  1745.             elseif but == keys.down and #servers > height then
  1746.                 scroll = math.min(scroll + 1, #servers - height)
  1747.             end
  1748.  
  1749.             draw()
  1750.         end
  1751.     end
  1752. end
  1753.  
  1754.  
  1755. local tabInterface = function(index, startDomain)
  1756.     if startDomain then
  1757.         tabs[index].domain = startDomain
  1758.         hostInterface(index, startDomain)
  1759.     end
  1760.  
  1761.     while true do
  1762.         local domain = serverSelectionInterface(index)
  1763.  
  1764.         tabs[index].domain = domain
  1765.         os.queueEvent(updateMenubarEvent)
  1766.  
  1767.         hostInterface(index, domain)
  1768.     end
  1769. end
  1770.  
  1771.  
  1772.  
  1773. --    Tabs
  1774.  
  1775.  
  1776. local switchTab = function(index, shouldntResume)
  1777.     if not tabs[index] then
  1778.         return
  1779.     end
  1780.  
  1781.     if tabs[currentTab].win then
  1782.         tabs[currentTab].win.setVisible(false)
  1783.     end
  1784.  
  1785.     currentTab = index
  1786.  
  1787.     term.redirect(term.native())
  1788.     clear(theme.background, theme.text)
  1789.     drawMenubar()
  1790.  
  1791.     for _, tab in pairs(tabs) do
  1792.         tab.win.setVisible(false)
  1793.     end
  1794.  
  1795.     term.redirect(tabs[index].win)
  1796.     term.setCursorPos(1, 1)
  1797.     tabs[index].win.setVisible(true)
  1798.     tabs[index].win.redraw()
  1799.  
  1800.     if not shouldntResume then
  1801.         --term.redirect()
  1802.         term.setCursorPos(tabs[index].ox, tabs[index].oy)
  1803.  
  1804.         coroutine.resume(tabs[index].thread, tabSwitchEvent, index)
  1805.  
  1806.         local ox, oy = term.getCursorPos()
  1807.         tabs[index].ox = ox
  1808.         tabs[index].oy = oy
  1809.     end
  1810. end
  1811.  
  1812.  
  1813. local closeCurrentTab = function()
  1814.     if #tabs <= 0 then
  1815.         return
  1816.     end
  1817.  
  1818.     table.remove(tabs, currentTab)
  1819.  
  1820.     currentTab = math.max(currentTab - 1, 1)
  1821.     switchTab(currentTab, true)
  1822. end
  1823.  
  1824.  
  1825. local loadTab = function(index, domain)
  1826.     tabs[index] = {}
  1827.     tabs[index].domain = domain and domain or "Server Listing"
  1828.     tabs[index].win = window.create(term.native(), 1, 3, w, h - 2, false)
  1829.     tabs[index].thread = coroutine.create(function()
  1830.         local _, err = pcall(function()
  1831.             tabInterface(index, domain)
  1832.         end)
  1833.  
  1834.         if err then
  1835.             os.queueEvent(triggerErrorEvent, err)
  1836.         end
  1837.     end)
  1838.  
  1839.     tabs[index].ox = 1
  1840.     tabs[index].oy = 1
  1841.  
  1842.     switchTab(index)
  1843. end
  1844.  
  1845.  
  1846.  
  1847. --    Interface
  1848.  
  1849.  
  1850. local handleMouseClick = function(event, but, x, y)
  1851.     if y == 1 then
  1852.         if x == w then
  1853.             error()
  1854.         end
  1855.  
  1856.         return true
  1857.     elseif y == 2 then
  1858.         local index = determineClickedTab(x, y)
  1859.         if index == "new" and #tabs < maxTabs then
  1860.             loadTab(#tabs + 1)
  1861.         elseif index == "close" then
  1862.             closeCurrentTab()
  1863.         elseif index then
  1864.             switchTab(index)
  1865.         end
  1866.  
  1867.         return true
  1868.     end
  1869.  
  1870.     return false
  1871. end
  1872.  
  1873.  
  1874. local handleEvents = function()
  1875.     local loadedTab = false
  1876.  
  1877.     if #args > 0 then
  1878.         for _, domain in pairs(args) do
  1879.             if isValidDomain(domain) and serverExists(domain) then
  1880.                 loadTab(#tabs + 1, domain)
  1881.                 loadedTab = true
  1882.             end
  1883.         end
  1884.     end
  1885.  
  1886.     if not loadedTab then
  1887.         loadTab(1)
  1888.     end
  1889.  
  1890.     while true do
  1891.         local event = {os.pullEvent()}
  1892.  
  1893.         if event[1] == updateMenubarEvent then
  1894.             drawMenubar()
  1895.         end
  1896.  
  1897.         local cancelEvent = false
  1898.         if event[1] == "mouse_click" then
  1899.             cancelEvent = handleMouseClick(unpack(event))
  1900.         elseif event[1] == triggerErrorEvent then
  1901.             error(event[2])
  1902.         end
  1903.  
  1904.         if not cancelEvent then
  1905.             term.redirect(tabs[currentTab].win)
  1906.             term.setCursorPos(tabs[currentTab].ox, tabs[currentTab].oy)
  1907.  
  1908.             if event[1] == "mouse_click" then
  1909.                 event[4] = event[4] - 2
  1910.             end
  1911.  
  1912.             coroutine.resume(tabs[currentTab].thread, unpack(event))
  1913.  
  1914.             local ox, oy = term.getCursorPos()
  1915.             tabs[currentTab].ox = ox
  1916.             tabs[currentTab].oy = oy
  1917.  
  1918.             local allowedEvents = {
  1919.                 ["rednet_message"] = true,
  1920.                 ["modem_message"] = true,
  1921.                 ["timer"] = true,
  1922.             }
  1923.  
  1924.             if allowedEvents[event[1]] then
  1925.                 for i, tab in pairs(tabs) do
  1926.                     if i ~= currentTab then
  1927.                         term.setCursorPos(tab.ox, tab.oy)
  1928.                         coroutine.resume(tab.thread, unpack(event))
  1929.  
  1930.                         local ox, oy = term.getCursorPos()
  1931.                         tabs[i].ox = ox
  1932.                         tabs[i].oy = oy
  1933.                     end
  1934.                 end
  1935.             end
  1936.         end
  1937.     end
  1938. end
  1939.  
  1940.  
  1941.  
  1942. --    Main
  1943.  
  1944.  
  1945. local main = function()
  1946.     if term.isColor() then
  1947.         theme = colorTheme
  1948.     else
  1949.         theme = grayscaleTheme
  1950.     end
  1951.  
  1952.     setupModem()
  1953.     setupMenubar()
  1954.     fs.makeDir(serversFolder)
  1955.  
  1956.     handleEvents()
  1957. end
  1958.  
  1959.  
  1960. local handleError = function(err)
  1961.     clear(theme.background, theme.text)
  1962.  
  1963.     fill(1, 3, w, 3, theme.subtle)
  1964.     term.setCursorPos(1, 4)
  1965.     center("Firewolf Server has crashed!")
  1966.  
  1967.     term.setBackgroundColor(theme.background)
  1968.     term.setCursorPos(1, 8)
  1969.     centerSplit(err, w - 4)
  1970.     print("\n")
  1971.     center("Please report this error to")
  1972.     center("GravityScore or 1lann.")
  1973.     print("")
  1974.     center("Press any key to exit.")
  1975.  
  1976.     os.pullEvent("key")
  1977.     os.queueEvent("")
  1978.     os.pullEvent()
  1979. end
  1980.  
  1981.  
  1982. local originalDir = shell.dir()
  1983. local originalTerminal = term.current()
  1984. local _, err = pcall(main)
  1985. term.redirect(originalTerminal)
  1986. shell.setDir(originalDir)
  1987.  
  1988. if err and not err:lower():find("terminate") then
  1989.     handleError(err)
  1990. end
  1991.  
  1992. if modem then
  1993.     for _, side in pairs(sides) do
  1994.         if peripheral.getType(side) == "modem" then
  1995.             rednet.close(side)
  1996.         end
  1997.     end
  1998.     modem("closeAll")
  1999. end
  2000.  
  2001.  
  2002. clear(colors.black, colors.white)
  2003. center("Thanks for using Firewolf Server " .. version)
  2004. center("Made by GravityScore and 1lann")
  2005. print("")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement