IGDev

TRoR - Term Redirect over Rednet

Sep 7th, 2015
238
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 36.42 KB | None | 0 0
  1. --[[PACKET API: This API allows information to be easily transferred between computers. Packets are formed in a similar fashion to real network packets. Each packet
  2. contains the type of the program sending it ("server", "client", "browser", etc.), the sender's computer name and ID, the recipient's (destination's) computer name and
  3. ID, the type of the data being sent, and the data itself. The data may be anything except a function or a table containing a function. Packets are serialized as strings
  4. and sent over rednet. The recipient ID is used as the sending channel: pastebin:8CfjPgCu]]
  5. print("Packet Utility 1.0")
  6. --Utility for quickly yielding. This is very important for the queue functions as they are intended to run parallel to other functions
  7. function yield()
  8.     --Queue an event and then yield for it. This is faster than using a timer, but does the same job
  9.     os.queueEvent("threadyield")
  10.     coroutine.yield("threadyield")
  11.  
  12. end
  13. --[[Utility for checking if a variable is of a required type. If the type matches, true is returned. Otherwise false may be returned, or a function may be provided to
  14. take come custom action such as raising an error or returning an error message. Passing "standard_error" as the function will cause an error to be raised with a common
  15. error message. The level argument is the stack level above this function call which is responsible for the error. This defaults to the level above the caller]]
  16. function checkType(mVar, sType, func, nLevel)
  17.     --The function to run if the type doesn't match, or a default function returning false
  18.     local func = func or function() return false end
  19.     --The level above this function's call which caused the issue, or the level above the caller
  20.     local nLevel = nLevel or 2
  21.     --Response to most type mismatches: "Expected [expected type], got [actual type]"
  22.     local function standard_error(gVar, sExpectedType, sType, nLevel)
  23.        
  24.         error(string.format("Expected %s, got %s", sExpectedType, sType), nLevel)
  25.        
  26.     end
  27.    
  28.     if func == "standard_error" then
  29.        
  30.         func = standard_error
  31.        
  32.     end
  33.     --Check our argument's types
  34.     if type(sType) ~= "string" then standard_error(nil, "string", type(sType), 3) end
  35.     if type(func) ~= "function" then standard_error(nil, "function", type(func), 3) end
  36.     if type(nLevel) ~= "number" then standard_error(nil, "number", type(nLevel), 3) end
  37.     --If the level is 0, leave it. Otherwise, add 2 to compensate for the two function calls to get to func. The level of the calling function is technically 3, but for
  38.     --convenience we add 2 so they may use 1 as their level
  39.     if nLevel ~= 0 then
  40.        
  41.         nLevel = nLevel + 2
  42.        
  43.     end
  44.     --If the type of mVar does not match what is defined in sType, call func
  45.     if type(mVar) ~= sType then
  46.         --func is passed the variable which failed the test, the type which it needed to be, the actual type, and the level to raise the error at
  47.         return func(mVar, sType, type(mVar), nLevel)
  48.        
  49.     end
  50.     --mVar passed the test
  51.     return true
  52.    
  53. end
  54. --UnProtected CALL at a specified Level. Re-raises any error raised by the given function at the specified level. This makes sure our functions don't get blamed for a
  55. --user's mistake
  56. local function upcallL(nLevel, func, ...)
  57.     --Call the function with pcall
  58.     local tResult = {pcall(func, ...)}
  59.     --Extract the result of pcall's attempt
  60.     local bResult = table.remove(tResult, 1)
  61.     --If the level is 0, leave it. Otherwise, add 1 to compensate for the call to this function. The level of the calling function is technically 2, but for
  62.     --convenience we add 1 so they may use 1 as their level
  63.     if nLevel ~= 0 then
  64.        
  65.         nLevel = nLevel + 1
  66.        
  67.     end
  68.    
  69.     if bResult then
  70.         --Unpack what the function returned
  71.         return unpack(tResult)
  72.        
  73.     else
  74.         --Raise an error at the specified level
  75.         error(tResult[1], nLevel)
  76.        
  77.     end
  78.    
  79. end
  80. --Utility for quickly clearing a table, used to empty the queues after they've been processed in multi-packet mode
  81. function clearTable(tTbl)
  82.    
  83.     checkType(tTbl, "table", "standard_error")
  84.     --Get the first index in the table
  85.     local k = next(tTbl)
  86.    
  87.     while k do
  88.         --Set the index's value to nil
  89.         tTbl[k] = nil
  90.         --Set the index to the next non-nil index. This will be nil if the table is empty, which will end the loop
  91.         k = next(tTbl)
  92.  
  93.     end
  94.  
  95. end
  96. --Copies a table (in case you didn't know)
  97. function copyTable(tTbl)
  98.    
  99.     checkType(tTbl, "table", "standard_error")
  100.     --The table to copy to
  101.     local tCopy = {}
  102.     --Get the first index in the table
  103.     local k = next(tTbl)
  104.  
  105.     while k do
  106.         --Set the index's value in the copy, to its value in the original
  107.         tCopy[k] = tTbl[k]
  108.         --Get the next index after the current one. This will be nil if there are no more indexes, which will end the loop
  109.         k = next(tTbl, k)
  110.  
  111.     end
  112.     --Return the copy, and the original next to it for convenience
  113.     return tCopy, tTbl
  114.    
  115. end
  116. --Utility for creating packet tables
  117. function asPacketData(senderType, senderName, senderID, recipientName, recipientID, dataType, data, tOther)
  118.     --[[
  119.         Packet contains:
  120.             -what kind of server or client is sending the information
  121.             -the computer name and ID of the sender
  122.             -the computer name and ID of the computer intended to receive the packet (the ID is usually used as the sending channel)
  123.             -the type of data being sent
  124.             -the data itself
  125.             -any other information which a program may acknowledge, but the packet API will ignore
  126.     ]]
  127.     local tPacket = {
  128.  
  129.         senderType=senderType,
  130.         senderName=senderName,
  131.         senderID=senderID,
  132.         recipientName=recipientName,
  133.         recipientID=recipientID,
  134.         dataType=dataType,
  135.         data=data
  136.  
  137.     }
  138.     --If tOther is nil, set it to an empty table
  139.     local tOther = tOther or {}
  140.     checkType(tOther, "table", "standard_error")
  141.     --Add all the values in tOther to the packet
  142.     for k, v in pairs(tOther) do
  143.        
  144.         tPacket[k] = v
  145.        
  146.     end
  147.    
  148.     return tPacket
  149.    
  150. end
  151. --Utility for creating packet tables to be passed to send as send does not need to know the sender name or ID
  152. function asPacketDataS(senderType, recipientName, recipientID, dataType, data, tOther)
  153.     --Leave the sender name and ID nil, as send will set it to this computer's name and ID for us
  154.     return upcallL(2, asPacketData,
  155.        
  156.         senderType,
  157.         nil, nil,
  158.         recipientName,
  159.         recipientID,
  160.         dataType,
  161.         data,
  162.         tOther
  163.        
  164.     )
  165.    
  166. end
  167. --[[Compares two packets, a sample and a real packet, to check for things like who the sender was, and who they were sending to. This is intended to be used to check if
  168. this computer was the intended recipient. If all values in the sample packet match the same values in the real packet, all values in the sample packets are functions
  169. which take the same value of the sample packet as an argument and calling these functions return true, or any combination of the two, this function will return true.
  170. Otherwise, it will return false. Any values in the packet which are not in the sample packet will be ignored]]
  171. function checkPacket(tSamplePacket, tPacket)
  172.    
  173.     checkType(tSamplePacket, "table", "standard_error")
  174.     checkType(tPacket, "table", "standard_error")
  175.     --We only care about items that tSamplePacket has in it. If tPacket has an item the tSamplePacket doesn't, we'll ignore it
  176.     for k, v in pairs(tSamplePacket) do
  177.         --If the packet value doesn't match the sample packet value, and the sample packet value is not a function or is a function and it returns false
  178.         if not (tPacket[k] == v or (checkType(v, "function") and v(tPacket[k]))) then
  179.  
  180.             return false
  181.  
  182.         end
  183.  
  184.     end
  185.  
  186.     return true
  187.  
  188. end
  189. --Confirms that a table contains all of the parts of a standard packet
  190. local function validatePacket(tPacket)
  191.    
  192.     --Create a sample packet of functions which returns if the types of the packet match the defined standard component types, and check it against the packet with checkPacket
  193.     return checkPacket(asPacketData(
  194.        
  195.         function(mData) return checkType(mData, "string") end,--Sender type
  196.         function(mData) return checkType(mData, "string") end,--Sender name
  197.         function(mData) return checkType(mData, "number") end,--Sender ID
  198.         function(mData) return checkType(mData, "string") end,--Recipient name
  199.         function(mData) return checkType(mData, "number") end,--Recipient ID
  200.         function(mData) return checkType(mData, "string") end--Data type
  201.        
  202.     ), tPacket)
  203.  
  204. end
  205. --Formats informations about who's sending, who they're sending to, and what they're sending and returns it as a string which can be sent over rednet
  206. function asPacket(...)
  207.     --Convert the arguments into a packet table
  208.     local tPacket = upcallL(2, asPacketData, ...)
  209.     --Check it against the standard
  210.     if not validatePacket(tPacket) then
  211.        
  212.         error("asPacket: You are not allowed to generate corrupted packets", 2)
  213.  
  214.     end
  215.     --Serialize the packet as a string to be sent over rednet. If the packet contains a function, an error will be raised
  216.     return upcallL(2, textutils.serialize, tPacket)
  217.  
  218. end
  219. --Formats a string as a table of data, handling any error caused by the process and returning them as an errorMessage packet
  220. function asData(sPacket)
  221.    
  222.     checkType(sPacket, "string", "standard_error")
  223.     --Attempt to format the string as a table of information
  224.     local tPacket = textutils.unserialize(sPacket)
  225.     --If the result is a table, and the table passes as a standard packet, return it
  226.     if checkType(tPacket, "table") and validatePacket(tPacket) then
  227.  
  228.         return tPacket
  229.  
  230.     end
  231.     --Corrupted packet error, will usually be ignored as checkPacket will reject it in most cases
  232.     return asPacketData(
  233.  
  234.         "system",
  235.         "asData",
  236.         -1,
  237.         "none",
  238.         -1,
  239.         "errorMessage",
  240.         "Packet corrupted"
  241.  
  242.     )
  243.  
  244. end
  245. --Sends a packet on the channel of the given recipient recipient ID
  246. function send(sSenderType, sRecipientName, nRecipientID, sDataType, mData)
  247.    
  248.     --The packet is sent to the specified recipient's computer ID, assuming it will be receiving over rednet. This computers label and ID are used to form the packet
  249.     rednet.send(nRecipientID,
  250.         upcallL(2, asPacket, sSenderType, (os.getComputerLabel() or "LABEL_NOT_SET"), os.getComputerID(),
  251.             sRecipientName, nRecipientID, sDataType, mData)
  252.     )
  253.  
  254. end
  255. --[[Receives a packet and converts it into data. If a timeout is specified, and no data is received in that amount of time, an empty or "blank" packet will be returned
  256. If a sample packet is specified, this function will not return until it receives a packet which passes the check against the specified sample packet. If specified, this
  257. function may return blank packets, even if they do not match the sample packet. This defaults to false]]
  258. function receive(nTimeout, tSamplePacket)
  259.     --nTimeout may be nil, but otherwise it must be a number
  260.     if nTimeout then
  261.        
  262.         checkType(nTimeout, "number", "standard_error")
  263.        
  264.     end
  265.     --If tSamplePacket is nil, set it to an empty table
  266.     local tSamplePacket = tSamplePacket or {}
  267.     checkType(tSamplePacket, "table", "standard_error")
  268.    
  269.     while true do
  270.         --Rednet may raise a termination error, we will catch this and return it as an error message if this is permitted
  271.         local tMessage = {pcall(rednet.receive, nTimeout)}
  272.         local tPacket
  273.        
  274.         if not tMessage[1] then
  275.             --Some error occurred, create a packet containing the error message
  276.             tPacket = asPacketData(
  277.                
  278.                 "system",
  279.                 "receive",
  280.                 -1,
  281.                 "none",
  282.                 -1,
  283.                 "errorMessage",
  284.                 tMessage[2]
  285.                
  286.             )
  287.            
  288.         end
  289.         --If receive timed out, it returned nil. If this is the case, create a blank packet
  290.         if not tMessage[3] then
  291.            
  292.             tPacket = asPacketData(
  293.                
  294.                 "system",
  295.                 "receive",
  296.                 -1,
  297.                 "none",
  298.                 -1,
  299.                 "blank"
  300.                
  301.             )
  302.            
  303.         end
  304.         --If nothing has gone wrong so far, try to convert the received message into a packet
  305.         if not tPacket then
  306.            
  307.             tPacket = asData(tMessage[3])
  308.            
  309.         end
  310.         --Compare the packet to the sample packet. If it fails, we'll receive again until we get a packet that passes. When we get a packet that passes, we'll return it.
  311.         --If the packet is blank, we'll return it regardless of whether or not it passes
  312.         if checkPacket(tSamplePacket, tPacket) or tPacket.dataType == "blank" then
  313.            
  314.             return tPacket
  315.            
  316.         end
  317.        
  318.     end
  319.  
  320. end
  321. --Receives a packet, or multi-packet, and places it in the queue
  322. function queueReceiver(tQueue, tSamplePacket)
  323.    
  324.     checkType(tQueue, "table", "standard_error")
  325.     --If tSamplePacket is nil, set it to an empty table
  326.     local tSamplePacket = tSamplePacket or {}
  327.     checkType(tSamplePacket, "table", "standard_error")
  328.  
  329.     while true do
  330.         --Wait indefinitely for a packet which matches the sample packet
  331.         tPacket = receive(nil, tSamplePacket)
  332.         --If the packet was sent in multi-mode, it must be received in multi-mode. This means the data will be a table containing multiple pieces of data, and a flag the
  333.         --multiMode flag in this table will be set to true
  334.         if checkType(tPacket.data, "table") and tPacket.data.multiMode then
  335.             --Erase the flag, we don't need it any more
  336.             tPacket.data.multiMode = nil
  337.             --Add every piece of data being sent to the queue
  338.             for k, v in pairs(tPacket.data) do
  339.                
  340.                 table.insert(tQueue, v)
  341.  
  342.             end
  343.  
  344.         else
  345.             --Add the data to the queue
  346.             table.insert(tQueue, tPacket.data)
  347.  
  348.         end
  349.  
  350.     end
  351.  
  352. end
  353. --Runs parallel to a function which should fill the given queue with information to send. This information should NOT be formatted as a packet. As information is
  354. --received, it will be formatted as a packet, or as a multi-packet if the third function argument is true
  355. function queueSender(tQueue, tTemplatePacket, bMultiPacketMode)
  356.    
  357.     checkType(tQueue, "table", "standard_error")
  358.     checkType(tTemplatePacket, "table", "standard_error")
  359.     checkType(bMultiPacketMode, "boolean", "standard_error")
  360.     --tTemplatePacket may specify multiple recipients. They will be stored here
  361.     local tRecipients = {}
  362.    
  363.     if tTemplatePacket.recipients and checkType(tTemplatePacket.recipients, "table") then
  364.        
  365.         for k, v in pairs(tTemplatePacket.recipients) do
  366.             --If v is not a table
  367.             checkType(v, "table",
  368.                 function(gVar, sExpectedType, sType, nLevel)
  369.                    
  370.                     error(string.format("Error processing entry %s, entry: Expected %s, got %s", tostring(k), sExpectedType, sType), nLevel)
  371.                    
  372.                 end)
  373.             --If v.recipientName is not a string
  374.             checkType(v.recipientName, "string",
  375.                 function(gVar, sExpectedType, sType, nLevel)
  376.                    
  377.                     error(string.format("Error processing entry %s, recipientName: Expected %s, got %s", tostring(k), sExpectedType, sType), nLevel)
  378.                    
  379.                 end)
  380.             --If v.recipientID is not a number
  381.             checkType(v.recipientID, "number",
  382.                 function(gVar, sExpectedType, sType, nLevel)
  383.                    
  384.                     error(string.format("Error processing entry %s, recipientID: Expected %s, got %s", tostring(k), sExpectedType, sType), nLevel)
  385.                    
  386.                 end)
  387.            
  388.             table.insert(tRecipients, {recipientName=v.recipientName, recipientID=v.recipientID})
  389.            
  390.         end
  391.        
  392.     else
  393.         --Just treat tTemplatePacket as a regular packet
  394.         table.insert(tRecipients, {recipientName=tTemplatePacket.recipientName, recipientID=tTemplatePacket.recipientID})
  395.        
  396.     end
  397.    
  398.     while true do
  399.         --If there is something in the queue
  400.         if #tQueue > 0 then
  401.            
  402.             for k, v in pairs(tRecipients) do
  403.                 --In multi-mode we send the entire queue at once as a table, then clear the entire queue at once
  404.                 if bMultiPacketMode then
  405.                    
  406.                     tQueue.multiMode = true
  407.                     upcallL(2, send, tTemplatePacket.senderType, v.recipientName,
  408.                     v.recipientID, tTemplatePacket.dataType, tQueue)
  409.                     clearTable(tQueue)
  410.                    
  411.                 else
  412.                     --In single-mode, we clear one entry at a time, sending its value as the packet data
  413.                     upcallL(2, send, tTemplatePacket.senderType, v.recipientName,
  414.                     v.recipientID, tTemplatePacket.dataType, table.remove(tQueue, 1))
  415.                    
  416.                 end
  417.                
  418.             end
  419.            
  420.         end
  421.         --In single packet mode, if this runs too fast, the receiver will glitch out. yield is too fast, so we use sleep to slow the loop down a little
  422.         pcall(sleep, 0)
  423.  
  424.     end
  425.  
  426. end
  427. --Runs the given function parallel to the queueReceiver. The function is called every time a new piece of data is received and is passed that piece of data as an argument
  428. function packetListener(func, tSamplePacket)
  429.    
  430.     checkType(func, "function", "standard_error")
  431.     --The queue to receive our packets in
  432.     local tQueue = {}
  433.    
  434.     parallel.waitForAny(
  435.        
  436.         function()
  437.            
  438.             upcallL(2, queueReceiver, tQueue, tSamplePacket)
  439.  
  440.         end,
  441.         function()
  442.            
  443.             while true do
  444.                
  445.                 if #tQueue > 0 then
  446.                     --Empty out the queue
  447.                     while #tQueue > 0 do
  448.                         --Call the function, passing it the value being removed from the queue. If the function returns false, end the loop
  449.                         if not func(table.remove(tQueue, 1)) then
  450.  
  451.                             return
  452.  
  453.                         end
  454.  
  455.                     end
  456.  
  457.                 end
  458.  
  459.                 yield()
  460.  
  461.             end
  462.  
  463.         end
  464.  
  465.     )
  466.  
  467. end
  468. --Runs the given function parallel to the queueSender. The queue is passed to the function as an argument, anything placed in the queue will be sent over rednet and deleted
  469. function packetSender(func, tTemplatePacket, bMultiPacketMode)
  470.    
  471.     checkType(func, "function", "standard_error")
  472.     --The queue to send out packets from
  473.     local tQueue = {}
  474.  
  475.     parallel.waitForAny(
  476.  
  477.         function()
  478.            
  479.             upcallL(2, queueSender, tQueue, tTemplatePacket, bMultiPacketMode)
  480.  
  481.         end,
  482.         function()
  483.             --The running loop must happen in this function
  484.             func(tQueue)
  485.  
  486.         end
  487.  
  488.     )
  489.  
  490. end
  491. --END PACKET API
  492. --[[TRoR API: This API allows a program to share screen output with other computers, or receive it from another computer. It allows events to be transferred between
  493. computers, and it allows them to be modified, both before and after being sent, and just before they are pulled. It allows the shell to be escaped and a function to be
  494. run in the top level coroutine. This is because the multishell will strip flags off of mouse click events. Three built in functions will run the shell, the multishell,
  495. and a modified version of the multishll which doesn't strip mouse click events: pastebin:kjKrUP41]]
  496. print("TRoR Term Redirect over Rednet 1.0")
  497. --Kills the current running shell, allowing the given function to run in the top level coroutine
  498. --TLCO: http://www.computercraft.info/forums2/index.php?/topic/22078-extremely-short-no-multishell-tlco-kills-multishell-runs-normal-shell-as-the-top-level-coroutine/
  499. function killShell(func, ...)
  500.    
  501.     checkType(func, "function", "standard_error")
  502.     local tArgs = {...}
  503.     --Store the original versions of the functions we'll be overriding
  504.     local nativeShutdown = os.shutdown
  505.     local nativeYield = coroutine.yield
  506.     --This is called just before the bios exits, after the shell has died
  507.     os.shutdown = function()
  508.         --We're ready to move on, set these variables back to their native values for normal operation
  509.         os.shutdown = nativeShutdown
  510.         coroutine.yield = nativeYield
  511.         --Clean out any terms that anyone has set up
  512.         term.redirect(term.native())
  513.         --This has to be reset or it won't run again
  514.         os.unloadAPI("rom/apis/rednet")
  515.         os.loadAPI("rom/apis/rednet")
  516.         --Run the function
  517.         ok, err = pcall(func, unpack(tArgs))
  518.        
  519.         if not ok then
  520.             --Attempt to show the user any errors which may have occurred
  521.             pcall(function()
  522.                
  523.                 term.redirect(term.native())
  524.                 printError(err)
  525.                 print("Press any key to continue...")
  526.                 os.pullEventRaw("key")
  527.                
  528.             end)
  529.                
  530.         end
  531.         --The bios won't attempt another shutdown, so we will. This also allows the shell to be killed again, the overridden shutdown will be called here
  532.         os.shutdown()
  533.        
  534.     end
  535.     --When whatever program is currently running tries to yield or pull an event, this will crash it. This will eventually crash the shell, which will return control
  536.     --to the bios, where our overridden shutdown function will be called
  537.     coroutine.yield = function(...) error("Shell death", 0) end
  538.     --Begin the chaos
  539.     error("Shell death", 0)
  540.    
  541. end
  542. --Creates a startup file which runs a set of specified commands. If a startup file already exists, it will be moved by this function, then moved back by the new startup
  543. local function replaceStartup(tArgs)
  544.     --Collection of commands for the new shell to run
  545.     local tConverted = {}
  546.     --A single command being formed
  547.     local tCommand = {}
  548.     --Adds a command to the collection and clears it from the working variable
  549.     local function addCommand()
  550.        
  551.         if #tCommand > 0 then
  552.            
  553.             table.insert(tConverted, tCommand)
  554.             tCommand = {}
  555.            
  556.         end
  557.        
  558.     end
  559.    
  560.     for k, v in pairs(tArgs) do
  561.         --If it's a table, convert everything in it to a string.
  562.         if checkType(v, "table") then
  563.             --Add whatever command was previously being built; we're now working on a new command
  564.             addCommand()
  565.            
  566.             for ke, va in pairs(v) do
  567.                
  568.                 table.insert(tCommand, tostring(va))
  569.                
  570.             end
  571.            
  572.         else
  573.            
  574.             addCommand()
  575.             --Convert whatever else v is into a string, we'll try to run it anyway
  576.             table.insert(tCommand, tostring(v))
  577.            
  578.         end
  579.         --Add whatever we've got left
  580.         addCommand()
  581.        
  582.     end
  583.     --If there are no commands to run, we don't need to override the startup
  584.     if #tConverted < 1 then
  585.        
  586.         return
  587.        
  588.     end
  589.     --In case the default name already exists for some reason, we'll find a name which doesn't exist
  590.     local sStartupRelocation
  591.    
  592.     if fs.exists("startup") then
  593.        
  594.         local sName = ".startup"
  595.         local nAttemptCount = 1
  596.         local sStartupRelocation = sName
  597.         --We'll check for ".startup" first, then ".startup2", ".startup3", etc.
  598.         while fs.exists(sStartupRelocation) do
  599.            
  600.             nAttemptCount = nAttemptCount + 1
  601.             --".startup[attempt count]"
  602.             sStartupRelocation = sName .. nAttemptCount
  603.            
  604.         end
  605.        
  606.     end
  607.    
  608.     local sStartup = string.format(
  609.        
  610.         [[
  611.             --The serialized converted commands, these will just be a normal table when it is loaded
  612.             local tCommands = %s
  613.             --The startup location, or nil if there was no startup
  614.             local sStartupLocation = %s
  615.             local bRunStartup
  616.             --We need to make sure this happens as soon as possible, we don't want to leave a mess if we crash or the user reboots the computer
  617.             fs.delete("startup")
  618.             --If there was a startup here before, move it back in place
  619.             if sStartupLocation then
  620.                
  621.                 fs.move(sStartupLocation, "startup")
  622.                
  623.             end
  624.            
  625.             if multishell then
  626.                 --On the multishell, we run everything in its own tab all at once
  627.                 for k, v in pairs(tCommands) do
  628.                    
  629.                     --The last command, or only command if there is only one, will be run in the main shell tab
  630.                     if next(tCommands, k) then
  631.                        
  632.                         shell.openTab(unpack(v))
  633.                        
  634.                     else
  635.                        
  636.                         shell.run(unpack(v))
  637.                        
  638.                     end
  639.                    
  640.                 end
  641.                
  642.             else
  643.                 --On the shell, we run everything in the order it was provided, one at a time
  644.                 for k, v in pairs(tCommands) do
  645.                    
  646.                     shell.run(unpack(v))
  647.                    
  648.                 end
  649.                
  650.             end
  651.            
  652.         ]],
  653.        
  654.         textutils.serialize(tConverted),
  655.         tostring(sStartupRelocation)
  656.        
  657.     )
  658.     --If there is already a startup, move it out of the way
  659.     if sStartupRelocation then
  660.        
  661.         fs.move("startup", sStartupRelocation)
  662.        
  663.     end
  664.     --Write our new startup in the actual startup file
  665.     local tFile = fs.open("startup", "w")
  666.     tFile.write(sStartup)
  667.     tFile.close()
  668.    
  669. end
  670. --Clears the screen and prints a message in yellow or white text, depending on if the term is color or not respectively
  671. local function printStartupMessage(sMessage)
  672.     --User yellow for advanced computers, and white for regular computers
  673.     if term.isColor() then
  674.        
  675.         term.setTextColor(colors.yellow)
  676.        
  677.     else
  678.        
  679.         term.setTextColor(colors.white)
  680.        
  681.     end
  682.     --Background should always be black
  683.     term.setBackgroundColor(colors.black)
  684.     --Clear the screen
  685.     term.clear()
  686.     term.setCursorPos(1, 1)
  687.     print(sMessage)
  688.     term.setTextColor(colors.white)
  689.    
  690. end
  691. --Designed to be passed to killShell as the func argument. Runs the regular shell. This is one of the two functions which may be run to fix mouse click modification
  692. function runShell(...)
  693.    
  694.     replaceStartup({...})
  695.     --Display the running mode
  696.     printStartupMessage("Regular shell")
  697.     --Start the shell and rednet coroutines
  698.     parallel.waitForAny(
  699.        
  700.         function()
  701.            
  702.             os.run({}, "rom/programs/shell")
  703.            
  704.         end,
  705.         function()
  706.            
  707.             rednet.run()
  708.            
  709.         end
  710.        
  711.     )
  712.    
  713. end
  714. --Designed to be passed to killShell as the func argument. Runs the multishell. This program will break mouse click modification
  715. function runMultishell(...)
  716.     --Same as runShell, but running the multishell program instead
  717.     replaceStartup({...})
  718.     printStartupMessage("Regular multishell")
  719.    
  720.     parallel.waitForAny(
  721.        
  722.         function()
  723.            
  724.             os.run({}, "rom/programs/advanced/multishell")
  725.            
  726.         end,
  727.         function()
  728.            
  729.             rednet.run()
  730.            
  731.         end
  732.        
  733.     )
  734.    
  735. end
  736. --Runs a modified version of the multishell which doesn't strip the flags off of mouse click events.
  737. function clickSafeMultishell()
  738.     --The new program will be stored in this string
  739.     local sNewMultiShell = ""
  740.     --Simple convenience function for adding to the program
  741.     local function add(sText) sNewMultiShell = sNewMultiShell.."\n".. sText end
  742.     --This goes after the function definitions, before the multishell starts running
  743.     local sPreStart = [[
  744.         multishell.isClickSafe = true
  745.         local function copyEvent(tEventData)
  746.             local tEventCopy = {}
  747.             local k = next(tEventData)
  748.             while k do
  749.                 tEventCopy[k] = tEventData[k]
  750.                 k = next(tEventData, k)
  751.             end
  752.             return tEventCopy
  753.         end
  754.     ]]
  755.     --This replaces the portion of the running loop which handles click events. Rather than extracting the data normally found in a click event and passing that on, it
  756.     --copies the whole event, modifies it as needed, and then passes the copy with all its parts intact to the process
  757.     local sClickEvent = [[
  758.         -- Click event
  759.         local button, x, y = tEventData[2], tEventData[3], tEventData[4]
  760.         if bShowMenu and y == 1 then
  761.             -- Switch process
  762.             local tabStart = 1
  763.             for n=1,#tProcesses do
  764.                 tabEnd = tabStart + string.len( tProcesses[n].sTitle ) + 1
  765.                 if x >= tabStart and x <= tabEnd then
  766.                     selectProcess( n )
  767.                     redrawMenu()
  768.                     break
  769.                 end
  770.                 tabStart = tabEnd + 1
  771.             end
  772.         else
  773.             -- Passthrough to current process
  774.             tEventCopy = copyEvent(tEventData)
  775.             tEventCopy[4] = (bShowMenu and y-1) or y
  776.             resumeProcess( nCurrentProcess, table.unpack(tEventCopy) )
  777.             if cullProcess( nCurrentProcess ) then
  778.                 setMenuVisible( #tProcesses >= 2 )
  779.                 redrawMenu()
  780.             end
  781.         end
  782.  
  783.     elseif sEvent == "mouse_drag" or sEvent == "mouse_up" or sEvent == "mouse_scroll" then
  784.         -- Other mouse event
  785.         local p1, x, y = tEventData[2], tEventData[3], tEventData[4]
  786.         if not (bShowMenu and y == 1) then
  787.             -- Passthrough to current process
  788.             tEventCopy = copyEvent(tEventData)
  789.             tEventCopy[4] = (bShowMenu and y-1) or y
  790.             resumeProcess( nCurrentProcess, table.unpack(tEventCopy) )
  791.             if cullProcess( nCurrentProcess ) then
  792.                 setMenuVisible( #tProcesses >= 2 )
  793.                 redrawMenu()
  794.             end
  795.         end
  796.  
  797.     ]]
  798.     --This is basically a copy of os.run, except in runs strings instead of files
  799.     local function run(tEnv, sFunc, sName, ...)
  800.         --Prints an error message if it isn't nil and isn't an empty string
  801.         local function perr(sErr) if sErr and sErr ~= "" then printError(sErr) end end
  802.         local tEnv_ = tEnv
  803.         --Use the global environment as our index
  804.         setmetatable(tEnv_, {__index=_G})
  805.         --Attempt to load the string
  806.         local func, err = load(sFunc, sName, "t", tEnv_)
  807.        
  808.         if func then
  809.             --If it loaded, run it
  810.             ok, err = pcall(func, ...)
  811.            
  812.             if not ok then
  813.                 --Print out anything that goes wrong and return false
  814.                 perr(err)
  815.                
  816.                 return false
  817.                
  818.             end
  819.             --If everything went okay, return true
  820.             return true
  821.            
  822.         end
  823.        
  824.         perr(err)
  825.        
  826.         return false
  827.        
  828.     end
  829.    
  830.     local bAddLines = true
  831.     local tFile = fs.open("rom/programs/advanced/multishell", "r")
  832.     --This can only happen if the function is used on a non-advanced computer
  833.     checkType(tFile, "table", function(gVar, gExpectedType, gType, nLevel) error("Multishell not found", nLevel) end, 1)
  834.    
  835.     repeat
  836.        
  837.         local sLine = tFile.readLine()
  838.         --This is the start of the running loop
  839.         if sLine == "-- Begin" then
  840.            
  841.             add(sPreStart)
  842.             add(sLine)
  843.             --This is the start of the click event handler. We stop adding lines after this until we reach the "else" statement, which marks the end of the click handler
  844.         elseif sLine == "    elseif sEvent == \"mouse_click\" then" then
  845.            
  846.             bAddLines = false
  847.             add(sLine)
  848.             add(sClickEvent)
  849.             --This is the end of the click event handler. Start adding lines again
  850.         elseif sLine == "    else" then
  851.            
  852.             bAddLines = true
  853.             add(sLine)
  854.            
  855.         else
  856.             --If we're not in the click event handler portion, add the line
  857.             if bAddLines and sLine ~= nil then add(sLine) end
  858.            
  859.         end
  860.         --Until there are no more lines to read
  861.     until sLine == nil
  862.    
  863.     return run({}, sNewMultiShell, "ModdedMultiShell")
  864.    
  865. end
  866. --Designed to be passed to killShell as the func argument. Runs the multishell in the top level coroutine. This is one of the two functions which may be run to fix mouse
  867. --click modification
  868. function runClickSafeMultishell(...)
  869.    
  870.     replaceStartup({...})
  871.     printStartupMessage("Click safe multishell")
  872.    
  873.     parallel.waitForAny(
  874.        
  875.         function()
  876.            
  877.             clickSafeMultishell()
  878.            
  879.         end,
  880.         function()
  881.            
  882.             rednet.run()
  883.            
  884.         end
  885.        
  886.     )
  887.    
  888. end
  889. --Receives terminal commands from a remote computer and draws them on this computers screen
  890. function receiveScreen(tSamplePacket)
  891.    
  892.     local tSamplePacket = tSamplePacket or {}
  893.     checkType(tSamplePacket, "table", "standard_error")
  894.     --Create a new packet listener
  895.     upcallL(2, packetListener, function(tCommand)
  896.             --Make sure this is at least an empty table
  897.             tCommand.args = tCommand.args or {}
  898.             --The sender terminated, so we will too
  899.             if tCommand.command == "exit" then
  900.                
  901.                 return false
  902.                    
  903.             end
  904.             --The term commands are the names of the function, so get the function and call it with the args table as its arguments
  905.             term[tCommand.command](unpack(tCommand.args))
  906.             --Tell the packet listener to keep running
  907.             return true
  908.            
  909.         end,
  910.         asPacketData(
  911.             --We can use all the tests from the sample packet, except the data type must be "termCommand", and the data must be a table
  912.             tSamplePacket.senderType,
  913.             tSamplePacket.senderName,
  914.             tSamplePacket.senderID,
  915.             tSamplePacket.recipientName,
  916.             tSamplePacket.recipientID,
  917.             "termCommand",
  918.             function(mData) return checkType(mData, "table") end
  919.            
  920.         )
  921.     )
  922.    
  923. end
  924. --Sends terminal commands over rednet to be drawn by a remote computer
  925. function sendScreen(func, tTemplatePacket, bMultiPacketMode)
  926.    
  927.     checkType(func, "function", "standard_error")
  928.     checkType(tTemplatePacket, "table", "standard_error")
  929.    
  930.     upcallL(2, packetSender, function(tQueue)
  931.            
  932.             local tNewTerm = {}
  933.             --These functions do not need to be sent
  934.             local tReturnOnly = {
  935.                
  936.                 ["getCursorPos"] = true,
  937.                 ["isColor"] = true,
  938.                 ["isColour"] = true,
  939.                 ["getSize"] = true,
  940.                 ["getTextColor"] = true,
  941.                 ["getBackgroundColor"] = true,
  942.                 ["getTextColour"] = true,
  943.                 ["getBackgroundColour"] = true,
  944.                
  945.             }
  946.            
  947.             local tParentTerm
  948.            
  949.             for k, v in pairs(term.native()) do
  950.                
  951.                 local sName = k
  952.                
  953.                 tNewTerm[k] = (tReturnOnly[k] and function()
  954.                     --Return only functions are just called and their return values are returned
  955.                     return tParentTerm[sName]()
  956.                    
  957.                 end)
  958.                     or function(...)
  959.                         --Other functions are called, but not returned, and their name and arguments are sent as a command to any receiving computer
  960.                         tParentTerm[sName](...)
  961.                         table.insert(tQueue, {command=sName, args={...}})
  962.                        
  963.                     end
  964.                
  965.             end
  966.            
  967.             tParentTerm = term.redirect(tNewTerm)
  968.             --Resets the term to what it was before we got to it
  969.             term.resetTerm = function()
  970.                
  971.                 term.redirect(tParentTerm)
  972.                 term.resetTerm = nil
  973.                
  974.             end
  975.             --Set the remote term(s) cursor position
  976.             term.setCursorPos(term.getCursorPos())
  977.             --We'll let the function error, but not yet
  978.             ok, err = pcall(func, tQueue)
  979.             --Send the exit command
  980.             table.insert(tQueue, {command="exit"})
  981.             --Wait for any remain queued commands to be sent
  982.             while #tQueue > 0 do yield() end
  983.             --Reset the term if someone hasn't already
  984.             if term.resetTerm then
  985.                
  986.                 term.resetTerm()
  987.                
  988.             end
  989.             --Now let the error be raised
  990.             if not ok then
  991.                
  992.                 error(err, 0)
  993.                
  994.             end
  995.            
  996.         end,
  997.         asPacketDataS(
  998.            
  999.             tTemplatePacket.senderType,
  1000.             tTemplatePacket.recipientName,
  1001.             tTemplatePacket.recipientID,
  1002.             "termCommand"
  1003.            
  1004.         ), bMultiPacketMode
  1005.     )
  1006.    
  1007. end
  1008. --Receives events and queues them. A table containing functions which receive a table containing an event and return a table containing a modified version may be
  1009. --provided. These may return the event as is, modify it as needed, or return nil to cancel the event
  1010. function receiveEvents(func, tSamplePacket, tEventModifiers)
  1011.     --Default function just queues any even it receives
  1012.     local func = func or function(tEvent) os.queueEvent(unpack(tEvent)) return true end
  1013.     local tSamplePacket = tSamplePacket or {}
  1014.     local tEventModifiers = tEventModifiers or {}
  1015.     checkType(func, "function", "standard_error")
  1016.     checkType(tSamplePacket, "table", "standard_error")
  1017.     checkType(tEventModifiers, "table", "standard_error")
  1018.    
  1019.     for k, v in pairs(tEventModifiers) do
  1020.        
  1021.         checkType(v, "function", function(gVar, gExpectedType, gType, nLevel) error("All elements in the tEventModifiers table must be functions", nLevel) end)
  1022.        
  1023.     end
  1024.    
  1025.     local nativePullEvent = os.pullEventRaw
  1026.    
  1027.     os.pullEventRaw = function(...)
  1028.        
  1029.         while true do
  1030.             --Get a copy of the event
  1031.             local tEvent = copyTable({nativePullEvent(...)})
  1032.             --If there is a modification function, set the event to its call with the first copy as its arguments
  1033.             if tEventModifiers[tEvent[1]] then
  1034.                
  1035.                 tEvent = tEventModifiers[tEvent[1]](tEvent)
  1036.                
  1037.             end
  1038.            
  1039.             --If the modifications function returned something other than a table, usually nil, we assume it wants us to void this event and wait for another one
  1040.             if checkType(tEvent, "table") then
  1041.                
  1042.                 return unpack(tEvent)
  1043.                
  1044.             end
  1045.            
  1046.         end
  1047.        
  1048.     end
  1049.     --Resets os.pullEventRaw to what it was before we got to it
  1050.     os.resetOsTable = function()
  1051.        
  1052.         os.pullEventRaw = nativePullEvent
  1053.         os.resetOsTable = nil
  1054.        
  1055.     end
  1056.    
  1057.     ok, err = nil
  1058.    
  1059.     upcallL(2, packetListener, function(tEvent)
  1060.            
  1061.             ok, err = pcall(func, tEvent)
  1062.            
  1063.             if ok then
  1064.                
  1065.                 return err
  1066.                
  1067.             end
  1068.            
  1069.             return false
  1070.            
  1071.         end,
  1072.         asPacketData(
  1073.            
  1074.             tSamplePacket.senderType,
  1075.             tSamplePacket.senderName,
  1076.             tSamplePacket.senderID,
  1077.             tSamplePacket.recipientName,
  1078.             tSamplePacket.recipientID,
  1079.             "remoteEvent",
  1080.             function(mData) return checkType(mData, "table") end
  1081.            
  1082.         )
  1083.     )
  1084.     --Reset the os table if someone hasn't already
  1085.     if os.resetOsTable then
  1086.        
  1087.         os.resetOsTable()
  1088.        
  1089.     end
  1090.    
  1091.     if not ok then
  1092.        
  1093.         error(err, 0)
  1094.        
  1095.     end
  1096.    
  1097. end
  1098. --Sends events to a remote computer. A table containing functions which receive a table containing an event and return a table containing a modified version may be
  1099. --provided. These may return the event as is, modify it as needed, or return nil to cancel the event
  1100. function sendEvents(tTemplatePacket, tEventModifiers, bMultiPacketMode)
  1101.    
  1102.     tEventModifiers = tEventModifiers or {}
  1103.     checkType(tTemplatePacket, "table", "standard_error")
  1104.     checkType(tEventModifiers, "table", "standard_error")
  1105.    
  1106.     upcallL(2, packetSender, function(tQueue)
  1107.            
  1108.             while true do
  1109.                
  1110.                 local tEvent = copyTable({os.pullEventRaw()})
  1111.                
  1112.                 if tEventModifiers[tEvent[1]] then
  1113.                    
  1114.                     tEvent = tEventModifiers[tEvent[1]](tEvent)
  1115.                    
  1116.                 end
  1117.                
  1118.                 if checkType(tEvent, "table") then
  1119.                    
  1120.                     table.insert(tQueue, tEvent)
  1121.                    
  1122.                 end
  1123.                
  1124.             end
  1125.            
  1126.         end,
  1127.         asPacketDataS(
  1128.            
  1129.             tTemplatePacket.senderType,
  1130.             tTemplatePacket.recipientName,
  1131.             tTemplatePacket.recipientID,
  1132.             "remoteEvent"
  1133.            
  1134.         ), bMultiPacketMode
  1135.     )
  1136.    
  1137. end
  1138. --Tests if an event table contains the EventModified flag
  1139. local function eventModified(tEvent)
  1140.    
  1141.     for k, v in pairs(tEvent) do
  1142.        
  1143.         if v == "EventModified" then
  1144.            
  1145.             return true
  1146.            
  1147.         end
  1148.        
  1149.     end
  1150.    
  1151.     return false
  1152.    
  1153. end
  1154. --This function returns nil on any even which hasn't been modified, but just returns the event otherwise. The return result will always be nil, which will make the event
  1155. --it processes void. This may be used as an event modifier
  1156. function eventVoider(tEvent)
  1157.    
  1158.     if eventModified(tEvent) then
  1159.        
  1160.         return tEvent
  1161.        
  1162.     end
  1163.    
  1164. end
  1165. --When called, this creates a function which swaps out the name of the event for whatever the specified new name is. The return value may be used as an event modifier
  1166. function createEventSwapper(sNewName, ...)
  1167.    
  1168.     local tArgs = {...}
  1169.    
  1170.     return function(tEvent)
  1171.        
  1172.         if not eventModified(tEvent) then
  1173.            
  1174.             tEvent[1] = sNewName
  1175.            
  1176.             for k, v in pairs(tArgs) do
  1177.                
  1178.                 table.insert(tEvent, v)
  1179.                
  1180.             end
  1181.            
  1182.         end
  1183.        
  1184.         return tEvent
  1185.        
  1186.     end
  1187.    
  1188. end
Advertisement
Add Comment
Please, Sign In to add comment