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