Skip to content

Frequently asked questions

Indraneel Mahendrakumar edited this page Jul 25, 2022 · 1 revision

How can paths to mod directories be found?

Most games that support modding have a specific "mods directory" where mods must be placed - the game will then search through all mod directories under this directory and load mods from them. This could be a path under user://, so that users can add and remove mods as they wish; eg. user://Mods.

In addition, official DLCs and patches may be present under a different directory (but not always) - usually to separate them from user-created content and prevent users from simply copying over the DLC files. This could be a path under res://; eg. res://DLCs.

As an example, here is how directory paths of all mods under user://Mods could be found:

using Godot;

string modsDirectory = ProjectSettings.GlobalizePath("user://Mods"); // Convert user://Mods to a native OS path
string[] modDirectories = System.IO.Directory.GetDirectories(modsDirectory); // Get paths of all mod directories under user://Mods

Note that due to the way assembly and XML document loading works in C#, these paths must be expressed as native OS paths. Meaning that Modot's ModLoader class will not recognise paths beginning with user:// or res:// - they must first be converted to native OS paths using ProjectSettings.GlobalizePath(path) or something similar, as shown in the example above.

How can mods from res:// paths be loaded?

Modot requires that all paths passed to it must be native OS paths. However, Godot's documentation states that paths beginning with res:// may not actually have a native OS equivalent, as the application may be compressed into a .pck file or present in a "virtual" filesystem. This means that loading mods from any path under res:// is technically impossible.

However, there is a workaround - at runtime, these mods' files can simply be copied over into a corresponding user:// path. Godot's documentation guarantees that user:// paths will always be writable to and have a native OS equivalent. Modot offers some convenient extension methods to copy data from one Godot directory to another, and get the paths of sub-directories and files inside a Godot directory - these are very similar to C#'s own filesystem API.

As an example, here is how all mods under res://Mods could be loaded:

using Godot;
using Godot.Utility.Extensions;

using Directory directory = new(); // Create a new Godot directory
directory.CopyContents("res://Mods", "user://Mods", true); // Copy all directories and files recursively from res://Mods to user://Mods

string modsDirectory = ProjectSettings.GlobalizePath("user://Mods"); // Convert user://Mods to a native OS path
string[] modDirectories = System.IO.Directory.GetDirectories(modsDirectory); // Get paths of all mod directories under user://Mods

ModLoader.Load(modDirectories); // Load all mods under user://Mods, which now also includes mods from res://Mods

How can mods access the scene tree?

Mods can annotate static methods in their assemblies with the [ModStartup] attribute to execute their code when they are loaded.

Since these methods must be marked static, they will not have any access to instance methods or information - meaning they cannot use functions on nodes like GetTree(). References to the scene tree must be obtained through other ways, such as setting a static property in the main scene before loading mods, or by using Engine.GetMainLoop().

For example, here is how the scene tree could be obtained using Engine:

using Godot;
using Godot.Modding;

[ModStartup]
private static void OnStartup() // This static method exists in a mod's assembly and will be executed when all mods are loaded
{
    SceneTree sceneTree = (SceneTree)Engine.GetMainLoop(); // Get a reference to the scene tree
    // Do something with the scene tree here...
}

What happens if a mod is not loaded?

M mod may not be loaded for various reasons - invalid metadata files, missing dependencies, present incompatibilities, etc. In such cases, the mod sorting process begins again - taking into account the fact that there may be other mods that depend on the mod that just failed to load. A message containing details about the issue is also logged to the log file (user://Log.txt by default).

For example, take three mods A, B, and C, where C has A as a dependency. ModLoader will try to load all three of them, but if A fails to load, it'll try to load B and C. B will load without any issues, but C will not be loaded as it has A as a dependency and A could not be loaded. Therefore B is the only mod that will be loaded at the end.

What happens if an exception is thrown by a mod startup method?

Modot only catches exceptions that arise due to invalid metadata or other data definition-related reasons. It does not attempt to catch exceptions thrown by mod code, so if a static method annotated with [ModStartup] throws an exception, the application will crash.

Any unhandled exceptions thrown this way will usually (but not always) be logged to the log file (user://Log.txt by default).