Documentation / 26.2
Docs/SourbyMods

SourbyMods

ML1 is a native server-side mod format with a declared API generation, an early-boot lifecycle, and one classloader per mod.

SourbyMods is the first-party native mod loader for mods/*.jar. It is intentionally small: no dependency graph, no cross-mod classpath, and no claim of Fabric or Forge compatibility. It exists so server-side extensions can load early, reach NMS and Bukkit when appropriate, and participate in the runtime lifecycle.

What ML1 is

FormatML1
ArtifactA jar in mods/ containing sourbymod.yml at the root
API generation1
AccessNMS, Bukkit/Paper API, and dev.iyanz.sourbycraft.*
IsolationOne URLClassLoader per mod, parent = server classloader
Not supportedFabric or Forge mods; cross-mod dependencies
Not a Fabric or Forge bridgeFabric and Forge mods use different mappings, classloading, and lifecycles. A jar without a sourbymod.yml is skipped with a warning. ML1 does not translate them.

Descriptor

A mod jar contains sourbymod.yml at its root. The loader reads it with SnakeYAML’s safe constructor, so YAML gadget tags are blocked.

sourbymod.yml · minimal
id: example-mod
name: Example Mod
version: 1.0.0
main: com.example.ExampleMod
api: 1
idRequired. Unique identifier. Pattern: [a-z0-9-_]{1,32}.
nameOptional display name. Defaults to the id.
versionRequired. Arbitrary version string.
mainRequired. Fully-qualified class implementing dev.iyanz.sourbycraft.mod.SourbyMod.
apiRequired integer. Currently 1. The loader rejects a newer generation it does not know.

Lifecycle

onLoad runs at bootstrap, before world load and before Bukkit plugins enable. The SourbyCraft config, knobs, class initialization, andctx.registerModule(...) are safe here. Bukkit APIs, worlds, schedulers, and direct registry mutation are not.

onEnable runs when SourbyCraft’s plugin enables. Bukkit is fully available.onDisable runs in reverse order during module shutdown. A same-classloader reload re-enrolls modules; onLoad remains one-time, whileonEnable/onDisable fire once per reload cycle.

ExampleMod.java · skeleton
package com.example;

import dev.iyanz.sourbycraft.mod.ModContext;
import dev.iyanz.sourbycraft.mod.SourbyMod;

public class ExampleMod implements SourbyMod {
    @Override
    public void onLoad(ModContext ctx) {
        ctx.logger().info("loading " + ctx.modId());
    }

    @Override
    public void onEnable() {
        // Bukkit API is available here.
    }

    @Override
    public void onDisable() {
        // Reverse-order shutdown.
    }
}

ModContext

  • modId() — the id from the descriptor.
  • version() — the declared mod version.
  • dataDirectory() — mods/<id>/, created lazily.
  • logger() — a logger prefixed SourbyMod/<id>.
  • registerModule(SourbyModule) — enroll a module into the runtime registry; use this instead of a direct add from onLoad.

Classloading

Each mod gets its own URLClassLoader with the server classloader as parent. A mod can see NMS, Bukkit/Paper, and all SourbyCraft first-party APIs. ML1 has no dependency graph: mod A cannot declare a dependency on mod B. If two mods share code, ship it in both jars or expose it through a Bukkit service.

Loader behavior

  • Duplicate id: the second jar is skipped with a warning.
  • api newer than the loader knows: skipped with a warning.
  • onLoad throws: that mod is disabled; the server and other mods continue.
  • Missing main class or wrong interface: skipped with a warning.
  • No sourbymod.yml (Fabric, Forge, random jar): skipped with a warning.
  • Zero mods: one informational line, no scanning beyond a directory listing.

Security

A SourbyMod is arbitrary code running in-process with full server privileges, the same trust level as a plugin. The loader adds no sandbox; an in-JVM sandbox is not a credible boundary. Deploy only mods from sources you trust.

Continue

sourbyapi covers the plugin API. TheAurora notes explain why early-load code still has to respect region ownership after the server starts ticking.