This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Dave-ee Jones's profile picture

[Trick] Event spoofing

Started by Dave-ee Jones, 18 September 2017 - 01:31 AM
Dave-ee Jones #1
Posted 18 September 2017 - 03:31 AM
Part 1 - Using the 'char' and 'key' events in an event queue
SpoilerThis is an example of running multiple shell programs in a sequence by calling one 'alias' program. It will show you how to use the 'char' and 'key' events to do so.

I can already tell a few of you are like "what in the world..", as you may not understand what this I'm talking about. Don't worry, I only make the title so long because I (strangely enough) cannot think of a clever name for this kind of program.

I haven't seen any programs made quite like this, but it's a simple and interesting concept I've quickly thought up that could help some of you who don't like to use "shell.run(…)" 5 times in a row (or something like that). Think of it as an alias for multiple programs.

This program allows you to run ANY NUMBER of programs in a preset sequence, allowing you to call a (virtually) infinite number of shell programs by using one program. Pretty neat, pretty nifty, pretty useless, if you don't need it. Could be useful for those people who have multiple startup files that they need to rotate through, or those people who have like 3 OS' that they want to use and they need to do a lot of file moving and renaming etc.

Where's the code already?
Well, I'm going to run through it all so you understand what's going on..(don't worry, there's not much code at all).

First bit of code? The table. And I mean, THE table.
Spoiler

local t_LIST = {
  "time",
  "lua",
  "print('Hello, there!')",
  "exit()"
}
Some may have guessed already but this table holds all the commands you want to do. Some of those some may have guessed that this is the way it runs the programs in sequence (top to bottom, 0 to .. whatever). Very, very few would have guessed how you actually run those programs. Well, strictly speaking, you don't, as this is where CC's backend takes over (well, not quite here but later on).

Last bit of code? (Wow, already?!)
Spoiler

