Skip to content

Instantly share code, notes, and snippets.

@HSGamer
Created May 3, 2025 00:16
Show Gist options
  • Select an option

  • Save HSGamer/82b4e72b8eaf9cac41289cba51116497 to your computer and use it in GitHub Desktop.

Select an option

Save HSGamer/82b4e72b8eaf9cac41289cba51116497 to your computer and use it in GitHub Desktop.
Load and remap libraries at runtime with libby and PaperMC's PluginRemapper
import net.byteflux.libby.BukkitLibraryManager;
import net.byteflux.libby.Library;
import org.bukkit.plugin.Plugin;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.function.Function;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
public class RemappedBukkitLibraryManager extends BukkitLibraryManager {
private final Function<List<Path>, List<Path>> REMAPPER;
{
Function<List<Path>, List<Path>> remapper;
try {
Class<?> clazz = Class.forName("org.bukkit.plugin.java.LibraryLoader");
Field remapperField = clazz.getDeclaredField("REMAPPER");
//noinspection unchecked
remapper = (Function<List<Path>, List<Path>>) remapperField.get(null);
} catch (Exception e) {
remapper = null;
}
REMAPPER = remapper;
}
public RemappedBukkitLibraryManager(Plugin plugin) {
super(plugin);
}
public RemappedBukkitLibraryManager(Plugin plugin, String directoryName) {
super(plugin, directoryName);
}
private Path remap(Path file) {
return REMAPPER != null ? REMAPPER.apply(Collections.singletonList(file)).get(0) : file;
}
// This method's only purpose is to add the "paperweight-mappings-namespace" attribute to the MANIFEST.MF file of the library
// So that it can be remapped by Paper's PluginRemapper
// Thanks Copilot for this monstrosity of a method that solves the problem
private void modifyManifest(Path file, Path modifiedFile) {
try {
Path tempJarPath = Files.createTempFile("temp-", ".jar");
try (JarFile jarFile = new JarFile(file.toFile());
JarOutputStream tempJarOutputStream = new JarOutputStream(Files.newOutputStream(tempJarPath))) {
// Copy existing entries
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (JarFile.MANIFEST_NAME.equals(entry.getName())) {
continue; // Skip the existing MANIFEST.MF
}
try (InputStream entryInputStream = jarFile.getInputStream(entry)) {
tempJarOutputStream.putNextEntry(new JarEntry(entry.getName()));
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = entryInputStream.read(buffer)) != -1) {
tempJarOutputStream.write(buffer, 0, bytesRead);
}
tempJarOutputStream.closeEntry();
}
}
// Modify the MANIFEST.MF
Manifest manifest = jarFile.getManifest();
if (manifest == null) {
manifest = new Manifest();
}
manifest.getMainAttributes().putValue("paperweight-mappings-namespace", "spigot");
// Write the modified MANIFEST.MF
tempJarOutputStream.putNextEntry(new JarEntry(JarFile.MANIFEST_NAME));
manifest.write(tempJarOutputStream);
tempJarOutputStream.closeEntry();
}
Files.move(tempJarPath, modifiedFile, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public void remapAndLoadLibrary(Library library) {
Path path = saveDirectory.resolve(library.getPath());
Path parent = path.getParent();
String name = path.getFileName().toString();
name = name.substring(0, name.lastIndexOf('.'));
boolean downloaded = false;
if (!Files.exists(path)) {
downloadLibrary(library);
downloaded = true;
}
Path modifiedPath = parent.resolve(name + "-modified.jar");
if (downloaded || !Files.exists(modifiedPath)) {
modifyManifest(path, modifiedPath);
}
Path remappedPath = remap(modifiedPath);
if (library.isIsolatedLoad()) {
addToIsolatedClasspath(library, remappedPath);
} else {
addToClasspath(remappedPath);
}
}
}
import net.byteflux.libby.Library;
import org.bukkit.plugin.java.JavaPlugin;
public final class TestPlugin extends JavaPlugin {
@Override
public void onEnable() {
RemappedBukkitLibraryManager libraryManager = new RemappedBukkitLibraryManager(this);
Library lib = Library.builder()
.groupId("net{}wesjd")
.artifactId("anvilgui")
.version("1.10.5-SNAPSHOT")
.repository("https://repo.codemc.io/repository/maven-public/")
.build();
libraryManager.remapAndLoadLibrary(lib);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment