DEV Community

Alvalens
Alvalens

Posted on

I Fixed a Minecraft Mod by Patching a Java .class File

Minecraft Dawncraft

I was playing DawnCraft when I ran into a strangely specific bug.

The Spellbound Book worked perfectly in singleplayer.

On my dedicated server, it didn't.

The weird part was that the book still applied its Misconduction effect. It was only the actual attacks that stopped working.

That was annoying enough that I stopped trying to fix it from inside the game and opened the mod's JAR instead.

The investigation eventually ended with me editing a compiled Java class file directly.

The complete patch and reproduction tooling are available here:

GitHub: https://github.com/Alvalens/dawncraft-spellbound-server-fix


The number that gave it away

My setup was:

Component Version
DawnCraft 2.0.16_hf
Minecraft 1.18.2
Forge 1.18.2
Illage & Spillage 1.18.2-1.1.3
Server Exaroton dedicated server

The Spellbound Book is supposed to give the player Misconduction, which enables its attacks.

In this particular DawnCraft-bundled version, I found two relevant attacks:

  1. Empty main hand + right-click the top of a block → a line of Imps.
  2. Empty main hand + attack a mob while affected by Misconduction → three Illager Souls.

Singleplayer:

Both worked.

Dedicated server:

Neither worked.

I even granted myself Misconduction directly on the server to rule out the book itself.

Still nothing.

That gave me a useful split:

Spellbound Book
      │
      ├── Apply Misconduction ─────── WORKS
      │
      └── React to player actions ─── BROKEN
Enter fullscreen mode Exit fullscreen mode

So the effect system wasn't the problem.

Something was preventing the attack handlers from running.


Minecraft Inventory

The Spellbound Book in DawnCraft. The item and Misconduction effect worked; only the actual attacks failed on the dedicated server.


Going to the JAR instead of guessing

The relevant mod was:

illageandspillage-1.18.2-1.1.3.jar
Enter fullscreen mode Exit fullscreen mode

A JAR is just a ZIP archive containing compiled Java classes and metadata.

So instead of changing random server settings, I started looking for the code responsible for the Spellbound attacks.

One class immediately stood out:

com.yellowbrossproductions
    .illageandspillage
    .events
    .ModClientEvents
Enter fullscreen mode Exit fullscreen mode

The name was interesting.

ModClientEvents.

I inspected the compiled class with javap.

That's where the two attacks appeared.

The first handler listens for:

PlayerInteractEvent.RightClickBlock
Enter fullscreen mode Exit fullscreen mode

and eventually calls:

EntityUtil.createLineImpsAttack(...)
Enter fullscreen mode Exit fullscreen mode

The second listens for:

LivingAttackEvent
Enter fullscreen mode Exit fullscreen mode

and creates three IllagerSoulEntity instances.

So the implementation matched what I had observed in-game.

The question changed from:

Why doesn't Spellbound work?

to:

Why aren't these event handlers receiving events on the dedicated server?


The smoking gun

I inspected the class-level annotation.

The original class contained:

@Mod.EventBusSubscriber(
    modid = "illageandspillage",
    bus = Bus.FORGE,
    value = {Dist.CLIENT}
)
Enter fullscreen mode Exit fullscreen mode

That was it.

The event subscriber was explicitly restricted to the physical client.

On an integrated singleplayer world, Minecraft is still running a client process alongside its integrated server.

So the class gets registered.

On a dedicated server, there is no physical client.

So the class isn't registered.

The behavior suddenly made sense:

                 Spellbound attack
                        │
                        ▼
                 Forge event bus
                        │
              ┌─────────┴─────────┐
              │                   │
        Singleplayer        Dedicated Server
              │                   │
        Physical CLIENT    Physical SERVER
              │                   │
              ▼                   X
     ModClientEvents       Not registered
              │
              ▼
       Attack handlers
              │
       ┌──────┴──────┐
       ▼             ▼
     Imps       Illager Souls
Enter fullscreen mode Exit fullscreen mode

The server wasn't executing the wrong code.

It wasn't executing the code at all.


Why didn't I just recompile it?

