Precompiling Lua files & Script integrity

<<< PREV

If you reached this far in the builder’s tutorials (and assuming you did not just skip over the chapters), you’re well aware that Lua scripts are simple plain-text files, which means they can be opened and edited at any time. Even after you’ve finished working on your levels. Even after you have packaged those levels and uploaded them to a hosting site. Even when players are playing your released project… oh dear!

We weren’t born yesterday, were we? We know that for every hundred players that respect the level builder’s vision and play the game as intended, there will inevitably be a few that don’t care for the intended way of playing the game. This has always been the case and could never be entirely prevented. But it has become especially apparent and blatant in recent years, with some players outright removing certain plugin DLL’s from the game folder, because they did not like the changes that the plugin added (and not even considering the fact that they may potentially softlock or break the game by doing so). For this reason, the Plugin integrity check FLEP patch was created. It allows to specify plugins which, if they are missing, will make the game abort. The goal of such a solution is preventing the more defiant players from playing an altered version of the game, without all the plugins in place.

It’s not much of a stretch to expect such players to tamper with Lua script files too, knowing they’re plain text and can be opened and edited even in good ol’ Notepad. Or they may even delete such files, removing the effect from the game altogether. As was mentioned, the effects made possible by this plugin, are not always just for show. For example, a player may decide to open up a script responsible for a damaging projectile, change some numbers and make the projectile heal Lara, instead. Or perhaps another module bestows upon Lara a special magic power for combat, as an alternative to weapons, but the player makes the power insta-kill every single enemy, or give Lara infinite mana to spam the attack. Or a player may just disable a trap by removing the responsible module import in the level script. Plenty of opportunities for unruly players to cheat through your levels, or softlock themselves (and then have the audacity to put the blame on the builder, of all people, for this outcome)!

We would need a way to make this tampering process less straightforward, such that the Lua script files cannot be simply edited in Notepad (or some other text editor of choice), and ensuring that any missing script files prevent the game from being played. Lucky for you builders, there is a solution!


Sections

        1. Two script forms – text or binary
        2. Precompiling scripts
        3. Using the precompilation tool (LuaCompiler)
        4. Enforcing script integrity
        5. Suggested workflow for precompiling scripts

Two script forms – text or binary

I have vaguely alluded that there are actually two different file extensions that Lua script files can have. The first extension is the familiar .lua, the other is the more elusive .luac. You will not notice this second file extension in the dropdown menu of Notepad++ (if you’re using it). This is actually expected, due to how these two file formats differ.

While .lua files are the plain-text script files, editable in any text editor, .luac files are binary files. If you know a thing or two about computers, you know that binary files are not as easy to edit, at least not with a dedicated tool that can parse them. This would mean that converting the script files (both module and level scripts) to binary would make modifying them more of a challenge.

Module scripts as well as level scripts can have either of these two extensions. It is for this exact reason that the require() instruction expects a file name of the module without the extension, likewise the ExtraNG string for the level script. The plugin can read both and allows for either form to be present, in the respective folders!

However, it does prioritize one form over the other. If there are two modules with the same name, but one is in plain-text form with the .lua extension and the other is in binary form with the .luac one, the plugin will prioritize loading the plain-text file. This is particularly relevant in the off-chance that the actual code in both files happens to be different (e.g. the .luac is a more recent iteration than the .lua). A similar thing takes place with level scripts. When the level is loaded, the plugin prioritizes searching for the .lua level scripts first, then the .luac level scripts. There is no particular reason for this prioritizing, but in the edge case that both file types are present with an identical name, we (the plugin devs) simply had to let one take prevalence over the other, and we chose the plain-text file to come first.

Aside from the loading priority, the plugin does not see a difference between a .lua and a .luac file. The contents that is relevant to the plugin remains the same in either file. Due to how the standard Lua runtime is implemented, when it reads plain-text script files, it compiles them to the binary form during runtime regardless. If the file is already in binary form, though, it is removing one extra step that the Lua runtime does when parsing scripts. We say this binary script file is precompiled.