for i=1,#t_LIST do
  for n=1,#t_LIST[i] do
	os.queueEvent("char",string.sub(t_LIST[i],n,n)
  end
  os.queueEvent("key",keys.enter)
end
So some of you may now have realised how easy this is, and the rest of you are probably wondering "..what just happened?". Well, let me explain.

"os.queueEvent(…)" is (as the name suggests) a function that when called queues an event. Whenever you press a button it queues both a "key" event and a "char" event (this can be seen more clearly with my Event Logger, EMAL), so all we need to do to make the shell think that we are entering all those commands is queue a bunch of char/key events. Keep in mind the differences between "char" and "key". "Char" refers to the character that relates to the key, so whenever I want to queue a key event for a letter I use the "char" event. Whenever I want to queue a key that isn't a letter, e.g. Space, Enter, Left Alt, 0, 1, 2 etc., I use the "key" event.

Whole code:
Spoiler

local t_LIST = {
  "time",
  "lua",
  "print('Hello, there!')",
  "exit()"
}

for i=1,#t_LIST do
  for n=1,#t_LIST[i] do
	os.queueEvent("char",string.sub(t_LIST[i],n,n)
  end
  os.queueEvent("key",keys.enter)
end

Part 2 - Adding different events
SpoilerSo we've discussed 'key' and 'char' events, creating a table that is run through to queue a bunch of key presses.

So as you may have realised os.queueEvent(…) can spoof any event that your heart desires, and more. You can create custom events as well. E.g.

os.queueEvent("dummy",true)
local event, arg = os.pullEvent()
print(event, arg)
# Output: dummy, true
But what about other events, like maybe a spoofing a floppy drive being removed or added, mouse events (clicking, releasing, dragging etc.), pastes and even redstone changes. Well, yes, you can queue those events and make programs think that a user has just clicked on a button on the screen when they actually didn't.

Well, to do all these we're going to need to make some changes to our code because at the moment it can only handle 'char' events. So, first of all, let's change up the table.
Spoiler

local t_LIST = {
  [1] = {
eventType = "char",
eventArg = "lua",
endWithKey = keys.enter
  },
  [2] = {
eventType = "char",
eventArg = "print('Hello World!')",
endWithKey = keys.enter
  },
  [3] = {
eventType = "char",
eventArg = "exit()",
endWithKey = keys.enter
  },
  [4] = {
eventType = "char",
eventArg = "eventchecker",
endWithKey = keys.enter
  },
  [5] = {
eventType = "mouse_click",
eventArg = { 1, 2, 2 },
endWithKey = false
  }
}

So you may notice the general layout of this table has changed..completely. This is because we need to tell the for loop more than just a string to write now. We need to also tell it what kind of event it's going to queue, what the arguments are (we don't want to queue a string for a mouse click do we?), and if we want to end with a key or not (just in case it is a "char" event with a string that needs to be entered into an input box).

Notice how the "mouse_click" event (number 5) has a table as an argument. This is because the 'mouse_click' event returns 3 arguments. The button clicked and the X and Y positions. So this particular event has the button set to '1' (left button clicked), X is 2 and Y is 2. So it clicked in at position (2,2) with the left button. Easy. Now we just need to tell the for loop to handle this as such.
Spoiler

for i=1,#t_LIST do
	if type(t_LIST[i].eventArg) == "table" then
		os.queueEvent(t_LIST[i].eventType,unpack(t_LIST[i].eventArg))
	else
		os.queueEvent(t_LIST[i].eventType,t_LIST[i].eventArg)
	end
	if t_LIST[i].endWithKey then
		os.queueEvent("key",t_LIST[i].endWithKey)
	end
end

So you may notice that we've gone from 2 for loops to 1, making the general flow a bit faster. What it's doing is it's going through each table in the 't_LIST' table. On line 2 we can see that it's checking if the 'eventArg' variable is a table or not. If it is, we need to unpack the table so that 'os.queueEvent(…)' can have all the arguments lined out as if it was like so:

os.queueEvent("mouse_click",unpack({1,2,2})) => os.queueEvent("mouse_click",1,2,2)

However, we need to make sure it doesn't unpack other 'types', like strings or numbers, so we have to put it in an if statement for the other 'case'. After this, we need to check if there is an 'endWithKey' key set. If there is, we can queue a 'key' event with that specific key as the argument. If it isn't set (refer to the "mouse_click" event table) and it's set to either 'false' or 'nil' then it won't press any key, or even queue the event.

It's as easy as that. It is a bit confusing to think that it's not running any programs before the program even finishes, but just remember it works.

Whole code:
Spoiler

local t_LIST = {
  [1] = {
	eventType = "char",
	eventArg = "lua",
	endWithKey = keys.enter
  },
  [2] = {
	eventType = "char",
	eventArg = "print('Hello World!')",
	endWithKey = keys.enter
  },
  [3] = {
	eventType = "char",
	eventArg = "exit()",
	endWithKey = keys.enter
  },
  [4] = {
	eventType = "char",
	eventArg = "eventchecker",
	endWithKey = keys.enter
  },
  [5] = {
	eventType = "mouse_click",
	eventArg = { 1, 2, 2 },
	endWithKey = false
  }
}

for i=1,#t_LIST do
	if type(t_LIST[i].eventArg) == "table" then
		os.queueEvent(t_LIST[i].eventType,unpack(t_LIST[i].eventArg))
	else
		os.queueEvent(t_LIST[i].eventType,t_LIST[i].eventArg)
	end
	if t_LIST[i].endWithKey then
		os.queueEvent("key",t_LIST[i].endWithKey)
	end
end

Disadvantages of using the event queue to call programs:
  • Queuing a program that uses "os.pullEvent()" will steal the next event in the event queue. So if, for example, you use a program that calls "os.pullEvent()" once, and you've told this program to run the event-pulling program and then "time" after it, it may only enter something like this:
  • 
    ime
    
    Where the event-pulling program steals the "char" event that has the argument "t". Keep in mind if you call a program that has a while-loop with "os.pullEvent()", it will keep stealing your events in the event queue until the program finishes. Not cool, I know. What is cool about it is you can pass events to that program - so if you know it's going to loop with "os.pullEvent()" you can queue specific events (e.g. mouse clicks for UIs, key presses etc. to navigate through a menu without you having to do anything), so it's cool in that sense.
  • Queuing a program that uses "sleep()" will completely wipe the event queue. This is because "sleep()" wipes the event queue. Pretty annoying, I know.
Edited on 19 September 2017 - 11:45 PM
CLNinja #2
Posted 18 September 2017 - 06:51 AM
I think this is kind of pointless, you can just use os.run({},program).


local t_LIST = {"time"}
for k,v in pairs(t_LIST) do
	 os.run({},v)
end
print("hello, there!")

You can just do that and shorten your code drastically instead of running time, running lua, then queuing an unnecessary amount of events to do something that takes 1 line.

or instead of queuing events, do this:


load(string,name,"t",{})()
-- String being the code to run
-- Name being the name that would show up in errors
-- "t" being the mode (only t is supported by CC, which is text)
-- {} being the environment
Edited on 18 September 2017 - 04:56 AM
Dave-ee Jones #3
Posted 18 September 2017 - 07:29 AM
I think this is kind of pointless, you can just use os.run({},program).


local t_LIST = {"time"}
for k,v in pairs(t_LIST) do
	 os.run({},v)
end
print("hello, there!")

You can just do that and shorten your code drastically instead of running time, running lua, then queuing an unnecessary amount of events to do something that takes 1 line.

or instead of queuing events, do this:


load(string,name,"t",{})()
-- String being the code to run
-- Name being the name that would show up in errors
-- "t" being the mode (only t is supported by CC, which is text)
-- {} being the environment

My main goal was showing how powerful the os.queueEvent(..) can be, and also showing that you can make an alias program that can run multiple programs in a set sequence. Also, your way of doing it doesn't display the shell or the commands shown in the shell. Also, argument handling is more difficult in your programs, whereas mine can be done in a single string. E.g:

edit test

AND the way your code is set out means it cannot be interact with programs or queries in the command line. E.g:

lua
print('test')
exit()
This runs the lua command prompt, writes print('test') in it and exits.

Not to mention it queues all the char events instantly which means the program exits immediately, the char events being pulled from a large queue, which is where you can interact with programs you've just run without actually doing anything with the computer. And if you run a program that queries you in the command line you can preset answesr. E.g:

myprogram
"Would you like to download the newest update? "
no
"Launching.."

You could also get the table to take keys as well so you can do cool little things like this:

edit test
while true do
  print('hey')
  sleep(1)
end
ctrl
s
ctrl
e
test
That will run the program created by this program. Try doing that with 'os.run(..)' or 'shell.run(…)' (Yes I know you can do it with 'load', but as my examples show it is far more flexible).

Hopefully you get my point of tutorialing. :)/>
While your ways work, and work well, they may not suit what other people are looking for. Besides, this tutorial is about what's in this tutorial. If I wanted to make a tutorial on 'load' or 'os.run()' I would. :P/>
SquidDev #4
Posted 18 September 2017 - 08:14 AM
While your ways work, and work well, they may not suit what other people are looking for. Besides, this tutorial is about what's in this tutorial. If I wanted to make a tutorial on 'load' or 'os.run()' I would. :P/>
Perhaps it would be better to call it "how to spoof input", as it isn't a great way to just run multiple commands - shell.run works fine for that. It might be worth also queuing the "paste" event instead of one "char" event for each character.
Dave-ee Jones #5
Posted 19 September 2017 - 01:39 AM
While your ways work, and work well, they may not suit what other people are looking for. Besides, this tutorial is about what's in this tutorial. If I wanted to make a tutorial on 'load' or 'os.run()' I would. :P/>
Perhaps it would be better to call it "how to spoof input", as it isn't a great way to just run multiple commands - shell.run works fine for that. It might be worth also queuing the "paste" event instead of one "char" event for each character.

