Last active
August 3, 2023 20:29
-
-
Save happyzleaf/1b98c6c5e7e30c2861e0a381c347460b to your computer and use it in GitHub Desktop.
Dynamically finding and loading a mod
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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