001    /*
002     * The FML Forge Mod Loader suite.
003     * Copyright (C) 2012 cpw
004     *
005     * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or any later version.
007     *
008     * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
009     * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
010     *
011     * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51
012     * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
013     */
014    package cpw.mods.fml.common.modloader;
015    
016    import java.io.File;
017    import java.io.FileReader;
018    import java.io.FileWriter;
019    import java.io.IOException;
020    import java.lang.reflect.Constructor;
021    import java.lang.reflect.Field;
022    import java.lang.reflect.Modifier;
023    import java.util.ArrayList;
024    import java.util.EnumSet;
025    import java.util.List;
026    import java.util.Map;
027    import java.util.Properties;
028    import java.util.Set;
029    import java.util.logging.Level;
030    
031    import net.minecraft.command.ICommand;
032    
033    import com.google.common.base.Strings;
034    import com.google.common.base.Throwables;
035    import com.google.common.collect.ImmutableMap;
036    import com.google.common.collect.Lists;
037    import com.google.common.collect.Sets;
038    import com.google.common.eventbus.EventBus;
039    import com.google.common.eventbus.Subscribe;
040    
041    import cpw.mods.fml.common.FMLCommonHandler;
042    import cpw.mods.fml.common.FMLLog;
043    import cpw.mods.fml.common.LoadController;
044    import cpw.mods.fml.common.Loader;
045    import cpw.mods.fml.common.LoaderException;
046    import cpw.mods.fml.common.MetadataCollection;
047    import cpw.mods.fml.common.ModClassLoader;
048    import cpw.mods.fml.common.ModContainer;
049    import cpw.mods.fml.common.ModMetadata;
050    import cpw.mods.fml.common.ProxyInjector;
051    import cpw.mods.fml.common.Side;
052    import cpw.mods.fml.common.TickType;
053    import cpw.mods.fml.common.discovery.ASMDataTable;
054    import cpw.mods.fml.common.discovery.ASMDataTable.ASMData;
055    import cpw.mods.fml.common.discovery.ContainerType;
056    import cpw.mods.fml.common.event.FMLConstructionEvent;
057    import cpw.mods.fml.common.event.FMLInitializationEvent;
058    import cpw.mods.fml.common.event.FMLLoadCompleteEvent;
059    import cpw.mods.fml.common.event.FMLPostInitializationEvent;
060    import cpw.mods.fml.common.event.FMLPreInitializationEvent;
061    import cpw.mods.fml.common.event.FMLServerStartingEvent;
062    import cpw.mods.fml.common.network.FMLNetworkHandler;
063    import cpw.mods.fml.common.network.NetworkRegistry;
064    import cpw.mods.fml.common.registry.GameRegistry;
065    import cpw.mods.fml.common.registry.TickRegistry;
066    import cpw.mods.fml.common.versioning.ArtifactVersion;
067    import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
068    import cpw.mods.fml.common.versioning.VersionRange;
069    
070    public class ModLoaderModContainer implements ModContainer
071    {
072        public BaseModProxy mod;
073        private File modSource;
074        public Set<ArtifactVersion> requirements = Sets.newHashSet();
075        public ArrayList<ArtifactVersion> dependencies = Lists.newArrayList();
076        public ArrayList<ArtifactVersion> dependants = Lists.newArrayList();
077        private ContainerType sourceType;
078        private ModMetadata metadata;
079        private ProxyInjector sidedProxy;
080        private BaseModTicker gameTickHandler;
081        private BaseModTicker guiTickHandler;
082        private String modClazzName;
083        private String modId;
084        private EventBus bus;
085        private LoadController controller;
086        private boolean enabled = true;
087        private String sortingProperties;
088        private ArtifactVersion processedVersion;
089        private boolean isNetworkMod;
090        private List<ICommand> serverCommands = Lists.newArrayList();
091    
092        public ModLoaderModContainer(String className, File modSource, String sortingProperties)
093        {
094            this.modClazzName = className;
095            this.modSource = modSource;
096            this.modId = className.contains(".") ? className.substring(className.lastIndexOf('.')+1) : className;
097            this.sortingProperties = Strings.isNullOrEmpty(sortingProperties) ? "" : sortingProperties;
098        }
099    
100        /**
101         * We only instantiate this for "not mod mods"
102         * @param instance
103         */
104        ModLoaderModContainer(BaseModProxy instance) {
105            this.mod=instance;
106            this.gameTickHandler = new BaseModTicker(instance, false);
107            this.guiTickHandler = new BaseModTicker(instance, true);
108        }
109    
110        /**
111         *
112         */
113        private void configureMod(Class<? extends BaseModProxy> modClazz, ASMDataTable asmData)
114        {
115            File configDir = Loader.instance().getConfigDir();
116            File modConfig = new File(configDir, String.format("%s.cfg", getModId()));
117            Properties props = new Properties();
118    
119            boolean existingConfigFound = false;
120            boolean mlPropFound = false;
121    
122            if (modConfig.exists())
123            {
124                try
125                {
126                    FMLLog.fine("Reading existing configuration file for %s : %s", getModId(), modConfig.getName());
127                    FileReader configReader = new FileReader(modConfig);
128                    props.load(configReader);
129                    configReader.close();
130                }
131                catch (Exception e)
132                {
133                    FMLLog.log(Level.SEVERE, e, "Error occured reading mod configuration file %s", modConfig.getName());
134                    throw new LoaderException(e);
135                }
136                existingConfigFound = true;
137            }
138    
139            StringBuffer comments = new StringBuffer();
140            comments.append("MLProperties: name (type:default) min:max -- information\n");
141    
142    
143            List<ModProperty> mlPropFields = Lists.newArrayList();
144            try
145            {
146                for (ASMData dat : Sets.union(asmData.getAnnotationsFor(this).get("net.minecraft.src.MLProp"), asmData.getAnnotationsFor(this).get("MLProp")))
147                {
148                    if (dat.getClassName().equals(modClazzName))
149                    {
150                        try
151                        {
152                            mlPropFields.add(new ModProperty(modClazz.getDeclaredField(dat.getObjectName()), dat.getAnnotationInfo()));
153                            FMLLog.finest("Found an MLProp field %s in %s", dat.getObjectName(), getModId());
154                        }
155                        catch (Exception e)
156                        {
157                            FMLLog.log(Level.WARNING, e, "An error occured trying to access field %s in mod %s", dat.getObjectName(), getModId());
158                        }
159                    }
160                }
161                for (ModProperty property : mlPropFields)
162                {
163                    if (!Modifier.isStatic(property.field().getModifiers()))
164                    {
165                        FMLLog.info("The MLProp field %s in mod %s appears not to be static", property.field().getName(), getModId());
166                        continue;
167                    }
168                    FMLLog.finest("Considering MLProp field %s", property.field().getName());
169                    Field f = property.field();
170                    String propertyName = !Strings.nullToEmpty(property.name()).isEmpty() ? property.name() : f.getName();
171                    String propertyValue = null;
172                    Object defaultValue = null;
173    
174                    try
175                    {
176                        defaultValue = f.get(null);
177                        propertyValue = props.getProperty(propertyName, extractValue(defaultValue));
178                        Object currentValue = parseValue(propertyValue, property, f.getType(), propertyName);
179                        FMLLog.finest("Configuration for %s.%s found values default: %s, configured: %s, interpreted: %s", modClazzName, propertyName, defaultValue, propertyValue, currentValue);
180    
181                        if (currentValue != null && !currentValue.equals(defaultValue))
182                        {
183                            FMLLog.finest("Configuration for %s.%s value set to: %s", modClazzName, propertyName, currentValue);
184                            f.set(null, currentValue);
185                        }
186                    }
187                    catch (Exception e)
188                    {
189                        FMLLog.log(Level.SEVERE, e, "Invalid configuration found for %s in %s", propertyName, modConfig.getName());
190                        throw new LoaderException(e);
191                    }
192                    finally
193                    {
194                        comments.append(String.format("MLProp : %s (%s:%s", propertyName, f.getType().getName(), defaultValue));
195    
196                        if (property.min() != Double.MIN_VALUE)
197                        {
198                            comments.append(",>=").append(String.format("%.1f", property.min()));
199                        }
200    
201                        if (property.max() != Double.MAX_VALUE)
202                        {
203                            comments.append(",<=").append(String.format("%.1f", property.max()));
204                        }
205    
206                        comments.append(")");
207    
208                        if (!Strings.nullToEmpty(property.info()).isEmpty())
209                        {
210                            comments.append(" -- ").append(property.info());
211                        }
212    
213                        if (propertyValue != null)
214                        {
215                            props.setProperty(propertyName, extractValue(propertyValue));
216                        }
217                        comments.append("\n");
218                    }
219                    mlPropFound = true;
220                }
221            }
222            finally
223            {
224                if (!mlPropFound && !existingConfigFound)
225                {
226                    FMLLog.fine("No MLProp configuration for %s found or required. No file written", getModId());
227                    return;
228                }
229    
230                if (!mlPropFound && existingConfigFound)
231                {
232                    File mlPropBackup = new File(modConfig.getParent(),modConfig.getName()+".bak");
233                    FMLLog.fine("MLProp configuration file for %s found but not required. Attempting to rename file to %s", getModId(), mlPropBackup.getName());
234                    boolean renamed = modConfig.renameTo(mlPropBackup);
235                    if (renamed)
236                    {
237                        FMLLog.fine("Unused MLProp configuration file for %s renamed successfully to %s", getModId(), mlPropBackup.getName());
238                    }
239                    else
240                    {
241                        FMLLog.fine("Unused MLProp configuration file for %s renamed UNSUCCESSFULLY to %s", getModId(), mlPropBackup.getName());
242                    }
243    
244                    return;
245                }
246                try
247                {
248                    FileWriter configWriter = new FileWriter(modConfig);
249                    props.store(configWriter, comments.toString());
250                    configWriter.close();
251                    FMLLog.fine("Configuration for %s written to %s", getModId(), modConfig.getName());
252                }
253                catch (IOException e)
254                {
255                    FMLLog.log(Level.SEVERE, e, "Error trying to write the config file %s", modConfig.getName());
256                    throw new LoaderException(e);
257                }
258            }
259        }
260    
261        private Object parseValue(String val, ModProperty property, Class<?> type, String propertyName)
262        {
263            if (type.isAssignableFrom(String.class))
264            {
265                return (String)val;
266            }
267            else if (type.isAssignableFrom(Boolean.TYPE) || type.isAssignableFrom(Boolean.class))
268            {
269                return Boolean.parseBoolean(val);
270            }
271            else if (Number.class.isAssignableFrom(type) || type.isPrimitive())
272            {
273                Number n = null;
274    
275                if (type.isAssignableFrom(Double.TYPE) || Double.class.isAssignableFrom(type))
276                {
277                    n = Double.parseDouble(val);
278                }
279                else if (type.isAssignableFrom(Float.TYPE) || Float.class.isAssignableFrom(type))
280                {
281                    n = Float.parseFloat(val);
282                }
283                else if (type.isAssignableFrom(Long.TYPE) || Long.class.isAssignableFrom(type))
284                {
285                    n = Long.parseLong(val);
286                }
287                else if (type.isAssignableFrom(Integer.TYPE) || Integer.class.isAssignableFrom(type))
288                {
289                    n = Integer.parseInt(val);
290                }
291                else if (type.isAssignableFrom(Short.TYPE) || Short.class.isAssignableFrom(type))
292                {
293                    n = Short.parseShort(val);
294                }
295                else if (type.isAssignableFrom(Byte.TYPE) || Byte.class.isAssignableFrom(type))
296                {
297                    n = Byte.parseByte(val);
298                }
299                else
300                {
301                    throw new IllegalArgumentException(String.format("MLProp declared on %s of type %s, an unsupported type",propertyName, type.getName()));
302                }
303    
304                double dVal = n.doubleValue();
305                if ((property.min()!=Double.MIN_VALUE && dVal < property.min()) || (property.max()!=Double.MAX_VALUE && dVal > property.max()))
306                {
307                    FMLLog.warning("Configuration for %s.%s found value %s outside acceptable range %s,%s", modClazzName,propertyName, n, property.min(), property.max());
308                    return null;
309                }
310                else
311                {
312                    return n;
313                }
314            }
315    
316            throw new IllegalArgumentException(String.format("MLProp declared on %s of type %s, an unsupported type",propertyName, type.getName()));
317        }
318        private String extractValue(Object value)
319        {
320            if (String.class.isInstance(value))
321            {
322                return (String)value;
323            }
324            else if (Number.class.isInstance(value) || Boolean.class.isInstance(value))
325            {
326                return String.valueOf(value);
327            }
328            else
329            {
330                throw new IllegalArgumentException("MLProp declared on non-standard type");
331            }
332        }
333    
334        @Override
335        public String getName()
336        {
337            return mod != null ? mod.getName() : modId;
338        }
339    
340        @Deprecated
341        public static ModContainer findContainerFor(BaseModProxy mod)
342        {
343            return FMLCommonHandler.instance().findContainerFor(mod);
344        }
345    
346        @Override
347        public String getSortingRules()
348        {
349            return sortingProperties;
350        }
351    
352        @Override
353        public boolean matches(Object mod)
354        {
355            return this.mod == mod;
356        }
357    
358        /**
359         * Find all the BaseMods in the system
360         * @param <A>
361         */
362        public static <A extends BaseModProxy> List<A> findAll(Class<A> clazz)
363        {
364            ArrayList<A> modList = new ArrayList<A>();
365    
366            for (ModContainer mc : Loader.instance().getActiveModList())
367            {
368                if (mc instanceof ModLoaderModContainer && mc.getMod()!=null)
369                {
370                    modList.add((A)((ModLoaderModContainer)mc).mod);
371                }
372            }
373    
374            return modList;
375        }
376    
377        @Override
378        public File getSource()
379        {
380            return modSource;
381        }
382    
383        @Override
384        public Object getMod()
385        {
386            return mod;
387        }
388    
389        @Override
390        public Set<ArtifactVersion> getRequirements()
391        {
392            return requirements;
393        }
394    
395        @Override
396        public List<ArtifactVersion> getDependants()
397        {
398            return dependants;
399        }
400    
401        @Override
402        public List<ArtifactVersion> getDependencies()
403        {
404            return dependencies;
405        }
406    
407    
408        public String toString()
409        {
410            return modId;
411        }
412    
413        @Override
414        public ModMetadata getMetadata()
415        {
416            return metadata;
417        }
418    
419        @Override
420        public String getVersion()
421        {
422            if (mod == null || mod.getVersion() == null)
423            {
424                return "Not available";
425            }
426            return mod.getVersion();
427        }
428    
429        public BaseModTicker getGameTickHandler()
430        {
431            return this.gameTickHandler;
432        }
433    
434        public BaseModTicker getGUITickHandler()
435        {
436            return this.guiTickHandler;
437        }
438    
439        @Override
440        public String getModId()
441        {
442            return modId;
443        }
444    
445        @Override
446        public void bindMetadata(MetadataCollection mc)
447        {
448            Map<String, Object> dummyMetadata = ImmutableMap.<String,Object>builder().put("name", modId).put("version", "1.0").build();
449            this.metadata = mc.getMetadataForId(modId, dummyMetadata);
450            Loader.instance().computeDependencies(sortingProperties, getRequirements(), getDependencies(), getDependants());
451        }
452    
453        @Override
454        public void setEnabledState(boolean enabled)
455        {
456            this.enabled = enabled;
457        }
458    
459        @Override
460        public boolean registerBus(EventBus bus, LoadController controller)
461        {
462            if (this.enabled)
463            {
464                FMLLog.fine("Enabling mod %s", getModId());
465                this.bus = bus;
466                this.controller = controller;
467                bus.register(this);
468                return true;
469            }
470            else
471            {
472                return false;
473            }
474        }
475    
476        // Lifecycle mod events
477    
478        @Subscribe
479        public void constructMod(FMLConstructionEvent event)
480        {
481            try
482            {
483                ModClassLoader modClassLoader = event.getModClassLoader();
484                modClassLoader.addFile(modSource);
485                EnumSet<TickType> ticks = EnumSet.noneOf(TickType.class);
486                this.gameTickHandler = new BaseModTicker(ticks, false);
487                this.guiTickHandler = new BaseModTicker(ticks.clone(), true);
488                Class<? extends BaseModProxy> modClazz = (Class<? extends BaseModProxy>) modClassLoader.loadBaseModClass(modClazzName);
489                configureMod(modClazz, event.getASMHarvestedData());
490                isNetworkMod = FMLNetworkHandler.instance().registerNetworkMod(this, modClazz, event.getASMHarvestedData());
491                ModLoaderNetworkHandler dummyHandler = null;
492                if (!isNetworkMod)
493                {
494                    FMLLog.fine("Injecting dummy network mod handler for BaseMod %s", getModId());
495                    dummyHandler = new ModLoaderNetworkHandler(this);
496                    FMLNetworkHandler.instance().registerNetworkMod(dummyHandler);
497                }
498                Constructor<? extends BaseModProxy> ctor = modClazz.getConstructor();
499                ctor.setAccessible(true);
500                mod = modClazz.newInstance();
501                if (dummyHandler != null)
502                {
503                    dummyHandler.setBaseMod(mod);
504                }
505                ProxyInjector.inject(this, event.getASMHarvestedData(), FMLCommonHandler.instance().getSide());
506            }
507            catch (Exception e)
508            {
509                controller.errorOccurred(this, e);
510                Throwables.propagateIfPossible(e);
511            }
512        }
513    
514        @Subscribe
515        public void preInit(FMLPreInitializationEvent event)
516        {
517            try
518            {
519                this.gameTickHandler.setMod(mod);
520                this.guiTickHandler.setMod(mod);
521                TickRegistry.registerTickHandler(this.gameTickHandler, Side.CLIENT);
522                TickRegistry.registerTickHandler(this.guiTickHandler, Side.CLIENT);
523                GameRegistry.registerWorldGenerator(ModLoaderHelper.buildWorldGenHelper(mod));
524                GameRegistry.registerFuelHandler(ModLoaderHelper.buildFuelHelper(mod));
525                GameRegistry.registerCraftingHandler(ModLoaderHelper.buildCraftingHelper(mod));
526                GameRegistry.registerPickupHandler(ModLoaderHelper.buildPickupHelper(mod));
527                GameRegistry.registerDispenserHandler(ModLoaderHelper.buildDispenseHelper(mod));
528                NetworkRegistry.instance().registerChatListener(ModLoaderHelper.buildChatListener(mod));
529                NetworkRegistry.instance().registerConnectionHandler(ModLoaderHelper.buildConnectionHelper(mod));
530            }
531            catch (Exception e)
532            {
533                controller.errorOccurred(this, e);
534                Throwables.propagateIfPossible(e);
535            }
536        }
537    
538    
539        @Subscribe
540        public void init(FMLInitializationEvent event)
541        {
542            try
543            {
544                mod.load();
545            }
546            catch (Throwable t)
547            {
548                controller.errorOccurred(this, t);
549                Throwables.propagateIfPossible(t);
550            }
551        }
552    
553        @Subscribe
554        public void postInit(FMLPostInitializationEvent event)
555        {
556            try
557            {
558                mod.modsLoaded();
559            }
560            catch (Throwable t)
561            {
562                controller.errorOccurred(this, t);
563                Throwables.propagateIfPossible(t);
564            }
565        }
566    
567        @Subscribe
568        public void loadComplete(FMLLoadCompleteEvent complete)
569        {
570            ModLoaderHelper.finishModLoading(this);
571        }
572    
573        @Subscribe
574        public void serverStarting(FMLServerStartingEvent evt)
575        {
576            for (ICommand cmd : serverCommands)
577            {
578                evt.registerServerCommand(cmd);
579            }
580        }
581        @Override
582        public ArtifactVersion getProcessedVersion()
583        {
584            if (processedVersion == null)
585            {
586                processedVersion = new DefaultArtifactVersion(modId, getVersion());
587            }
588            return processedVersion;
589        }
590    
591        @Override
592        public boolean isImmutable()
593        {
594            return false;
595        }
596    
597        @Override
598        public boolean isNetworkMod()
599        {
600            return this.isNetworkMod;
601        }
602    
603        @Override
604        public String getDisplayVersion()
605        {
606            return metadata!=null ? metadata.version : getVersion();
607        }
608    
609        public void addServerCommand(ICommand command)
610        {
611            serverCommands .add(command);
612        }
613    
614        @Override
615        public VersionRange acceptableMinecraftVersionRange()
616        {
617            return Loader.instance().getMinecraftModContainer().getStaticVersionRange();
618        }
619    }