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.src.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    
069    public class ModLoaderModContainer implements ModContainer
070    {
071        public BaseModProxy mod;
072        private File modSource;
073        public Set<ArtifactVersion> requirements = Sets.newHashSet();
074        public ArrayList<ArtifactVersion> dependencies = Lists.newArrayList();
075        public ArrayList<ArtifactVersion> dependants = Lists.newArrayList();
076        private ContainerType sourceType;
077        private ModMetadata metadata;
078        private ProxyInjector sidedProxy;
079        private BaseModTicker gameTickHandler;
080        private BaseModTicker guiTickHandler;
081        private String modClazzName;
082        private String modId;
083        private EventBus bus;
084        private LoadController controller;
085        private boolean enabled = true;
086        private String sortingProperties;
087        private ArtifactVersion processedVersion;
088        private boolean isNetworkMod;
089        private List<ICommand> serverCommands = Lists.newArrayList();
090    
091        public ModLoaderModContainer(String className, File modSource, String sortingProperties)
092        {
093            this.modClazzName = className;
094            this.modSource = modSource;
095            this.modId = className.contains(".") ? className.substring(className.lastIndexOf('.')+1) : className;
096            this.sortingProperties = Strings.isNullOrEmpty(sortingProperties) ? "" : sortingProperties;
097        }
098    
099        /**
100         * We only instantiate this for "not mod mods"
101         * @param instance
102         */
103        ModLoaderModContainer(BaseModProxy instance) {
104            this.mod=instance;
105            this.gameTickHandler = new BaseModTicker(instance, false);
106            this.guiTickHandler = new BaseModTicker(instance, true);
107        }
108    
109        /**
110         *
111         */
112        private void configureMod(Class<? extends BaseModProxy> modClazz, ASMDataTable asmData)
113        {
114            File configDir = Loader.instance().getConfigDir();
115            File modConfig = new File(configDir, String.format("%s.cfg", getModId()));
116            Properties props = new Properties();
117    
118            boolean existingConfigFound = false;
119            boolean mlPropFound = false;
120    
121            if (modConfig.exists())
122            {
123                try
124                {
125                    FMLLog.fine("Reading existing configuration file for %s : %s", getModId(), modConfig.getName());
126                    FileReader configReader = new FileReader(modConfig);
127                    props.load(configReader);
128                    configReader.close();
129                }
130                catch (Exception e)
131                {
132                    FMLLog.log(Level.SEVERE, e, "Error occured reading mod configuration file %s", modConfig.getName());
133                    throw new LoaderException(e);
134                }
135                existingConfigFound = true;
136            }
137    
138            StringBuffer comments = new StringBuffer();
139            comments.append("MLProperties: name (type:default) min:max -- information\n");
140    
141    
142            List<ModProperty> mlPropFields = Lists.newArrayList();
143            try
144            {
145                for (ASMData dat : Sets.union(asmData.getAnnotationsFor(this).get("net.minecraft.src.MLProp"), asmData.getAnnotationsFor(this).get("MLProp")))
146                {
147                    if (dat.getClassName().equals(modClazzName))
148                    {
149                        try
150                        {
151                            mlPropFields.add(new ModProperty(modClazz.getDeclaredField(dat.getObjectName()), dat.getAnnotationInfo()));
152                            FMLLog.finest("Found an MLProp field %s in %s", dat.getObjectName(), getModId());
153                        }
154                        catch (Exception e)
155                        {
156                            FMLLog.log(Level.WARNING, e, "An error occured trying to access field %s in mod %s", dat.getObjectName(), getModId());
157                        }
158                    }
159                }
160                for (ModProperty property : mlPropFields)
161                {
162                    if (!Modifier.isStatic(property.field().getModifiers()))
163                    {
164                        FMLLog.info("The MLProp field %s in mod %s appears not to be static", property.field().getName(), getModId());
165                        continue;
166                    }
167                    FMLLog.finest("Considering MLProp field %s", property.field().getName());
168                    Field f = property.field();
169                    String propertyName = !Strings.nullToEmpty(property.name()).isEmpty() ? property.name() : f.getName();
170                    String propertyValue = null;
171                    Object defaultValue = null;
172    
173                    try
174                    {
175                        defaultValue = f.get(null);
176                        propertyValue = props.getProperty(propertyName, extractValue(defaultValue));
177                        Object currentValue = parseValue(propertyValue, property, f.getType(), propertyName);
178                        FMLLog.finest("Configuration for %s.%s found values default: %s, configured: %s, interpreted: %s", modClazzName, propertyName, defaultValue, propertyValue, currentValue);
179    
180                        if (currentValue != null && !currentValue.equals(defaultValue))
181                        {
182                            FMLLog.finest("Configuration for %s.%s value set to: %s", modClazzName, propertyName, currentValue);
183                            f.set(null, currentValue);
184                        }
185                    }
186                    catch (Exception e)
187                    {
188                        FMLLog.log(Level.SEVERE, e, "Invalid configuration found for %s in %s", propertyName, modConfig.getName());
189                        throw new LoaderException(e);
190                    }
191                    finally
192                    {
193                        comments.append(String.format("MLProp : %s (%s:%s", propertyName, f.getType().getName(), defaultValue));
194    
195                        if (property.min() != Double.MIN_VALUE)
196                        {
197                            comments.append(",>=").append(String.format("%.1f", property.min()));
198                        }
199    
200                        if (property.max() != Double.MAX_VALUE)
201                        {
202                            comments.append(",<=").append(String.format("%.1f", property.max()));
203                        }
204    
205                        comments.append(")");
206    
207                        if (!Strings.nullToEmpty(property.info()).isEmpty())
208                        {
209                            comments.append(" -- ").append(property.info());
210                        }
211    
212                        if (propertyValue != null)
213                        {
214                            props.setProperty(propertyName, extractValue(propertyValue));
215                        }
216                        comments.append("\n");
217                    }
218                    mlPropFound = true;
219                }
220            }
221            finally
222            {
223                if (!mlPropFound && !existingConfigFound)
224                {
225                    FMLLog.fine("No MLProp configuration for %s found or required. No file written", getModId());
226                    return;
227                }
228    
229                if (!mlPropFound && existingConfigFound)
230                {
231                    File mlPropBackup = new File(modConfig.getParent(),modConfig.getName()+".bak");
232                    FMLLog.fine("MLProp configuration file for %s found but not required. Attempting to rename file to %s", getModId(), mlPropBackup.getName());
233                    boolean renamed = modConfig.renameTo(mlPropBackup);
234                    if (renamed)
235                    {
236                        FMLLog.fine("Unused MLProp configuration file for %s renamed successfully to %s", getModId(), mlPropBackup.getName());
237                    }
238                    else
239                    {
240                        FMLLog.fine("Unused MLProp configuration file for %s renamed UNSUCCESSFULLY to %s", getModId(), mlPropBackup.getName());
241                    }
242    
243                    return;
244                }
245                try
246                {
247                    FileWriter configWriter = new FileWriter(modConfig);
248                    props.store(configWriter, comments.toString());
249                    configWriter.close();
250                    FMLLog.fine("Configuration for %s written to %s", getModId(), modConfig.getName());
251                }
252                catch (IOException e)
253                {
254                    FMLLog.log(Level.SEVERE, e, "Error trying to write the config file %s", modConfig.getName());
255                    throw new LoaderException(e);
256                }
257            }
258        }
259    
260        private Object parseValue(String val, ModProperty property, Class<?> type, String propertyName)
261        {
262            if (type.isAssignableFrom(String.class))
263            {
264                return (String)val;
265            }
266            else if (type.isAssignableFrom(Boolean.TYPE) || type.isAssignableFrom(Boolean.class))
267            {
268                return Boolean.parseBoolean(val);
269            }
270            else if (Number.class.isAssignableFrom(type) || type.isPrimitive())
271            {
272                Number n = null;
273    
274                if (type.isAssignableFrom(Double.TYPE) || Double.class.isAssignableFrom(type))
275                {
276                    n = Double.parseDouble(val);
277                }
278                else if (type.isAssignableFrom(Float.TYPE) || Float.class.isAssignableFrom(type))
279                {
280                    n = Float.parseFloat(val);
281                }
282                else if (type.isAssignableFrom(Long.TYPE) || Long.class.isAssignableFrom(type))
283                {
284                    n = Long.parseLong(val);
285                }
286                else if (type.isAssignableFrom(Integer.TYPE) || Integer.class.isAssignableFrom(type))
287                {
288                    n = Integer.parseInt(val);
289                }
290                else if (type.isAssignableFrom(Short.TYPE) || Short.class.isAssignableFrom(type))
291                {
292                    n = Short.parseShort(val);
293                }
294                else if (type.isAssignableFrom(Byte.TYPE) || Byte.class.isAssignableFrom(type))
295                {
296                    n = Byte.parseByte(val);
297                }
298                else
299                {
300                    throw new IllegalArgumentException(String.format("MLProp declared on %s of type %s, an unsupported type",propertyName, type.getName()));
301                }
302    
303                double dVal = n.doubleValue();
304                if ((property.min()!=Double.MIN_VALUE && dVal < property.min()) || (property.max()!=Double.MAX_VALUE && dVal > property.max()))
305                {
306                    FMLLog.warning("Configuration for %s.%s found value %s outside acceptable range %s,%s", modClazzName,propertyName, n, property.min(), property.max());
307                    return null;
308                }
309                else
310                {
311                    return n;
312                }
313            }
314    
315            throw new IllegalArgumentException(String.format("MLProp declared on %s of type %s, an unsupported type",propertyName, type.getName()));
316        }
317        private String extractValue(Object value)
318        {
319            if (String.class.isInstance(value))
320            {
321                return (String)value;
322            }
323            else if (Number.class.isInstance(value) || Boolean.class.isInstance(value))
324            {
325                return String.valueOf(value);
326            }
327            else
328            {
329                throw new IllegalArgumentException("MLProp declared on non-standard type");
330            }
331        }
332    
333        @Override
334        public String getName()
335        {
336            return mod != null ? mod.getName() : modId;
337        }
338    
339        @Deprecated
340        public static ModContainer findContainerFor(BaseModProxy mod)
341        {
342            return FMLCommonHandler.instance().findContainerFor(mod);
343        }
344    
345        @Override
346        public String getSortingRules()
347        {
348            return sortingProperties;
349        }
350    
351        @Override
352        public boolean matches(Object mod)
353        {
354            return this.mod == mod;
355        }
356    
357        /**
358         * Find all the BaseMods in the system
359         * @param <A>
360         * @return
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        /**
430         * @return
431         */
432        public BaseModTicker getGameTickHandler()
433        {
434            return this.gameTickHandler;
435        }
436        /**
437         * @return
438         */
439        public BaseModTicker getGUITickHandler()
440        {
441            return this.guiTickHandler;
442        }
443    
444        @Override
445        public String getModId()
446        {
447            return modId;
448        }
449    
450        @Override
451        public void bindMetadata(MetadataCollection mc)
452        {
453            Map<String, Object> dummyMetadata = ImmutableMap.<String,Object>builder().put("name", modId).put("version", "1.0").build();
454            this.metadata = mc.getMetadataForId(modId, dummyMetadata);
455            Loader.instance().computeDependencies(sortingProperties, getRequirements(), getDependencies(), getDependants());
456        }
457    
458        @Override
459        public void setEnabledState(boolean enabled)
460        {
461            this.enabled = enabled;
462        }
463    
464        @Override
465        public boolean registerBus(EventBus bus, LoadController controller)
466        {
467            if (this.enabled)
468            {
469                FMLLog.fine("Enabling mod %s", getModId());
470                this.bus = bus;
471                this.controller = controller;
472                bus.register(this);
473                return true;
474            }
475            else
476            {
477                return false;
478            }
479        }
480    
481        // Lifecycle mod events
482    
483        @Subscribe
484        public void constructMod(FMLConstructionEvent event)
485        {
486            try
487            {
488                ModClassLoader modClassLoader = event.getModClassLoader();
489                modClassLoader.addFile(modSource);
490                EnumSet<TickType> ticks = EnumSet.noneOf(TickType.class);
491                this.gameTickHandler = new BaseModTicker(ticks, false);
492                this.guiTickHandler = new BaseModTicker(ticks.clone(), true);
493                Class<? extends BaseModProxy> modClazz = (Class<? extends BaseModProxy>) modClassLoader.loadBaseModClass(modClazzName);
494                configureMod(modClazz, event.getASMHarvestedData());
495                isNetworkMod = FMLNetworkHandler.instance().registerNetworkMod(this, modClazz, event.getASMHarvestedData());
496                ModLoaderNetworkHandler dummyHandler = null;
497                if (!isNetworkMod)
498                {
499                    FMLLog.fine("Injecting dummy network mod handler for BaseMod %s", getModId());
500                    dummyHandler = new ModLoaderNetworkHandler(this);
501                    FMLNetworkHandler.instance().registerNetworkMod(dummyHandler);
502                }
503                Constructor<? extends BaseModProxy> ctor = modClazz.getConstructor();
504                ctor.setAccessible(true);
505                mod = modClazz.newInstance();
506                if (dummyHandler != null)
507                {
508                    dummyHandler.setBaseMod(mod);
509                }
510                ProxyInjector.inject(this, event.getASMHarvestedData(), FMLCommonHandler.instance().getSide());
511            }
512            catch (Exception e)
513            {
514                controller.errorOccurred(this, e);
515                Throwables.propagateIfPossible(e);
516            }
517        }
518    
519        @Subscribe
520        public void preInit(FMLPreInitializationEvent event)
521        {
522            try
523            {
524                this.gameTickHandler.setMod(mod);
525                this.guiTickHandler.setMod(mod);
526                TickRegistry.registerTickHandler(this.gameTickHandler, Side.CLIENT);
527                TickRegistry.registerTickHandler(this.guiTickHandler, Side.CLIENT);
528                GameRegistry.registerWorldGenerator(ModLoaderHelper.buildWorldGenHelper(mod));
529                GameRegistry.registerFuelHandler(ModLoaderHelper.buildFuelHelper(mod));
530                GameRegistry.registerCraftingHandler(ModLoaderHelper.buildCraftingHelper(mod));
531                GameRegistry.registerPickupHandler(ModLoaderHelper.buildPickupHelper(mod));
532                GameRegistry.registerDispenserHandler(ModLoaderHelper.buildDispenseHelper(mod));
533                NetworkRegistry.instance().registerConnectionHandler(ModLoaderHelper.buildConnectionHelper(mod));
534            }
535            catch (Exception e)
536            {
537                controller.errorOccurred(this, e);
538                Throwables.propagateIfPossible(e);
539            }
540        }
541    
542    
543        @Subscribe
544        public void init(FMLInitializationEvent event)
545        {
546            try
547            {
548                mod.load();
549            }
550            catch (Throwable t)
551            {
552                controller.errorOccurred(this, t);
553                Throwables.propagateIfPossible(t);
554            }
555        }
556    
557        @Subscribe
558        public void postInit(FMLPostInitializationEvent event)
559        {
560            try
561            {
562                mod.modsLoaded();
563            }
564            catch (Throwable t)
565            {
566                controller.errorOccurred(this, t);
567                Throwables.propagateIfPossible(t);
568            }
569        }
570    
571        @Subscribe
572        public void loadComplete(FMLLoadCompleteEvent complete)
573        {
574            ModLoaderHelper.finishModLoading(this);
575        }
576    
577        @Subscribe
578        public void serverStarting(FMLServerStartingEvent evt)
579        {
580            for (ICommand cmd : serverCommands)
581            {
582                evt.registerServerCommand(cmd);
583            }
584        }
585        @Override
586        public ArtifactVersion getProcessedVersion()
587        {
588            if (processedVersion == null)
589            {
590                processedVersion = new DefaultArtifactVersion(modId, getVersion());
591            }
592            return processedVersion;
593        }
594    
595        @Override
596        public boolean isImmutable()
597        {
598            return false;
599        }
600    
601        @Override
602        public boolean isNetworkMod()
603        {
604            return this.isNetworkMod;
605        }
606    
607        @Override
608        public String getDisplayVersion()
609        {
610            return metadata!=null ? metadata.version : getVersion();
611        }
612    
613        public void addServerCommand(ICommand command)
614        {
615            serverCommands .add(command);
616        }
617    }