I prefer 'event spoofer', as you could spoof basically any event with a few code changes. This was an example of how event spoofing can be useful.

In terms of the 'paste' event, what about those programs that have custom read() functions, or they just ignore 'paste' events? While the event queue may get a bit more clogged it's still better in terms of versatility. I've also noticed that if you use an emulator it sometimes pastes twice, which I find frustrating.
CLNinja #6
Posted 19 September 2017 - 01:58 AM
While your ways work, and work well, they may not suit what other people are looking for. Besides, this tutorial is about what's in this tutorial. If I wanted to make a tutorial on 'load' or 'os.run()' I would. :P/>
Perhaps it would be better to call it "how to spoof input", as it isn't a great way to just run multiple commands - shell.run works fine for that. It might be worth also queuing the "paste" event instead of one "char" event for each character.

I prefer 'event spoofer', as you could spoof basically any event with a few code changes. This was an example of how event spoofing can be useful.

In terms of the 'paste' event, what about those programs that have custom read() functions, or they just ignore 'paste' events? While the event queue may get a bit more clogged it's still better in terms of versatility. I've also noticed that if you use an emulator it sometimes pastes twice, which I find frustrating.

I've never had an emulator paste twice. Thats an issue you may want to bring up to the developer. Also, your point with the programs that dont allow you to paste is null and void to the point you were making with the title of the post. "run programs in sequence" You can do that simply with an os.run call in a for loop, or using load like i showed you before hand. If you're relying on the event queuing system to type in other programs, then yes its the best way to go, but your post title does not state that intent.
Dave-ee Jones #7
Posted 19 September 2017 - 02:13 AM
While your ways work, and work well, they may not suit what other people are looking for. Besides, this tutorial is about what's in this tutorial. If I wanted to make a tutorial on 'load' or 'os.run()' I would. :P/>
Perhaps it would be better to call it "how to spoof input", as it isn't a great way to just run multiple commands - shell.run works fine for that. It might be worth also queuing the "paste" event instead of one "char" event for each character.

