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 }