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.src.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 }