Precompiling scripts

Precompiled scripts, as mentioned, are script files that are already in binary form. These binary files have the .luac extension (the ‘c’ at the end presumably stands for “compiled”). Aside from the loading priority described in the section above, the plugin does not prefer text files over binary files. If anything, the precompiled file is faster to load, as the Lua runtime does not have to compile it first. However, this speed boost due to precompilation is truly negligible and not the main reason to precompile your script files.

The main reason has already been stated – binary files are a lot harder to edit than text files. If you open a .lua in any text editor, you get to see the entire code in text form and make any desired changes to it. If you try to forcibly read a binary file through Notepad++, what you get is a bunch of random, indecipherable characters. This is because the binary file no longer consists of ASCII or Unicode text characters, but of a binary byte sequence. Notably, one cannot prevent still making edits to a file in this form, but it certainly takes more effort and skill not to break anything and corrupt the file in the process. Hopefully, this will discourage all but the most determined players from tampering with script files in a custom level set.

Precompilation is NOT encryption

I have to give a very explicit disclaimer here, which is to clarify that precompilation does nothing to obfuscate the contents of the script file, aside from taking the code from human-readable form (text) to machine-readable form (bytes). It doesn’t perform any sort of encryption algorithm on the script files. This means that you can still take a .luac binary file and go to a website hosting a Lua decompiler, like https://luadec.metaworm.site/. The binary script will be converted back to its text form, missing only some originally used identifiers and comments. A determined player may still use such websites to understand or even “reverse-engineer” the binary file back to a text file, for easy editing.

So why don’t we just implement encryption? Because we, the developers of this plugin, are not in favor of such solutions. As much as we understand the argument for not allowing players to cheat or alter the game in unintended ways, we also do not support entirely forbidding access to information. If someone is willing to put in the effort, they can also cheat by altering the savegame memory or hex-editing the game executable, which is just a step or two further of reversing a Lua file. You can never prevent this completely, and if the players end up ruining their gaming experience in the process, we simply say – it’s their choice.

If you still want encryption, the source code of this plugin is available on GitHub, so you may implement any kind of encryption you want, at your own discretion. However, it will not come from us.


Using the precompilation tool (LuaCompiler)

If you snooped around the contents of the Plugin_ParticleSystem.zip archive, you may have noticed a folder in there, LuaCompiler.

This folder contains a simple, rudimentary tool, LuaCompiler.exe, for generating a precompiled binary (.luac) out of a plain-text Lua file (.lua).

It has a simple drag-and-drop interface (either after launching the program and dropping the .lua files on the window, or directly onto the program EXE in File Explorer). The tool generates corresponding binary files out of the given Lua text files, in the same directory as the input files.

Of course, the end goal is for the binary files to take place of the text files. This way, when the level becomes available to the public, the contents of the modules and level scripts is less likely to be altered. Minding the fact that the plugin prioritizes loading the text files first, one should remove the text versions (.lua) from the levelscripts and modulescripts folders, leaving just the binaries (.luac) behind.

A very important thing to note is that the tool can only do a one-way conversion from text to binary! It cannot convert back from binary to a text file. For this reason, you should make sure that you generate the precompiled scripts at the last stages of preparing your custom level package (right before sending it to a level hosting site or elsewhere), as well as keeping the original text-form script files (modules and level scripts) in a safe place, just in case. Even though in a pinch, it would be possible to partially recover the contents of a script from a binary (for example with the online decompiling tool linked above), the original identifiers as well as any comments may be lost in the process. Perhaps, this has far bigger consequences for module coders (who may lose their entire work) than builders, but still, it’s advisable to play it safe and back everything up just before the final conversion to binary files. The tool does not delete the .lua files by itself, in order to make this process extra safe.


Enforcing script integrity

