Advanced Protection

Go beyond basic verification. Implement LicenseGuard, dispersed checks, and ProGuard obfuscation to make your plugin nearly impossible to crack.

LicenseGuard Pattern

Wrap the SDK in a dedicated utility class that handles verification, heartbeat, and enforcement. This class should NOT implement Listener or CommandExecutor so ProGuard can fully obfuscate it.

LicenseGuard.java
java
package com.example.myplugin.util;

import ch.irixiagroup.ilicence.ILicence;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import java.time.Duration;

public class LicenseGuard {

    private final JavaPlugin plugin;
    private ILicence licence;

    public LicenseGuard(JavaPlugin plugin, String key, String pluginId) {
        this.plugin = plugin;
        this.licence = ILicence.builder()
                .plugin(plugin)
                .licenseKey(key != null ? key : "")
                .pluginId(pluginId)
                .heartbeat(Duration.ofMinutes(30))
                .gracePeriod(Duration.ofHours(24))
                .onStatusChange((wasValid, isNowValid) -> {
                    if (!isNowValid) {
                        Bukkit.getScheduler().runTask(plugin, this::shutdown);
                    }
                })
                .verify();
    }

    public boolean check() {
        return licence != null && licence.isValid();
    }

    public boolean enforce() {
        if (!check()) { shutdown(); return false; }
        return true;
    }

    private void shutdown() {
        plugin.getLogger().severe("License validation failed. Disabling plugin.");
        Bukkit.getPluginManager().disablePlugin(plugin);
    }

    public void stop() {
        if (licence != null) licence.stop();
    }
}

Why a wrapper?

Separating license logic into its own class makes it harder to find and remove. ProGuard will rename this class to something like a.class, hiding its purpose entirely.

Main Class Integration

Initialize the LicenseGuard in onEnable() before any other manager. Read the license key from config.yml — never hardcode it.

MyPlugin.java
java
private LicenseGuard licenseGuard;

@Override
public void onEnable() {
    saveDefaultConfig();

    String licenseKey = getConfig().getString("license-key", "");
    if (licenseKey.isEmpty()) {
        getLogger().severe("No license key! Set 'license-key' in config.yml");
        getServer().shutdown();
        return;
    }

    licenseGuard = new LicenseGuard(this, licenseKey, "my-plugin-slug");

    // ... rest of your plugin init
}

@Override
public void onDisable() {
    if (licenseGuard != null) licenseGuard.stop();
}

Dispersed License Checks

Don't rely on a single verification at startup. Add enforce() calls at critical points in your game logic. Even if someone removes the startup check, the plugin will still shut down when it hits a dispersed check.

Critical Method Protection
java
// In your most critical method (game start, match begin, etc.)
public boolean startGame(Player player) {
    if (!licenseGuard.enforce()) return false;

    // ... game logic
    return true;
}
Place enforce() in your most-used methods (game start, match begin, command execution)
Spread checks across multiple classes for maximum protection
The enforce() method is instant (no API call) — it reads the cached status
If the license becomes invalid mid-game, the plugin shuts down at the next check

Obfuscation with ProGuard

ProGuard renames your classes, methods, and fields to meaningless names like a, b, c. This makes it extremely hard for someone to find and remove your license checks by decompiling the JAR.

Maven Setup

Add the ProGuard Maven plugin to your pom.xml after the maven-shade-plugin. It obfuscates the already-shaded JAR so all dependencies are included.

pom.xml
xml
<plugin>
    <groupId>com.github.wvengen</groupId>
    <artifactId>proguard-maven-plugin</artifactId>
    <version>2.6.1</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals><goal>proguard</goal></goals>
        </execution>
    </executions>
    <configuration>
        <proguardVersion>7.6.1</proguardVersion>
        <injar>${project.build.finalName}.jar</injar>
        <outjar>${project.build.finalName}.jar</outjar>
        <obfuscate>true</obfuscate>
        <options>
            <!-- Keep your main class name -->
            <option>-keep public class com.example.MyPlugin {
                public void onEnable();
                public void onDisable();
            }</option>

            <!-- Keep Bukkit event handlers + commands -->
            <option>-keep class * implements org.bukkit.event.Listener { *; }</option>
            <option>-keep class * implements org.bukkit.command.CommandExecutor { *; }</option>
            <option>-keep class * implements org.bukkit.command.TabCompleter { *; }</option>

            <!-- Keep enums + ILicence SDK -->
            <option>-keepclassmembers enum * { *; }</option>
            <option>-keep class ch.irixiagroup.ilicence.** { *; }</option>

            <!-- Only obfuscate, don't shrink or optimize -->
            <option>-dontshrink</option>
            <option>-dontoptimize</option>
            <option>-dontwarn **</option>
        </options>
        <libs>
            <lib>${java.home}/jmods/java.base.jmod</lib>
            <lib>${java.home}/jmods/java.logging.jmod</lib>
        </libs>
    </configuration>
    <dependencies>
        <dependency>
            <groupId>com.guardsquare</groupId>
            <artifactId>proguard-base</artifactId>
            <version>7.6.1</version>
        </dependency>
        <dependency>
            <groupId>com.guardsquare</groupId>
            <artifactId>proguard-core</artifactId>
            <version>9.1.7</version>
        </dependency>
    </dependencies>
</plugin>

Important Rules

ProGuard must run AFTER the shade plugin. Keep your main class, Bukkit listeners, command executors, and the ILicence SDK un-obfuscated. Everything else (including LicenseGuard) gets obfuscated.

What Gets Obfuscated

Class TypeObfuscated?Why
Main plugin className keptBukkit needs to find it via plugin.yml
Event listenersName keptBukkit reflects on @EventHandler methods
Command executorsName keptBukkit reflects on onCommand()
LicenseGuardFully obfuscatedNot a Listener/Executor — hidden by design
Utility classesFully obfuscatedInternal implementation details
ILicence SDKKept as-isShaded dependency, needs reflection
EnumsValues keptJava enum serialization requires it

Verify Obfuscation

After building, run jar tf target/YourPlugin.jar | grep -E "Guard|Util" and check that your utility classes appear as single letters like a.class. If you see the original names, your ProGuard config needs adjustment.

Best Practices

01

Never hardcode license keys

Always read from config.yml. Ship with an empty default.

02

Use heartbeat + grace period

30-minute heartbeat with 24-hour grace. Catches revocations without false positives.

03

Disperse your checks

Add enforce() in 3-5 critical methods across different classes.

04

Obfuscate with ProGuard

Makes license removal nearly impossible by hiding class and method names.

05

Keep LicenseGuard clean

Don't implement Listener or CommandExecutor on it. Let ProGuard hide it completely.

06

Handle onDisable() safely

Call licenseGuard.stop() first, wrap cleanup in try-catch for early shutdown scenarios.

How It Stops Cracking

Attack: Decompile JAR and remove verify() call

Defense: Dispersed checks in critical methods. License check at game start, match begin, command execution.

Attack: Patch ILicence.verify() to always return valid

Defense: ProGuard hides the class name (becomes a.class). Cracker can't find it.

Attack: Modify the plugin's config.yml

Defense: Unnecessary. License verified against API, not config file.

Attack: Intercept network traffic and fake API response

Defense: HTTPS TLS 1.3. Responses signed server-side (future: JWT).

Attack: Wait 7 days for license to expire on server records

Defense: Heartbeat checks every 30 minutes. Plugin disables if server stops checking in.