I prefer 'event spoofer', as you could spoof basically any event with a few code changes. This was an example of how event spoofing can be useful.

In terms of the 'paste' event, what about those programs that have custom read() functions, or they just ignore 'paste' events? While the event queue may get a bit more clogged it's still better in terms of versatility. I've also noticed that if you use an emulator it sometimes pastes twice, which I find frustrating.

I've never had an emulator paste twice. Thats an issue you may want to bring up to the developer. Also, your point with the programs that dont allow you to paste is null and void to the point you were making with the title of the post. "run programs in sequence" You can do that simply with an os.run call in a for loop, or using load like i showed you before hand. If you're relying on the event queuing system to type in other programs, then yes its the best way to go, but your post title does not state that intent.

1. I've already explained the differences between the two ways, so I'm not going to bother explaining it again.
2. There's no need to be so rude.
3. There are lots of programs that overwrite the read() function. The way I'm doing it is more versatile and 'safe' in terms of it will work for anything without worries of programs requiring extra input. Read() functions will always require key presses, and since this is based on events being key presses it will work no matter what, unless the event queue is wiped. However, if I used the 'paste' method and someone wanted to run a program that had a custom read() function (very common), it would more than likely not work. It's just a matter of versatility and usefulness. While this program may not look useful to you, it doesn't mean it's not useful to others so there is no need to be so rude and arrogant saying that other programs work better than this one. I've already told you, I'm aware os.run(..) works fine, and it will more than likely work better for you, but it doesn't mean it's the only option.
4. I also said that I couldn't think of a good name for this thread, now I have (thanks to SquidDev).
Dave-ee Jones #8
Posted 19 September 2017 - 02:39 AM
Split the tutorial into 2 parts to allow for a 'beginner' section and an 'advanced' section. Beginner being introduced to simple tables, the event queue (limited to 'key' and 'char' events) and for loops, the advanced being introduced to more advanced tables, all events (and custom events) and referencing the table/s.

EDIT: Just had a thought. If you really wanted to spoof a floppy disk being put in a disk drive you could create the "/disk" directory then pull the "disk" event. Then the program could write what it would to the disk to the "/disk" directory instead. Little tricky thought..
Edited on 19 September 2017 - 12:52 AM
CLNinja #9
Posted 20 September 2017 - 09:26 PM
Split the tutorial into 2 parts to allow for a 'beginner' section and an 'advanced' section. Beginner being introduced to simple tables, the event queue (limited to 'key' and 'char' events) and for loops, the advanced being introduced to more advanced tables, all events (and custom events) and referencing the table/s.

EDIT: Just had a thought. If you really wanted to spoof a floppy disk being put in a disk drive you could create the "/disk" directory then pull the "disk" event. Then the program could write what it would to the disk to the "/disk" directory instead. Little tricky thought..

Yup. You can do a lot of fancy stuff using event spoofing. Its sort of how Justync's rednoot works (putting a modem on a face and overwriting the functions returned) and also how me and my friend Scooptas OS, VorbaniOS works, to create device files instead of sides for peripherals.

It hijacks coroutine.yield and pullEvent to grab the required information and pass it to the user.