Precompiling your script files should protect the contents of Lua scripts from being tampered with (well, as much as it’s possible without resorting to full encryption). Unfortunately, precomilation is of no help when players can just go and delete the script files, even in the binary .luac format! The good news is this also has been considered and the plugin provides a solution for it as well.

The plugin has another CUST_ constant included in it. This second constant is CUST_SCRIPT_INTEGRITY, with two possible options: ENABLED or DISABLED.

Customize= CUST_SCRIPT_INTEGRITY, ENABLED ; ENABLED script integrity

Customize= CUST_SCRIPT_INTEGRITY, DISABLED ; DISABLED script integrity
By default, without any of these commands in place, script integrity is disabled and you must enable it explicitly with the CUST_SCRIPT_INTEGRITY command.

What does script integrity do, when enabled? It changes the behavior of the game engine, so that it boots out of the game and displays an error message on screen, if any of the following are missing:
What this means is that if a specific script file is requested and the plugin attempts to load it, but is unable to find it, enabled script integrity forces the game to abort with an error message, informing that a specific level script or module is missing.


Of course, there is a chance for this to happen simply due to the builder misplacing some file (in which case, the script integrity helps the builder to catch it). However, the main goal is to prevent players from playing a game that does not have everything in the right place. This does not necessarily imply malice on the player’s part, but e.g. something going wrong while extracting the playable level from an archive (.zip or .rar), resulting in missing files. The error message notifies which asset is missing, allowing the player to rectify the issue, (reextract the contents, for instance).

The CUST_SCRIPT_INTEGRITY is recommended to be a global setting, meaning it should go in the [Title] section, where other global CUST_ commands can be used. However, it can also be used in a [Level] section in addition to being in [Title], to override the setting for a specific level. This can be useful, for example, if certain levels are done and can have script integrity enforced, but some levels are still work-in-progress and for them, you want to switch off integrity enforcement, while they are being worked on.

Switching on script integrity (globally) for the release version of the TRLE project is strongly recommended, especially if the particle effects influence gameplay or progression in important ways, and removing or misplacing a level script / module can lead to serious bugs or softlocks. That being said, just like script precompilation, it is merely an optional extra measure, which you may or may not take.

Suggested workflow for precompiling scripts

Taking the above instructions and precautions into account, here is a suggested workflow for when to generate precompiled binaries in the level building process:
  1. Create your level script files
  2. Import and customize modules as needed whilst building your levels
  3. Verify everything works as intended and needs no further adjusting
  4. Backup level scripts from levelscripts folder and modules from modulescripts folder!!! (they cannot be recovered from .luac)
  5. Use the LuaCompiler tool on all .lua text files present in BOTH the levelscripts and modulescripts folders
  6. Delete the plain-text .lua versions of the files from levelscripts and modulescripts, leaving just the .luac files behind (this is why the backup is important)
  7. Enable script integrity enforcement with Customize= CUST_SCRIPT_INTEGRITY, ENABLED added to the [Title] section.

Of course, there could be other, perhaps more efficient workflows to prepare a playable package while utilizing this plugin. But whatever you end up doing, you should always back up the text versions of the scripts – there is no easy way to retrieve them once they’re gone!
This chapter concludes the Builder Path! Bravo for making it to the end!

Now, as a builder who wants to use other people’s modules, you know everything you need to know about using the Particle System plugin. If you are curious of how you could make your own modules (even simple ones), consider taking a look at the Coder Path, which shows you how it’s all done! Because you’ve just finished the Builder Path, you have already picked up quite some information about working with the particle plugin. In the process, you have gained a great advantage for trying out module scripting yourself!

Otherwise, thank you for reading the manual and using the Particle System plugin! I hope you enjoy using the new particle effects made possible with its use. If you have any bug encounters or other reports to make, you can make a report on the Issues page of the plugin’s GitHub repository. Thank you in advance for any reported issues!

Fin


<<< PREV