001/*
002 * Forge Mod Loader
003 * Copyright (c) 2012-2013 cpw.
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser Public License v2.1
006 * which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
008 *
009 * Contributors:
010 *     cpw - implementation
011 */
012
013package cpw.mods.fml.common.modloader;
014
015import java.io.File;
016import java.io.FileReader;
017import java.io.FileWriter;
018import java.io.IOException;
019import java.lang.reflect.Constructor;
020import java.lang.reflect.Field;
021import java.lang.reflect.Modifier;
022import java.security.cert.Certificate;
023import java.util.ArrayList;
024import java.util.EnumSet;
025import java.util.List;
026import java.util.Map;
027import java.util.Properties;
028import java.util.Set;
029import java.util.logging.Level;
030
031import net.minecraft.command.ICommand;
032
033import com.google.common.base.Strings;
034import com.google.common.base.Throwables;
035import com.google.common.collect.ImmutableMap;
036import com.google.common.collect.Lists;
037import com.google.common.collect.Sets;
038import com.google.common.eventbus.EventBus;
039import com.google.common.eventbus.Subscribe;
040
041import cpw.mods.fml.common.FMLCommonHandler;
042import cpw.mods.fml.common.FMLLog;
043import cpw.mods.fml.common.LoadController;
044import cpw.mods.fml.common.Loader;
045import cpw.mods.fml.common.LoaderException;
046import cpw.mods.fml.common.MetadataCollection;
047import cpw.mods.fml.common.ModClassLoader;
048import cpw.mods.fml.common.ModContainer;
049import cpw.mods.fml.common.ModMetadata;
050import cpw.mods.fml.common.ProxyInjector;
051import cpw.mods.fml.common.TickType;
052import cpw.mods.fml.common.discovery.ASMDataTable;
053import cpw.mods.fml.common.discovery.ASMDataTable.ASMData;
054import cpw.mods.fml.common.discovery.ContainerType;
055import cpw.mods.fml.common.event.FMLConstructionEvent;
056import cpw.mods.fml.common.event.FMLInitializationEvent;
057import cpw.mods.fml.common.event.FMLLoadCompleteEvent;
058import cpw.mods.fml.common.event.FMLPostInitializationEvent;
059import cpw.mods.fml.common.event.FMLPreInitializationEvent;
060import cpw.mods.fml.common.event.FMLServerStartingEvent;
061import cpw.mods.fml.common.network.FMLNetworkHandler;
062import cpw.mods.fml.common.network.NetworkRegistry;
063import cpw.mods.fml.common.registry.GameRegistry;
064import cpw.mods.fml.common.registry.TickRegistry;
065import cpw.mods.fml.common.versioning.ArtifactVersion;
066import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
067import cpw.mods.fml.common.versioning.VersionRange;
068import cpw.mods.fml.relauncher.Side;
069
070public 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    @Override
341    public String getSortingRules()
342    {
343        return sortingProperties;
344    }
345
346    @Override
347    public boolean matches(Object mod)
348    {
349        return this.mod == mod;
350    }
351
352    /**
353     * Find all the BaseMods in the system
354     * @param <A>
355     */
356    public static <A extends BaseModProxy> List<A> findAll(Class<A> clazz)
357    {
358        ArrayList<A> modList = new ArrayList<A>();
359
360        for (ModContainer mc : Loader.instance().getActiveModList())
361        {
362            if (mc instanceof ModLoaderModContainer && mc.getMod()!=null)
363            {
364                modList.add((A)((ModLoaderModContainer)mc).mod);
365            }
366        }
367
368        return modList;
369    }
370
371    @Override
372    public File getSource()
373    {
374        return modSource;
375    }
376
377    @Override
378    public Object getMod()
379    {
380        return mod;
381    }
382
383    @Override
384    public Set<ArtifactVersion> getRequirements()
385    {
386        return requirements;
387    }
388
389    @Override
390    public List<ArtifactVersion> getDependants()
391    {
392        return dependants;
393    }
394
395    @Override
396    public List<ArtifactVersion> getDependencies()
397    {
398        return dependencies;
399    }
400
401
402    public String toString()
403    {
404        return modId;
405    }
406
407    @Override
408    public ModMetadata getMetadata()
409    {
410        return metadata;
411    }
412
413    @Override
414    public String getVersion()
415    {
416        if (mod == null || mod.getVersion() == null)
417        {
418            return "Not available";
419        }
420        return mod.getVersion();
421    }
422
423    public BaseModTicker getGameTickHandler()
424    {
425        return this.gameTickHandler;
426    }
427
428    public BaseModTicker getGUITickHandler()
429    {
430        return this.guiTickHandler;
431    }
432
433    @Override
434    public String getModId()
435    {
436        return modId;
437    }
438
439    @Override
440    public void bindMetadata(MetadataCollection mc)
441    {
442        Map<String, Object> dummyMetadata = ImmutableMap.<String,Object>builder().put("name", modId).put("version", "1.0").build();
443        this.metadata = mc.getMetadataForId(modId, dummyMetadata);
444        Loader.instance().computeDependencies(sortingProperties, getRequirements(), getDependencies(), getDependants());
445    }
446
447    @Override
448    public void setEnabledState(boolean enabled)
449    {
450        this.enabled = enabled;
451    }
452
453    @Override
454    public boolean registerBus(EventBus bus, LoadController controller)
455    {
456        if (this.enabled)
457        {
458            FMLLog.fine("Enabling mod %s", getModId());
459            this.bus = bus;
460            this.controller = controller;
461            bus.register(this);
462            return true;
463        }
464        else
465        {
466            return false;
467        }
468    }
469
470    // Lifecycle mod events
471
472    @Subscribe
473    public void constructMod(FMLConstructionEvent event)
474    {
475        try
476        {
477            ModClassLoader modClassLoader = event.getModClassLoader();
478            modClassLoader.addFile(modSource);
479            EnumSet<TickType> ticks = EnumSet.noneOf(TickType.class);
480            this.gameTickHandler = new BaseModTicker(ticks, false);
481            this.guiTickHandler = new BaseModTicker(ticks.clone(), true);
482            Class<? extends BaseModProxy> modClazz = (Class<? extends BaseModProxy>) modClassLoader.loadBaseModClass(modClazzName);
483            configureMod(modClazz, event.getASMHarvestedData());
484            isNetworkMod = FMLNetworkHandler.instance().registerNetworkMod(this, modClazz, event.getASMHarvestedData());
485            ModLoaderNetworkHandler dummyHandler = null;
486            if (!isNetworkMod)
487            {
488                FMLLog.fine("Injecting dummy network mod handler for BaseMod %s", getModId());
489                dummyHandler = new ModLoaderNetworkHandler(this);
490                FMLNetworkHandler.instance().registerNetworkMod(dummyHandler);
491            }
492            Constructor<? extends BaseModProxy> ctor = modClazz.getConstructor();
493            ctor.setAccessible(true);
494            mod = modClazz.newInstance();
495            if (dummyHandler != null)
496            {
497                dummyHandler.setBaseMod(mod);
498            }
499            ProxyInjector.inject(this, event.getASMHarvestedData(), FMLCommonHandler.instance().getSide());
500        }
501        catch (Exception e)
502        {
503            controller.errorOccurred(this, e);
504            Throwables.propagateIfPossible(e);
505        }
506    }
507
508    @Subscribe
509    public void preInit(FMLPreInitializationEvent event)
510    {
511        try
512        {
513            this.gameTickHandler.setMod(mod);
514            this.guiTickHandler.setMod(mod);
515            TickRegistry.registerTickHandler(this.gameTickHandler, Side.CLIENT);
516            TickRegistry.registerTickHandler(this.guiTickHandler, Side.CLIENT);
517            GameRegistry.registerWorldGenerator(ModLoaderHelper.buildWorldGenHelper(mod));
518            GameRegistry.registerFuelHandler(ModLoaderHelper.buildFuelHelper(mod));
519            GameRegistry.registerCraftingHandler(ModLoaderHelper.buildCraftingHelper(mod));
520            GameRegistry.registerPickupHandler(ModLoaderHelper.buildPickupHelper(mod));
521            NetworkRegistry.instance().registerChatListener(ModLoaderHelper.buildChatListener(mod));
522            NetworkRegistry.instance().registerConnectionHandler(ModLoaderHelper.buildConnectionHelper(mod));
523        }
524        catch (Exception e)
525        {
526            controller.errorOccurred(this, e);
527            Throwables.propagateIfPossible(e);
528        }
529    }
530
531
532    @Subscribe
533    public void init(FMLInitializationEvent event)
534    {
535        try
536        {
537            mod.load();
538        }
539        catch (Throwable t)
540        {
541            controller.errorOccurred(this, t);
542            Throwables.propagateIfPossible(t);
543        }
544    }
545
546    @Subscribe
547    public void postInit(FMLPostInitializationEvent event)
548    {
549        try
550        {
551            mod.modsLoaded();
552        }
553        catch (Throwable t)
554        {
555            controller.errorOccurred(this, t);
556            Throwables.propagateIfPossible(t);
557        }
558    }
559
560    @Subscribe
561    public void loadComplete(FMLLoadCompleteEvent complete)
562    {
563        ModLoaderHelper.finishModLoading(this);
564    }
565
566    @Subscribe
567    public void serverStarting(FMLServerStartingEvent evt)
568    {
569        for (ICommand cmd : serverCommands)
570        {
571            evt.registerServerCommand(cmd);
572        }
573    }
574    @Override
575    public ArtifactVersion getProcessedVersion()
576    {
577        if (processedVersion == null)
578        {
579            processedVersion = new DefaultArtifactVersion(modId, getVersion());
580        }
581        return processedVersion;
582    }
583
584    @Override
585    public boolean isImmutable()
586    {
587        return false;
588    }
589
590    @Override
591    public boolean isNetworkMod()
592    {
593        return this.isNetworkMod;
594    }
595
596    @Override
597    public String getDisplayVersion()
598    {
599        return metadata!=null ? metadata.version : getVersion();
600    }
601
602    public void addServerCommand(ICommand command)
603    {
604        serverCommands .add(command);
605    }
606
607    @Override
608    public VersionRange acceptableMinecraftVersionRange()
609    {
610        return Loader.instance().getMinecraftModContainer().getStaticVersionRange();
611    }
612    @Override
613    public Certificate getSigningCertificate()
614    {
615        return null;
616    }
617}