-
-
Notifications
You must be signed in to change notification settings - Fork 31
Making mods for Gale
Gale unpacks mods the same way as r2modman/TMM (see this page on r2modman's wiki). However, Gale installs mods using hard links instead of copying files. This means there will ever only be one copy of your mod on a user's machine. Some folders are excepted from this, and are instead copied for each profile:
BepInEx/configshimloader/cfgReturnOfModding/config
Warning
Never put mutable files outside of these folders, as they will be shared across all of the user's profiles.
Gale follows these rules when installing mods:
- It goes layer-by-layer down the dependency tree, installing the mods further up first.
- If multiple instances of a mod are found, it chooses the first version encountered.
- Mod installation never overrides existing files.
Gale supports all of BepInEx's config API, as long as the content of the resulting file is valid UTF-8. Option names are reformatted to use Sentence case for consistency across mods.
As well as parsing any .cfg files it encounters, Gale also lists files with the following extensions:
.json.toml.yaml.yml.xml.ini
Gale's config editor works quite differently than other mod managers'. Before serving a config file to the user, it reads and parses the entire file into its own internal data format. With every change, the whole file is re-serialized and written to disk. This method lets Gale display options in a rich way, but may result in file being slightly altered, such as added newlines and changed order of options and sections. However, none of this should affect the most commonly used high-level config APIs.
Dropdowns are shown when the option type is an enum, or if you provide an AcceptableValuesList. If the enum type has a [Flag] attribute, it becomes multi-select.
enum Difficulty {
Easy,
Medium,
Hard,
Nightmare
}
file.Bind<Difficulty>("...", "Difficulty", "...", Difficulty.Medium);
// or
file.Bind<string>("...", "Difficulty", "...", "Medium", new AcceptableValuesList<string>("Easy", "Medium", "Hard", "Nightmare"))
Sliders are shown when the option type is a number (int, float, double, e.t.c) and its bounded with a min and max. Step size for integer sliders is 1 and 0.1 for decimal sliders, although any applicable value can be entered in the input field.
file.Bind<int>("MaxPlayers", "...", "...", 16, new AcceptableValueRange<int>(4, 32));
file.Bind<int>("DuckPrice", "...", "...", 100);
file.Bind<float>("DuckDamage", "...", "...", 1.5);
Checkboxes are shown for boolean options.
file.Bind<bool>("AllowFriendlyFire", "...", "...", true);
Text fields are created when option type is string, or a type not normally supported by BepInEx (using custom TomlTypeConverters).
file.Bind<string>("WarningMessage", "...", "...", "Alert");
file.Bind<string>("Rarity", "...", "How often you'll see the scrap on each moon. ListSeparator=,", string.Empty);
file.Bind<string>("Rarity2", "...", "How often you'll see the scrap on each moon, with pipes! ListSeparator=|", string.Empty);
class FruitList {
public List<string> Items;
}
TomlTypeConverter.AddConverter(typeof(FruitList), new TypeConverter {
ConvertToString = (obj, type) => {
var list = (FruitList)obj;
return string.Join(", ", list.Items);
},
ConvertToObject = (str, type) => {
var list = new List<string>();
foreach (var item in str.Split(',')) {
list.Add(item.Trim());
}
return new FruitList { Items = list };
}
});
file.Bind<FruitList>("Fruits", "...", "...", new FruitList() { Items = ["Banana", "Apple", "Orange"] });
Additionally, a button to expand the field into a dialog is shown in any of the following cases:
- The description of the option contains
ListSeparator=X, whereXis any unicode character. - The option is over 100 characters long.
- The option contains a newline (
\n). - The description does not contain a
ListSeparatorspecifier and the option contains a comma (,).
The dialog contains a resizable multiline input as well as a list editor, where the option is split up according to the specified ListSeparator, or , by default.


Orphaned options are created when BepInEx reads an option from a config file, but no mods bind to that option. While Gale preserves them internally, orphaned entries as well as sections and files consisting only of such entries are hidden in the UI.