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