Once I knew the problem, the obvious source-level fix was tiny.

Change:

@Mod.EventBusSubscriber(
    modid = "illageandspillage",
    bus = Bus.FORGE,
    value = {Dist.CLIENT}
)
Enter fullscreen mode Exit fullscreen mode

to:

@Mod.EventBusSubscriber(
    modid = "illageandspillage",
    bus = Bus.FORGE
)
Enter fullscreen mode Exit fullscreen mode

Forge can then use the annotation's default side behavior rather than restricting registration to CLIENT.

But I didn't have the original source project and didn't want to rebuild an entire third-party mod just to change one annotation.

So I patched the compiled class instead.


Editing the class file

Java class files contain a constant pool, methods, fields, and attributes.

Annotations such as this one are stored in the class's runtime-visible annotation metadata.

The original annotation contained three element-value pairs:

modid
bus
value = CLIENT
Enter fullscreen mode Exit fullscreen mode

The patch removes only the value element.

So conceptually:

 @Mod.EventBusSubscriber(
     modid = "illageandspillage",
-    bus = Bus.FORGE,
-    value = {Dist.CLIENT}
+    bus = Bus.FORGE
 )
Enter fullscreen mode Exit fullscreen mode

No spell code was changed.

No entity behavior was changed.

No damage values were changed.

No configuration was changed.

No new abilities were added.

I changed the metadata that determines where the existing event subscriber gets registered.

I then repacked the modified class into the original JAR and verified the resulting class with javap.

The repository contains the reproducible patching tool rather than redistributing the third-party mod itself:

https://github.com/Alvalens/dawncraft-spellbound-server-fix


What I got wrong the first time

My first instinct was to treat this like a normal modpack configuration problem.

Maybe Exaroton was doing something weird.

Maybe there was a server configuration I had missed.

Maybe the mod simply didn't support multiplayer.

Those weren't unreasonable guesses.

But the useful clue was sitting in the behavior:

Singleplayer       → works
Dedicated server   → doesn't
Enter fullscreen mode Exit fullscreen mode

For Forge mods, that should make physical-side separation one of the first things worth checking.

The distinction is particularly easy to miss because integrated singleplayer isn't the same execution environment as a dedicated server.

Something incorrectly marked as client-only can look completely healthy during local testing.


The fix

I installed the patched JAR through Exaroton's override mechanism:

overrides/
└── mods/
    └── illageandspillage-1.18.2-1.1.3.jar
Enter fullscreen mode Exit fullscreen mode

Then I restarted the server.

Empty hand.

Misconduction active.

Right-click the ground.

Imps.

Then I attacked a mob.

Three Illager Souls.

The same two attacks that worked in singleplayer now worked on the dedicated server.


spellbound effect working

After the patch: the existing Spellbound attack works on the dedicated server.


One important scope note

There are newer Illage & Spillage versions and documentation that may describe additional Spellbound behavior.

That's not what this patch is doing.

I verified the actual code in the 1.18.2-1.1.3 JAR bundled with DawnCraft 2.0.16_hf, and the two relevant attacks are the ones described above.

The patch doesn't add anything.

It simply makes the existing event handlers register on the dedicated server.

The repository is therefore intentionally version-specific rather than pretending to be a universal Illage & Spillage fix.


The part I didn't expect

I started with a broken Minecraft spell.

The eventual debugging path was:

Observed behavior
      ↓
Singleplayer vs dedicated server comparison
      ↓
Inspect the mod JAR
      ↓
Find the event handlers
      ↓
Inspect event registration
      ↓
Find Dist.CLIENT
      ↓
Understand Forge physical sides
      ↓
Inspect classfile annotation
      ↓
Patch only the metadata
      ↓
Test on the real server
Enter fullscreen mode Exit fullscreen mode

The actual fix was one annotation value.

Everything else was figuring out which annotation mattered.

That's probably the part I find most useful about the whole experiment.

When a program behaves differently between environments, it's tempting to keep changing the environment until something works.

Sometimes it's better to stop guessing and go one layer deeper.

Even if that layer happens to be a Minecraft JAR.

Top comments (0)