Internal scripting

The internal scripting exploit is an exploit within VVVVVV's scripting engine that allows for internal scripting commands to be used, thus giving the user more control than with regular simplified scripting. These commands are the actual direct commands in the game engine itself, and all simplified scripts are translated to internal commands under the hood. Internal scripting is a superset of simplified scripting.

There are two ways to use internal commands: the say(-1) exploit and the load script exploit. The former produces cutscene bars while the latter does not, but the latter requires two scripts while the former does not.

Nowadays, most of the work normally needed to use these exploits can be automated by the internal scripting modes in Ved, but this article will detail exactly how and why they work.

say(-1) method
This is the first exploit discovered, found by FIQ in 2012.

Usage
Usage of the exploit looks like the following. For clarity, gray lines are just the "glue", the rest are the actual internal commands you want to execute.

say(-1) text(1,0,0,4) say(10) text(gray,-500,-500,3) This is the first block of internal scripting! I'm going to play a sound effect. speak endtext delay(30) playef(9) delay(30) text(1,0,0,4) say(9) squeak(player) text(cyan,0,0,1) Wow! That was cool! position(player,above) speak endtext endcutscene untilbars loadscript(stop)

When run, this will play a terminal squeak (despite there being no  in the script), followed by a gray text box, then a sound effect, then Viridian's squeak with a cyan text box created above the player.

The basic format of a script using this exploit can be described as follows:

say(-1) text(1,0,0,4) say(N)  text(1,0,0,4) say(N)  text(1,0,0,4) ... say(N)  text(1,0,0,4) say(N)  loadscript(stop)

The script starts with  and , followed by a series of   blocks. The last line of each  block is always , except for the very last   block, which ends with.

This method will by default produce an extra squeak (depending on if you used the color argument of  or not, or if you used   instead), and depending on the circumstances, it may be unwanted. This squeak can be removed by simply putting  at the top of the script. However, this removes an extra line of parser output per block (see Explanation below), so all  glue lines need to be replaced with.

Any negative number of any magnitude can replace -1 in, but it has to be negative. All s can be replaced with  s. The optional color argument to   doesn't matter. The first three arguments of each  command (if it is a gray "glue" line) don't matter, only that the last argument is 4. However, by convention, the first argument is 1 and the second and third arguments are 0.

In 2.0, the maximum amount of lines you can have in a  command is 5. Starting from 2.1, this limit was raised to 50. When using this exploit (or the other one), you cannot have a  command and its associated   or   span over multiple blocks; both the text box and its  /  must be in the same block of internal scripting.

Explanation
There are two script parsers in VVVVVV - the custom script parser ( in 2.2 and below,   starting from 2.3) and the script runner. The former reads simplified scripts and translates them into internal commands, while the latter reads internal commands and executes them accordingly.

When the above script is fed through the custom parser, this is the result. For clarity, gray lines are again "glue" lines.

cutscene untilbars squeak(terminal) text(gray,0,114,-1) text(1,0,0,4) customposition(center) speak_active squeak(terminal) text(gray,0,114,10) text(gray,-500,-500,3) This is the first block of internal scripting! I'm going to play a sound effect. speak endtext delay(30) playef(9) delay(30) text(1,0,0,4) customposition(center) speak_active squeak(terminal) text(gray,0,114,9) squeak(player) text(cyan,0,0,1) Wow! That was cool! position(player,above) speak endtext endcutscene untilbars loadscript(stop) customposition(center) speak_active endcutscene untilbars

Let's compare the original script and parsed script, along with the reasons for why the parser wrote each line:

The main thing to take note here is the. When the custom parser encounters a  command, it will only read the number of lines given if it is between 0 and 50 (0 and 5 in 2.0). If the number given isn't in that range, then it will default to taking in 1 line.

However, that's not the end of it. The custom parser then translates the  to. When the script finally gets run, the  command has no such range restriction, so it will happily follow the order to take in -1 lines. This ends up being equivalent to taking in 0 lines, because its for-loop conditional ends up evaluating to  and never executes.

In this way, the internal command  is smuggled in and executed. This  ends up taking in the next four lines (,  ,  , and   in as a text box, meaning they're essentially overwritten as they are no longer able to be executed. This then paves the way for the start of the internal script to be executed accordingly.

The same trick with  is used at the end of each block to override the intervening four lines, which in the case of the lines after the first block are ,  ,  , and.

The last block uses  to avoid the extra   at the end of the script, as otherwise an extra unwanted text box would be created onscreen. stops the script by loading a non-existent script; since the main game  script doesn't exist, the script runner sees there are no commands loaded, and stops running the script. However, it is also equally valid to use  instead, as that will skip over the   and   lines, and as a bonus, automatically do the   and   without you having to supply them yourself. But it is, however, convention to use.

In effect, the actual script executed in the end, with all the overwritten lines removed, is the following. The first three lines were not originally in the lines given in the custom script.

cutscene untilbars squeak(terminal) text(gray,-500,-500,3) This is the first block of internal scripting! I'm going to play a sound effect. speak endtext delay(30) playef(9) delay(30) squeak(player) text(cyan,0,0,1) Wow! That was cool! position(player,above) speak endtext endcutscene untilbars

Load script method
This is the second exploit discovered. It was accidentally found by FIQ in 2012, when he was messing around with scripting.

Mixing internal and simplified
While it may seem like a script can only be either "internal" or "simplified", it is possible (but not recommended) to switch between simplified and internal scripting in a single script.

This is not recommended for a couple of reasons:


 * 1) All internal scripting is a superset of simplified scripting, therefore all simplified commands can trivially be translated to internal commands. Once you are in internal scripting, there is no gain from switching back to simplified.
 * 2) In terms of number of lines used, the custom script parser is a bit inefficient in some places with its translations of simplified to internal. If you wish to maintain compatibility with the pre-2.3 500 parser output script lines limit, then you would be better off sticking to internal scripting instead.

While you can transition from load script or say(-1) internal scripting to simplified scripting, you can only use the say(-1) method to jump back in to internal scripting.