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 }