I've always wanted to create my own Computercraft OS without having to modify bios.lua and other files. The problem with that though, is that then I can't change any of the default behavior. The solution however, turned out to be fairly simple, for those of us with a better understanding of how lua works.
function test()
print("testing!")
end
test()
The code above will simply print "testing!" correct? But what's actually happening in the code here?
- We declare the function test()
- We put a body in test()
- We end, and thus finish, test()
- We call test
function test()
print("testing 1!")
end
function testAgain()
print("testing 2!")
end
test = testAgain
test()
Traditionally, you might expect to get an error on the "test = testAgain" line. But in fact, you're simply telling lua that the value of test is now the value of testAgain. So when you call test, you're pointing to testAgain, and testAgain gets run instead, outputting "testing 2!"
So now we can get ourselves into some advanced, dangerous trickery. Let's try messing with the systems APIs.
function test(side)
print("I've stolen a function! Sorry " .. side .. " side, I'm not gonna read you")
return false
end
redstone.getInput = test
truth = redstone.getInput("left")
Here we've chosen redstone.getInput as our "victim." We've replaced it with test! So when you call redstone.getInput("left"), you get the return of false and the output "I've stolen a function! Sorry left side, I'm not gonna read you". Now why exactly is this dangerous? Well it turns out, after your program exits, redstone.getInput keeps pointing to your function. So the next guy's program who tries to call redstone.getInput() is going to be out of luck. He's only going to get false, no matter what, because your function always returns false.
But there's some good in this technique, I swear! Let's imagine I were writing an OS without rewriting any of dan200's files. I've come across a problem that people's programs are printing on the screen when I don't want them to be able to print. I can fix this with function replacement!
function myPrint(msg)
-- calculate condition
condition = conditionFindingFunction()
-- compare to see if printing is allowed
if condition then
--call the backup of print() that get's made before this function is called
printBackup(msg)
end
end
-- backup print to printBackup so that we don't lose the original print
printBackup = print
-- replace print with myPrint
print = myPrint
print("Testing!")
Here we do a more advanced set of replacements. We've got a function called printBackup. We never even declared it as a function, but we can still set its value to print. This ensures that as long as we don't lose or change printBackup(), we won't lose the original print(). Then we set print's value to our own custom function myPrint(). In here we calculate the condition to see if printing is currently allowed, throw it in an if statement, and call the printBackup that was made before this function was ever called. So now there's a system wide change to the print function.
One more application of this, again pertaining to the print function, would be forcing print to print into the text windows an OS might use. A lot of OSes for computercraft create a box out of text and call it a window. They might also make use of some class system (which uses tables to store functions and variables) to have objects represent those windows. Here, we'll assume both of those things are true. Let's try to force print to have different parameters and print out to the window.
function myPrint(msg, window)
if window ~= nil then
window:write(msg)
end
end
print = myPrint
It's important to note here that we're assuming window is a standard window object, which is just a table full of functions and variables. Tables can store functions and you can access them using standard table.function() notation, but if you use a colon instead of a period (as shown here), it will automatically pass in the table object as an argument to the function, and if the function was declared with the colon notation, you can use the self keyword to reference that table passed in an invisible parameter. Otherwise you'd have to accept the table as a normal parameter.
Anyways. So we first check if window is nil. If it is, the programmer was trying to use print the regular way; by using print(msg). There's no specified window so we'll just throw it out and ignore the message entirely. (In a real OS, I wouldn't do this. I'd specify a specific place in which these messages would be logged or printed, but we're not going to spend that much time on this) If the window is not nil, we call the function write(msg) stored in the window object to print the message onto the window however the function specifies it to be printed. Notice that we didn't even back up the old print function. It's practically useless now. Windows are going to be using a custom write function that deals with the term API, and printing is supposed to bring them to our function. So there's no need for the old function.
EDIT: New application I just figured out (with the idea coming from KaoS). What if you want a password protected computer? Well as we all know, ctrl-T will close your password program. Now there's a very well known way around this that prevents the computer from ever terminating a program using ctrl-T.
os.pullEvent = os.pullEventRaw
Most people probably just copied this code into their password programs and forgot about it. But as we now know, it's a function replacement! You're replacing os.pullEvent with os.pullEventRaw. So now whenever ctrl-T is pressed, nothing happens. But This brings up a new problem. Once past your password protection, you can't use ctrl-T to terminate programs that you actually wan s to terminate. The solution to this might be obvious to many by now. But here's my implementation.
pullBackup = os.pullEvent
os.pullEvent = os.pullEventRaw
print("Enter password")
pass = read("*")
if pass ~= "password" then
os.reboot()
else
os.pullEvent = pullBackup
end
First, we backup os.pullEvent. Then we replace it with os.pullEventRaw so that any call to os.pullEvent actually calls os.pullEventRaw, which doesn't automatically handle the ctrl-T events. After that we use pretty standard procedure to read a password and reboot if what was read is not the password or finish the program if it is. But we do something a little different. We set os.pullEvent to pullBackup. This replaces os.pullEvent with the old one so that whenever programs call it in the future, it'll call the original os.pullEvent, which allows for ctrl-T to work..
I hope you've learned something here. I was just thinking about how you can set functions in javascript and wondered if it could be done in lua too. Luckily it can. This could become a wildly important feature of some OSes, but unfortunately a wildly dangerous feature in some malicious programs. I won't explain those though, as I want as low a chance of malicious code as possible. Either way, I'm glad you read this whole thing. Have fun with it. Happy crafting!