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