From 62cca5a4b7baecd7d4a8a89434af520f15dfa178 Mon Sep 17 00:00:00 2001 From: Christian Weeks Date: Wed, 28 Mar 2012 16:44:36 -0400 Subject: [PATCH] Thoughts on how to do the modloading properly --- fml/src/fml/Mod.java | 15 ++ fml/src/fml/server/FMLModContainer.java | 49 +++++ fml/src/fml/server/Loader.java | 193 ++++++++++++++++++ fml/src/fml/server/ModClassLoader.java | 18 ++ fml/src/fml/server/ModContainer.java | 9 + fml/src/fml/server/ModLoaderModContainer.java | 30 +++ fml/src/fml/stubs/mcpserver/BaseMod.java | 6 + 7 files changed, 320 insertions(+) create mode 100644 fml/src/fml/Mod.java create mode 100644 fml/src/fml/server/FMLModContainer.java create mode 100644 fml/src/fml/server/Loader.java create mode 100644 fml/src/fml/server/ModClassLoader.java create mode 100644 fml/src/fml/server/ModContainer.java create mode 100644 fml/src/fml/server/ModLoaderModContainer.java diff --git a/fml/src/fml/Mod.java b/fml/src/fml/Mod.java new file mode 100644 index 000000000..ae98b0c96 --- /dev/null +++ b/fml/src/fml/Mod.java @@ -0,0 +1,15 @@ +package fml; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Mod { + String name() default ""; + String version() default ""; + boolean wantsPreInit() default false; + boolean wantsPostInit() default false; + public @interface PreInit {} + public @interface Init {} + public @interface PostInit {} +} diff --git a/fml/src/fml/server/FMLModContainer.java b/fml/src/fml/server/FMLModContainer.java new file mode 100644 index 000000000..dbd483ade --- /dev/null +++ b/fml/src/fml/server/FMLModContainer.java @@ -0,0 +1,49 @@ +package fml.server; + +import fml.Mod; + +public class FMLModContainer implements ModContainer { + private Mod modDescriptor; + private Object modInstance; + public FMLModContainer(Class clazz) { + modDescriptor=clazz.getAnnotation(Mod.class); + + try { + modInstance=clazz.newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean wantsPreInit() { + return modDescriptor.wantsPreInit(); + } + + @Override + public boolean wantsPostInit() { + return modDescriptor.wantsPostInit(); + } + + @Override + public void preInit() { + + } + + @Override + public void init() { + // TODO Auto-generated method stub + + } + + @Override + public void postInit() { + // TODO Auto-generated method stub + + } + + public static ModContainer buildFor(Class clazz) { + return new FMLModContainer(clazz); + } + +} diff --git a/fml/src/fml/server/Loader.java b/fml/src/fml/server/Loader.java new file mode 100644 index 000000000..5b289f01c --- /dev/null +++ b/fml/src/fml/server/Loader.java @@ -0,0 +1,193 @@ +package fml.server; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import fml.Mod; +import fml.stubs.mcpserver.BaseMod; + +public class Loader { + private enum State { NOINIT, LOADING, PREINIT, INIT, POSTINIT, UP, ERRORED }; + private static State state; + private static Logger LOG = Logger.getLogger("ForgeModLoader.Loader"); + + private static List mods; + + private static ModClassLoader modClassLoader; + + private static Pattern zipJar = Pattern.compile("([\\w]+).(zip|jar)$"); + private static Pattern modClass = Pattern.compile("(.*)(mod_[^\\s]+)\\.class$"); + + public static Loader instance; + + public static void run() { + instance=new Loader(); + } + + private Loader() { + state=State.NOINIT; + load(); + preModInit(); + modInit(); + postModInit(); + state=State.UP; + } + + private void preModInit() { + state=State.PREINIT; + for (ModContainer mod : mods) { + if (mod.wantsPreInit()) { + mod.preInit(); + } + } + } + + private void modInit() { + state=State.INIT; + for (ModContainer mod : mods) { + mod.init(); + } + } + + private void postModInit() { + state=State.POSTINIT; + for (ModContainer mod : mods) { + if (mod.wantsPostInit()) { + mod.postInit(); + } + } + } + + private void load() { + File modsDir = new File(".", "mods"); + String canonicalModsPath; + try { + canonicalModsPath = modsDir.getCanonicalPath(); + } catch (IOException ioe) { + LOG.severe(String.format("Failed to resolve mods directory mods %s",modsDir.getAbsolutePath())); + LOG.throwing("fml.server.Loader", "initialize", ioe); + throw new LoaderException(ioe); + } + if (!modsDir.exists()) { + LOG.fine(String.format("No mod directory found, creating one: %s", canonicalModsPath)); + try { + modsDir.mkdir(); + } catch (Exception e) { + LOG.throwing("fml.server.Loader", "initialize", e); + throw new LoaderException(e); + } + } + if (!modsDir.isDirectory()) { + LOG.severe(String.format("Attempting to load mods from %s, which is not a directory", canonicalModsPath)); + LoaderException loaderException = new LoaderException(); + LOG.throwing("fml.server.Loader", "initialize", loaderException); + throw loaderException; + } + File[] modList = modsDir.listFiles(); + // Sort the files into alphabetical order first + Arrays.sort(modList); + + state=State.LOADING; + for (File modFile : modList) { + if (modFile.isDirectory()) { + LOG.info(String.format("Found directory %s. Attempting load", modFile.getName())); + attemptDirLoad(modFile); + } else { + Matcher matcher = zipJar.matcher(modFile.getName()); + if (matcher.find()) { + LOG.info(String.format("Found zip or jar file %s. Attempting load.", matcher.group(0))); + attemptFileLoad(modFile); + } + } + } + if (state==State.ERRORED) { + LOG.severe("A problem has occured during mod loading. Giving up now"); + throw new RuntimeException("Giving up please"); + } + } + + private void attemptDirLoad(File modDir) { + extendClassLoader(modDir); + + File[] content=modDir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return modClass.matcher(name).find(); + } + }); + for (File modClassFile : content) { + LOG.fine(String.format("Found a mod class %s in directory %s. Attempting to load it",modClassFile.getName(),modDir.getName())); + String clazzName=modClass.matcher(modClassFile.getName()).group(2); + loadModClass(modDir, modClassFile.getName(), clazzName); + LOG.fine(String.format("Successfully loaded mod class %s",modClassFile.getName())); + } + } + + private void loadModClass(File classSource, String classFileName, String clazzName) { + try { + Class clazz=Class.forName(clazzName,true,modClassLoader); + if (clazz.isAnnotationPresent(Mod.class)) { + // an FML mod + mods.add(FMLModContainer.buildFor(clazz)); + } else if (clazz.isAssignableFrom(BaseMod.class)) { + // a modloader mod + } else { + // Unrecognized + } + } catch (ClassNotFoundException e) { + LOG.warning(String.format("Failed to load mod class %s in %s",classFileName,classSource.getName())); + LOG.throwing("fml.server.Loader", "attemptDirLoad", e); + state=State.ERRORED; + } + } + + private void extendClassLoader(File file) { + if (modClassLoader==null) { + modClassLoader=new ModClassLoader(); + } + try { + modClassLoader.addFile(file); + } catch (MalformedURLException e) { + throw new LoaderException(e); + } + } + + private void attemptFileLoad(File modFile) { + extendClassLoader(modFile); + + try { + ZipFile jar=new ZipFile(modFile); + for (ZipEntry ze : Collections.list(jar.entries())) { + Matcher match=modClass.matcher(ze.getName()); + if (match.find()) { + String pkg=match.group(1).replace('/', '.'); + String clazzName=pkg+match.group(2); + loadModClass(modFile, ze.getName(), clazzName); + } + } + } catch (Exception e) { + LOG.warning(String.format("Zip file %s failed to read properly", modFile.getName())); + LOG.throwing("fml.server.Loader", "attemptFileLoad", e); + state=State.ERRORED; + } + } + + class LoaderException extends RuntimeException { + public LoaderException(Exception wrapped) { + super(wrapped); + } + + public LoaderException() { + } + } +} diff --git a/fml/src/fml/server/ModClassLoader.java b/fml/src/fml/server/ModClassLoader.java new file mode 100644 index 000000000..051c074b1 --- /dev/null +++ b/fml/src/fml/server/ModClassLoader.java @@ -0,0 +1,18 @@ +package fml.server; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; + +public class ModClassLoader extends URLClassLoader { + + public ModClassLoader() { + super(new URL[0]); + } + + public void addFile(File modFile) throws MalformedURLException { + URL url=modFile.toURI().toURL(); + super.addURL(url); + } +} diff --git a/fml/src/fml/server/ModContainer.java b/fml/src/fml/server/ModContainer.java new file mode 100644 index 000000000..344806de3 --- /dev/null +++ b/fml/src/fml/server/ModContainer.java @@ -0,0 +1,9 @@ +package fml.server; + +public interface ModContainer { + boolean wantsPreInit(); + boolean wantsPostInit(); + void preInit(); + void init(); + void postInit(); +} diff --git a/fml/src/fml/server/ModLoaderModContainer.java b/fml/src/fml/server/ModLoaderModContainer.java new file mode 100644 index 000000000..69c31386e --- /dev/null +++ b/fml/src/fml/server/ModLoaderModContainer.java @@ -0,0 +1,30 @@ +package fml.server; + +public class ModLoaderModContainer implements ModContainer { + public boolean wantsPreInit() { + return false; + } + + public boolean wantsPostInit() { + return false; + } + + @Override + public void preInit() { + // TODO Auto-generated method stub + + } + + @Override + public void init() { + // TODO Auto-generated method stub + + } + + @Override + public void postInit() { + // TODO Auto-generated method stub + + } + +} diff --git a/fml/src/fml/stubs/mcpserver/BaseMod.java b/fml/src/fml/stubs/mcpserver/BaseMod.java index f0519c779..ebb6d07f8 100644 --- a/fml/src/fml/stubs/mcpserver/BaseMod.java +++ b/fml/src/fml/stubs/mcpserver/BaseMod.java @@ -2,12 +2,16 @@ package fml.stubs.mcpserver; import java.util.Random; +import fml.Mod; + + import net.minecraft.src.EntityPlayer; import net.minecraft.src.IInventory; import net.minecraft.src.ItemStack; import net.minecraft.src.Packet250CustomPayload; import net.minecraft.src.World; +@Mod(name="blah",version="blah") public interface BaseMod { int addFuel(int id, int metadata); @@ -25,8 +29,10 @@ public interface BaseMod { abstract String getVersion(); // void keyboardEvent(KeyBinding event); + @Mod.PreInit abstract void load(); + @Mod.Init void modsLoaded(); void onItemPickup(EntityPlayer player, ItemStack item);