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}