Skip to content

MCEngine/mcextension

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

150 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

MCExtension

Java Version License

MCExtension is a high-performance, platform-agnostic library designed for Minecraft servers. It allows developers to load external JAR files as Extensions with isolated classloading and robust dependency resolution.


๐Ÿš€ Key Features

  • Platform Agnostic Core: Decoupled via HostContext, supporting Spigot, Paper, Folia, and more.
  • Isolated Classloading: Each extension operates in its own URLClassLoader to eliminate dependency conflicts.
  • Smart Dependency Resolution: Built-in support for extension-on-extension dependencies.
  • Async-First Design: Optimized for performance by keeping heavy I/O and initialization off the main server thread.
  • License Verification: Native support for license validation.
  • Modern Tech Stack: Built with Java 25.

๐Ÿ“ฆ Installation (Gradle)

MCExtension is available via GitHub Packages. Add the following to your build.gradle:

repositories {
    maven {
        name = "MCEngine"
        url = uri("https://maven.pkg.github.com/MCEngine/mcextension")
        credentials {
            username = System.getenv("GITHUB_ACTOR")
            password = System.getenv("GITHUB_TOKEN")
        }
    }
}

dependencies {
    // Core Manager
    implementation 'io.github.mcengine:manager:2026.0.6-4'
    
    // Platform Implementations
    implementation 'io.github.mcengine:spigotmc:2026.0.6-4'
    implementation 'io.github.mcengine:papermc:2026.0.6-4'
    implementation 'io.github.mcengine:foliamc:2026.0.6-4'
}

๐Ÿ› ๏ธ Developing an Extension

1. Implement IMCExtension

Your extension entry point must implement the IMCExtension interface.

package com.example.yourname.yourextension;

import io.github.mcengine.mcextension.api.IMCExtension;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.concurrent.Executor;

public class MyAwesomeExtension implements IMCExtension {

    // Hardcoded URLs and version info
    private static final String LICENSE_SERVER_URL = "https://auth.example.com/verify";
    private static final String UPDATE_SERVER_URL = "https://api.example.com/latest-version";
    private static final String CURRENT_VERSION = "1.0.0";

    @Override
    public boolean checkLicense(String token) {
        if (token == null || token.isBlank()) {
            System.out.println("[MyAwesomeExtension] License token is missing in config.yml!");
            return false;
        }

        System.out.println("[MyAwesomeExtension] Verifying license token...");

        try {
            HttpClient client = HttpClient.newBuilder()
                    .connectTimeout(Duration.ofSeconds(10))
                    .build();

            String jsonPayload = "{\"token\":\"" + token + "\"}";

            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create(LICENSE_SERVER_URL))
                    .timeout(Duration.ofSeconds(10))
                    .header("Content-Type", "application/json")
                    .POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
                    .build();

            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

            if (response.statusCode() == 200) {
                String responseBody = response.body();
                if (responseBody != null && responseBody.contains("\"valid\": true")) {
                    System.out.println("[MyAwesomeExtension] License verified successfully!");
                    return true;
                } else {
                    System.out.println("[MyAwesomeExtension] Invalid license token.");
                    return false;
                }
            } else {
                System.out.println("[MyAwesomeExtension] Server responded with error code: " + response.statusCode());
                return false;
            }

        } catch (Exception e) {
            System.out.println("[MyAwesomeExtension] Failed to connect to the license server: " + e.getMessage());
            return false;
        }
    }

    @Override
    public boolean checkUpdate(String url, String token) {
        System.out.println("[MyAwesomeExtension] Checking for updates...");

        try {
            HttpClient client = HttpClient.newBuilder()
                    .connectTimeout(Duration.ofSeconds(10))
                    .build();

            // Using GET request to fetch the latest version string from your API
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create(url != null ? url : UPDATE_SERVER_URL))
                    .timeout(Duration.ofSeconds(10))
                    .GET()
                    .build();

            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

            if (response.statusCode() == 200) {
                // Assuming the server returns plain text like "1.0.1"
                String latestVersion = response.body().trim();
                
                // Compare versions (You can implement a more robust semantic versioning comparison here)
                if (!CURRENT_VERSION.equals(latestVersion)) {
                    System.out.println("[MyAwesomeExtension] A new update is available: v" + latestVersion);
                    System.out.println("[MyAwesomeExtension] Please download it from our official website.");
                    return true;
                } else {
                    System.out.println("[MyAwesomeExtension] You are running the latest version.");
                    return false;
                }
            } else {
                System.out.println("[MyAwesomeExtension] Failed to check for updates. HTTP Code: " + response.statusCode());
                return false;
            }

        } catch (Exception e) {
            System.out.println("[MyAwesomeExtension] Could not reach the update server: " + e.getMessage());
            return false;
        }
    }

    @Override
    public void onLoad(Object plugin, Executor executor) {
        System.out.println("[MyAwesomeExtension] Extension is now fully loaded and running.");

        // Execute the update check asynchronously to avoid blocking the main server thread
        executor.execute(() -> {
            checkUpdate(UPDATE_SERVER_URL, null);
        });

        // Place your regular extension initialization logic here
    }

    @Override
    public void onDisable(Object plugin, Executor executor) {
        System.out.println("[MyAwesomeExtension] Extension has been disabled.");
        // Place your cleanup logic here
    }
}

2. Extension Configuration (extension.yml)

Place this in your extension's src/main/resources folder.

name: "MyExtension"
version: "1.0.0"

# Main classes for different platforms
mains:
  spigotmc: "io.github.example.SpigotMain"
  papermc: "io.github.example.PaperMain"

# Extension dependencies
extension:
  depend: [ "BaseExtensionID" ]

โš™๏ธ Platform Integration

To integrate MCExtension into your host plugin, use the provided HostContext or implement your own.

Example (Spigot/Paper):

import io.github.mcengine.mcextension.common.MCExtensionManager;
import io.github.mcengine.mcextension.spigotmc.context.SpigotHostContext;

public class MyPlugin extends JavaPlugin {
    private MCExtensionManager manager;

    @Override
    public void onEnable() {
        // 1. Initialize Context
        SpigotHostContext context = new SpigotHostContext(this);
        
        // 2. Initialize Manager (Iteration -1 for auto-resolve, platform "spigotmc")
        this.manager = new MCExtensionManager(-1, "spigotmc");
        
        // 3. Load Extensions Asynchronously
        Executor executor = Executors.newCachedThreadPool();
        manager.loadAllExtensions(context, executor);
    }
}

๐Ÿ” Advanced Features

License Verification

Extensions can enforce licensing by providing a config.yml in their data folder:

license: "XXXX-XXXX-XXXX-XXXX"

License

Distributed under the MIT License. See LICENSE for more information.


๐Ÿ‘ฅ Authors