Skip to content

Core API

Tom_XV edited this page Sep 19, 2026 · 7 revisions

English | 日本語

The core plugin, DragNWash.ModFramework.dll, namespace DragNWash.ModFramework.

[BepInDependency(ModFramework.Guid, BepInDependency.DependencyFlags.HardDependency)]

ModFramework

Member What it does
const string Guid "com.tomxv.dragnwash.modframework", to depend on
const string Version The framework's version
bool IsReady True once the framework has finished starting
event Action Ready Raised once when the framework has started. A handler added later runs immediately
void Register(ModInfo info) Tells the Mods screen about your mod. Registering the same GUID again replaces it
void AddModsPage(ModsScreenPage page) Adds a custom page to your mod's details, opened with a button

ModInfo

Property Meaning
Guid Your BepInEx GUID. Required
DisplayName Name for players; may hold spaces and punctuation. Defaults to the BepInEx name
Description One or two sentences
Authors string[]
Website Project or download page
UpdateRepository Your GitHub repository as "owner/name" (core 1.1.0 and later). Once a day the core reads its latest release and, when it is newer than the installed version, the Mods screen tags the mod Update with a button to the release page. Tag releases with the plugin's version (v1.2.0 or 1.2.0); drafts and pre-releases are never offered. Players can switch checking off
IsLibrary True for a prerequisite other mods build on. The Mods screen tags it Library, lists the mods that need it, and asks before it is switched off
Icon A Texture2D, ideally square. Create it in Awake (see Assets). Wins over IconPath
IconPath A PNG or JPG file loaded when you register. Register from Awake
Network NetworkUse[]: every host your mod connects to (core 1.2.0, experimental). See Going online

ModsScreenPage

For settings that do not fit BepInEx config entries.

ModFramework.AddModsPage(new ModsScreenPage
{
    Guid = MyMod.Guid,
    Title = "Statistics",
    Build = panel =>
    {
        // panel is a RectTransform, cleared before each call.
        // Build it with UnityEngine.UI and TextMeshPro components.
    },
});

GameOptions

Rows in the game's own Options screen. They follow the game's flow: changing a row previews it and shows the game's Save button, Save keeps it, Back without saving returns to the saved value, and Set Default uses the default you give. Labels and choices are in English; translation mods translate them like any other UI text.

GameOptions.AddChoice(new OptionsChoice
{
    Id = "com.example.mymod.difficulty",   // unique; adding the same id again is ignored
    Label = "Difficulty",
    Choices = new[] { "Relaxed", "Normal", "Messy" },
    Section = OptionsSection.Gameplay,       // Gameplay (default), Audio or Graphics
    DefaultIndex = 1,                        // -1 leaves the row alone on Set Default
    GetSaved = () => _difficulty.Value,
    Save = index => _difficulty.Value = index,
    Preview = index => ApplyDifficulty(index), // optional, called for every shown change
});

GameOptions.AddToggle("com.example.mymod.fastmode", "Fast Mode",
    getSaved: () => _fastMode.Value,
    save: value => _fastMode.Value = value,
    preview: null, section: OptionsSection.Gameplay, defaultValue: false);

// After changing the value somewhere else (your own menu, a config file edit):
GameOptions.Refresh("com.example.mymod.fastmode");

AddChoice can be called at any time; the row appears once the game's settings exist. It throws ArgumentException without Id, Label, at least two Choices, GetSaved and Save.

Services

How a library offers an API to other mods without them reaching into its internals.

// In the library (publish IScoreService in its DLL):
Services.Register<IScoreService>(new ScoreService(), new Version(1, 2), MyLibrary.Guid);

// In a mod that has a BepInDependency on the library, from Awake:
var scores = Services.Get<IScoreService>();

// In a mod without a hard dependency, or needing a minimum version:
if (Services.TryGet<IScoreService>(out var s, new Version(1, 1))) { /* use s */ }
Services.WhenAvailable<IScoreService>(s => { /* runs now, or when it is registered */ });
Member What it does
bool Register<T>(T implementation, Version version = null, string ownerGuid = null) Registers the provider of T. A second provider for the same T is refused and logged
T Get<T>() The provider, or null
bool TryGet<T>(out T service, Version minimumVersion = null) The provider if it has at least that version
Version GetVersion<T>() The version the provider registered, or null
void WhenAvailable<T>(Action<T> callback) Calls back now or as soon as T is registered

GameHooks

Check that the game code you patch still exists on the running build, so a game update disables one feature instead of crashing the game. Failed checks are logged and shown on the Mods screen as Unavailable on this game build.

// "ScrubController" stands for the game type you patch.
if (GameHooks.Require(MyMod.Guid, "Faster scrubbing", "ScrubController", "Update"))
{
    harmony.PatchAll(typeof(ScrubPatches));
}

// A check you made yourself:
var method = AccessTools.Method("ScrubController:Update");
GameHooks.Require(MyMod.Guid, "Faster scrubbing", method != null, "ScrubController.Update");
Member What it does
bool Require(string ownerGuid, string feature, string typeName, string methodName = null) True when the type (and method) exists. typeName may also be Harmony's "Type:Method" in one string, as the Inspector's Code view copies it (core 1.2.0)
bool Require(string ownerGuid, string feature, bool passed, string detail) Records your own check and returns passed
void Unavailable(string ownerGuid, string feature, string reason) Marks a feature unavailable for another reason (switched off after a crash, refused on this renderer); shown like a failed check (core 1.2.0)
IReadOnlyList<string> UnavailableFeatures(string ownerGuid) Features of a mod that failed a check

DeveloperTools (experimental, core 1.2.0)

One switch for everything meant for mod makers and translators rather than players, off by default: Options → Mods → Drag'n Wash ModFramework → Developer tools. Off, the Tool window stays closed and texture reloading is refused. Keep your own exports, hot reload, debug keys and windows behind it (GUIDE rule 8).

if (DeveloperTools.Enabled) ExportWorkingCopy();
DeveloperTools.WhenEnabled(StartWatchingFiles);   // now, and each time the switch is turned on
DeveloperTools.Changed += () => { if (!DeveloperTools.Enabled) StopWatchingFiles(); };
Member What it does
bool Enabled True while developer tools are on
event Action Changed Raised when the switch changes, from the Mods screen or the config file
void WhenEnabled(Action onEnabled) Runs it now if the tools are on, and again every time they are turned on later

GameEvents and SettingMeta (experimental, core 1.2.0)

  • Game events: GameEvents.OnSceneLoaded, OnSceneUnloaded, OnGameStarted, OnQuitting, each registered with your GUID and run on its own.
  • Settings meta: SettingMeta and SectionMeta in a ConfigDescription's tags order the settings page, hide advanced entries and say when a restart is needed; text values and key bindings are editable on the page.

GameInfo

Member Meaning
string UnityVersion For example 6000.3.14f1
GraphicsDeviceType GraphicsApi The graphics API in use
bool IsDirect3D12 True on Direct3D 12, where textures and fonts must be loaded at startup (see Assets)
OperatingSystemFamily OperatingSystem Windows, Linux (Steam Deck) or MacOSX

Also in the core

  • On/off switches from the Mods screen are applied by BepInEx/patchers/DragNWash.ModFramework.Preloader.dll before plugins load.
  • Conflict detection: after startup the core lists game methods patched by more than one mod in the log and marks those mods on the Mods screen. See Playing well with others.
  • Title screen: the version and the number of loaded BepInEx plugins above the build number.

Clone this wiki locally