Examples of what I mean by modding:
- minecraft mods: add some jar file into your mod folder
- skyrim mods: add some .esp file into your mod folder
- luanti: put some folder with .lua files and config into your .minetest/mods folder
Mods are basically “turing-complete” and can add different types of computation to your application, while integrating with the (GUI) system.
How to design a program that allow for modding?
With interpreted programming languages like python or lua, you can load code as strings at runtime … but is it done that way for these languages (that would be really bad for security)?
eval("print('mod loading ...')")
So roughly how I imagine modding in python to work, a demo in the python repl …
>>> items = {}
>>> newmod = """
... {"name": "holy-mod-sword", "value": 10, "do-damage": lambda x: x.firedamage(1000)}
... """
# loading the mod
>>> items["holy-mod-sword"] = eval(newmod)
>>> items
{'holy-mod-sword': {'name': 'holy-mod-sword', 'value': 10, 'do-damage': <function <lambda> at 0x7f8c710a9d00>}}
is it done that way or similar?
Usually, when games and programs natively support 3rd party mods/plugins its done so through a defined API - a modding API - just a bunch of functions made by the devs that allow you to register new stuff, change/override what already exists, react to events, … Example https://lua-api.factorio.com/latest/
Lua is often used as the language for the mods because it’s really easy to embed into a program (most games are done in compiled languages) and so creates a “sandbox” - you can only really call what the devs make available for the lua scripts.
Eval is bad for security boundaries and the string based approach is a pain to develop and maintain. An alternative that is equally bad for security but better for development would be dynamic imports using importlib.
If you want to support custom scripts while enforcing security boundaries, you could use an embeddable interpreter like lua, or create your own.
I think as soon as you add mods to your application that can bring their own code with them you have a potential security issue. The most secure approach that I have seen is wasm mods/plugins run in a sandbox.
Turing completeness and making functions, which can be used in combination of choice, available to the modder is a security nightmare I guess (basically your game becomes a simple operating system by making it programmable). I heard web-assembly allows for “sandboxing” but that depends on the hook functions that you allow your modder to access as well ….
But web-browsers seem to do it fine with their APIs.
For your
holy-mod-sword
example specifically, the better approach is to specify a structured data format (think Excel sheets) that your game loads from a specific directory at startup and inserts the found items, NPCs etc. into the game’s runtime data structures (e.g. loot tables).For example, Skyrim (and other Bethesda games) do this with .esp-files, which allow you to import a big variety of different game entities. In a small indie game, the format doesn’t have to be anything special; e.g. Elin modders use plain .xlsx files, where the columns are item name, item weight, item sell value, item damage, etc.
The above approach works for static data, such as simple items, but you’ll need more to allow modders to write Turing complete scripts. There are two different approaches to this:
- Expose a language runtime in which the game runs mods. You can design your own language and/or runtime (e.g. Warcraft 3 has JASS), or use existing ones (this is basically what
eval
is, though you’ll need to hide builtins, globals and such by passing extra parameters). These approaches are theoretically safe thanks to the runtime sandbox, but there are often unintended ways to escape the sandbox and execute arbitrary code. - Inject the mod executable directly into the game with tools like BepInEx (Unity) or DLL injection (used by e.g. Skyrim Script Extender). There are no security guard rails, so you’ll need to manually review mods.
TIL about BepInEx thanks! I will have to learn more about DLL injection.
no security guard rails, so you’ll need to manually review mods
I think we all know how this ends in practice … Gamers review all their mods code before playing
- Expose a language runtime in which the game runs mods. You can design your own language and/or runtime (e.g. Warcraft 3 has JASS), or use existing ones (this is basically what