Dependencies

Geode provides utilities for mods to depend on other mods, to make sharing code easy.

Note that Geode only manages dependencies that are mods. Normal C++ dependencies, like a JSON parsing library, ZIP library, or some networking utilities, should just be installed like they are in any other C++ project. Use CPM, Git submodules, copy the code to your project, whatever you prefer. If the library is dynamic, include the .dll with your mod through the files key in resources.

However, sometimes you want to depend on code that can’t just be used as a normal library; for example, custom keybinds. Geode does not provide any custom keybinds utilities out-of-the-box, so you need to use a library. However, it would not make much sense if every mod bundled their own incompatible systems for dealing with custom keybinds. Instead, there should be one mod that just adds the functionality, and then other mods can depend on that mod and call its functions to add keybinds.

How dependencies work

Dependency mods are like any other mods, except that they include their headers and linkable files in their .geode package. Usually the headers are located in an include directory inside the package, but they may also be anywhere. The linkable file is mod.id.lib on Windows and the normal mod binary on other platforms, located at the root of the package.

Otherwise, dependency mods are just like normal mods; they may place hooks, patches, add features to the game, and be published on the mods index. However, dependency mods should keep the features they add to a minimum, and be focused on the specific features they’re meant to add. For example, a custom keybinds dependency should only add the necessities for working with custom keybinds; it shouldn’t also add a bunch of other features, like adding more icons or customizing menus.

If a dependency is required, it is linked to; this means that for the mod that depends on it to run, the dependency must be present. However, as sometimes you may want to only use a dependency if it is loaded, dependencies may also be marked optional. In this case, the dependency is not linked to, which means that you can’t use any of the dependency’s functions, but have to use Geode’s functions for working with optional dependencies. See Optional dependencies for more info.

Adding dependencies

Dependencies can be added to your mod by simply adding it to the dependencies key in mod.json:

{
    "geode": "v1.0.0",
    "id": "my.example-mod",
    "name": "My example mod",
    "developer": "Me",
    "version": "v1.0.0",
    "dependencies": [
        {
            "id": "someone-elses.mod",
            "version": ">=v1.2.5",
            "importance": "required"
        }
    ]
}

Dependencies have two required properties: the ID of the dependency and the version depended on. Additionally, the dependency may have an importance, which specifies if the dependency is required or not.

The version field of a dependency may be written as >=version, =version, or <=version. The comparisons work as expected, with the addition that if the major versions are different, the comparison is always false. This means that if you depend on version >=1.2.5 of a mod, version v1.8.0 will be considered acceptable but v2.0.0 will not. For this reason, if you make a mod that is depended upon, you should follow strict semver.

Once you have added a dependency to your mod.json, if you have Geode CLI v1.4.0 or higher, it’s headers are automatically added to your project. If you have the mod installed, the headers from the installed version will be used. If you don’t have the mod installed, Geode will install it from the mods index. The added dependency files for your project can be found in build/geode-deps/<dep.id>. Geode automatically add build/geode-deps to your project’s include path, and links whatever binaries are found in dependencies, meaning you most likely don’t have to configure anything.

Example

The mod hjfod.gmd-api contains utilities for working with .GMD files. You can add it to your mod by adding the following to your mod.json:

{
    "dependencies": [
        {
            "id": "hjfod.gmd-api",
            "version": ">=v1.0.0"
        }
    ]
}

Now, if you reconfigure your CMake (or if you’re using CMake Tools in VS Code, it should be reconfigured automatically), Geode should automatically find hjfod.gmd-api in your installed mods. If the mods index, and install it for you.

#include <hjfod.gmd-api/include/GMD.hpp>
// do things with gmd-api's functions

If you now go to compile and test your mod, everything should work out-of-the-box.

Importance

Possible values:

  • required (default) - Dependency must be installed for this mod to work
  • recommended - Dependency is not required, but is recommended and will be downloaded by default.
  • suggested - Dependency is not required, and will not be downloaded by default.

In case the dependency is not required, it is not linked to, which in turn means that you can’t use any of its exported functions. In cases like these, the dependency should provide ways through Geode to dynamically call its functions, such as through events.

Events

The key system Geode provides for optional mod interop are events. For example, a mod that adds support for drag-and-dropping files on the GD window could define a drag-and-drop event that other mods can then listen to.

Usually however, events are defined in code in a way that requires linking by inheriting from the Event class. To avoid this, mods that want to support being used optionally should also provide events that are specializations of the DispatchEvent class:

using DragDropEvent = geode::DispatchEvent<ghc::filesystem::path>;
using DragDropFilter = geode::DispatchFilter<ghc::filesystem::path>;

// Posting events in source
DragDropEvent("geode.drag-drop/default", "path/to/file").post();

All DispatchEvents have an associated ID, which is specific for each DispatchEvent specialization. This can be used to differentiate between events; for example, the drag drop API might use this to let dependencies determine which file types they listen to.

Mods that use the dependency can now listen for drag-and-drop events:

$execute {
    new EventListener(+[](ghc::filesystem::path const& path) {
        log::info("File dropped: {}", path);
        return ListenerResult::Propagate;
    }, DragDropFilter("geode.drag-drop/default"));
};

An example of using dispatch events in practice can be found in MouseAPI.

Attributes

One way for mods to communicate with optional dependencies is through node attributes, which may contain any data. These are like the setUserData and setUserObject functions native to CCNodes, except that attributes have a string key associated with them. For example, a mod that adds scrollbars to layers might use the following check to see if a scrollbar should be added to a layer:

if (layer->getAttribute<bool>("hjfod.cool-scrollbars/enable")) {
    // add scrollbar
}

Other mods can set this attribute on their layers with the CCNode::setAttribute function.

Mods can also add an event listener to listen for when attributes are added/changed:

$execute {
    new EventListener<AttributeSetFilter>(
        +[](AttributeSetEvent* event) {
            addScrollbar(event->node);
        },
        AttributeSetFilter("hjfod.cool-scrollbars/enable")
    );
}