Skip to content

Instantly share code, notes, and snippets.

@happyzleaf
Last active August 3, 2023 20:29
Show Gist options
  • Save happyzleaf/1b98c6c5e7e30c2861e0a381c347460b to your computer and use it in GitHub Desktop.
Save happyzleaf/1b98c6c5e7e30c2861e0a381c347460b to your computer and use it in GitHub Desktop.
Dynamically finding and loading a mod
import net.minecraft.launchwrapper.LaunchClassLoader;
import net.minecraftforge.fml.relauncher.CoreModManager;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.FileInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* Finds and loads the requested mod. It can be used to inject methods and such through mixins during the coremod initialization.
*
* Example:
* <pre>
* try {
* ModsScanner.findAndLoad("com/pixelmonmod/pixelmon/Pixelmon.class");
* } catch (Exception e) {
* System.out.println("There was a problem trying to load the pixelmon. The program will not continue.");
* e.printStackTrace();
* return;
* }
* </pre>
*
* While debugging the plugin, the desired mod might not be in the <code>/mods</code> folder.
* If that's your case, you can set the option <code>com.package.ClassName</code> to the location of the jar.
*
* Example:
* In my workspace, the debug runs in the folder <code>./run/client</code> or <code>./run/server</code>.
* Therefore, my debug configuration has the VM options set as the following:
* <code>-Dcom.pixelmonmod.pixelmon.Pixelmon=.\..\..\libs\Pixelmon-1.12.2-7.2.2-server.jar</code>
*
* @author happyzleaf
* @author clienthax (loading jar snippet)
*/
public class ModsScanner {
/**
* @param clazz A class name that we're sure it's unique to our mod, the main mod class is perfect.
* @throws Exception Catch any possible exception, there could be a lot.
*/
public static void findAndLoad(String clazz) throws Exception {
if (clazz == null || !clazz.endsWith(".class")) {
throw new RuntimeException("The class is null or invalid. Class: " + clazz);
}
try {
Class.forName(clazz, false, ModsScanner.class.getClassLoader());
// The class is already there, we don't need to load it.
return;
} catch (Exception ignored) {}
File mod;
String provided = System.getProperty(clazz.substring(0, clazz.indexOf(".class")).replace("/", "."));
if (provided != null) {
mod = new File(provided);
if (!mod.exists() || !containsClass(clazz, mod)) {
throw new RuntimeException("The provided library didn't exist or did not contain the specified class. Path: " + mod.getAbsolutePath());
}
} else {
File modsFolder = new File(System.getProperty("user.dir"), "mods");
if (!modsFolder.exists()) {
throw new RuntimeException("The mods folder couldn't be found. Path: " + modsFolder.toString());
}
// Scanning through /mods/ searching for all the jars
mod = FileUtils.listFiles(modsFolder, new String[]{"jar"}, false).stream()
.filter(file -> containsClass(clazz, file))
.findAny().orElse(null);
// Opsie dopsie, no jar found :(
if (mod == null) {
throw new RuntimeException("The mod's jar cannot be found.");
}
}
// Once we found the mod, it's better to check if another coremod already loaded it
if (!CoreModManager.getReparseableCoremods().contains(mod.getName())) {
// Now we're safe to load the jar (thanks to clienthax)
System.out.println("Loading = " + mod.toURI().toURL().toString());
((LaunchClassLoader) ModsScanner.class.getClassLoader()).addURL(mod.toURI().toURL());
CoreModManager.getReparseableCoremods().add(mod.getName());
}
}
/**
* @param clazz The class to search.
* @param file The file to analyze.
* @return If the given file contains the given class.
*/
private static boolean containsClass(String clazz, File file) {
try (ZipInputStream zip = new ZipInputStream(new FileInputStream(file))) {
ZipEntry entry;
while ((entry = zip.getNextEntry()) != null) {
zip.closeEntry();
if (entry.getName().equals(clazz)) {
return true;
}
}
} catch (Exception ignored) {}
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment