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 069 public class ModLoaderModContainer implements ModContainer 070 { 071 public BaseModProxy mod; 072 private File modSource; 073 public Set<ArtifactVersion> requirements = Sets.newHashSet(); 074 public ArrayList<ArtifactVersion> dependencies = Lists.newArrayList(); 075 public ArrayList<ArtifactVersion> dependants = Lists.newArrayList(); 076 private ContainerType sourceType; 077 private ModMetadata metadata; 078 private ProxyInjector sidedProxy; 079 private BaseModTicker gameTickHandler; 080 private BaseModTicker guiTickHandler; 081 private String modClazzName; 082 private String modId; 083 private EventBus bus; 084 private LoadController controller; 085 private boolean enabled = true; 086 private String sortingProperties; 087 private ArtifactVersion processedVersion; 088 private boolean isNetworkMod; 089 private List<ICommand> serverCommands = Lists.newArrayList(); 090 091 public ModLoaderModContainer(String className, File modSource, String sortingProperties) 092 { 093 this.modClazzName = className; 094 this.modSource = modSource; 095 this.modId = className.contains(".") ? className.substring(className.lastIndexOf('.')+1) : className; 096 this.sortingProperties = Strings.isNullOrEmpty(sortingProperties) ? "" : sortingProperties; 097 } 098 099 /** 100 * We only instantiate this for "not mod mods" 101 * @param instance 102 */ 103 ModLoaderModContainer(BaseModProxy instance) { 104 this.mod=instance; 105 this.gameTickHandler = new BaseModTicker(instance, false); 106 this.guiTickHandler = new BaseModTicker(instance, true); 107 } 108 109 /** 110 * 111 */ 112 private void configureMod(Class<? extends BaseModProxy> modClazz, ASMDataTable asmData) 113 { 114 File configDir = Loader.instance().getConfigDir(); 115 File modConfig = new File(configDir, String.format("%s.cfg", getModId())); 116 Properties props = new Properties(); 117 118 boolean existingConfigFound = false; 119 boolean mlPropFound = false; 120 121 if (modConfig.exists()) 122 { 123 try 124 { 125 FMLLog.fine("Reading existing configuration file for %s : %s", getModId(), modConfig.getName()); 126 FileReader configReader = new FileReader(modConfig); 127 props.load(configReader); 128 configReader.close(); 129 } 130 catch (Exception e) 131 { 132 FMLLog.log(Level.SEVERE, e, "Error occured reading mod configuration file %s", modConfig.getName()); 133 throw new LoaderException(e); 134 } 135 existingConfigFound = true; 136 } 137 138 StringBuffer comments = new StringBuffer(); 139 comments.append("MLProperties: name (type:default) min:max -- information\n"); 140 141 142 List<ModProperty> mlPropFields = Lists.newArrayList(); 143 try 144 { 145 for (ASMData dat : Sets.union(asmData.getAnnotationsFor(this).get("net.minecraft.src.MLProp"), asmData.getAnnotationsFor(this).get("MLProp"))) 146 { 147 if (dat.getClassName().equals(modClazzName)) 148 { 149 try 150 { 151 mlPropFields.add(new ModProperty(modClazz.getDeclaredField(dat.getObjectName()), dat.getAnnotationInfo())); 152 FMLLog.finest("Found an MLProp field %s in %s", dat.getObjectName(), getModId()); 153 } 154 catch (Exception e) 155 { 156 FMLLog.log(Level.WARNING, e, "An error occured trying to access field %s in mod %s", dat.getObjectName(), getModId()); 157 } 158 } 159 } 160 for (ModProperty property : mlPropFields) 161 { 162 if (!Modifier.isStatic(property.field().getModifiers())) 163 { 164 FMLLog.info("The MLProp field %s in mod %s appears not to be static", property.field().getName(), getModId()); 165 continue; 166 } 167 FMLLog.finest("Considering MLProp field %s", property.field().getName()); 168 Field f = property.field(); 169 String propertyName = !Strings.nullToEmpty(property.name()).isEmpty() ? property.name() : f.getName(); 170 String propertyValue = null; 171 Object defaultValue = null; 172 173 try 174 { 175 defaultValue = f.get(null); 176 propertyValue = props.getProperty(propertyName, extractValue(defaultValue)); 177 Object currentValue = parseValue(propertyValue, property, f.getType(), propertyName); 178 FMLLog.finest("Configuration for %s.%s found values default: %s, configured: %s, interpreted: %s", modClazzName, propertyName, defaultValue, propertyValue, currentValue); 179 180 if (currentValue != null && !currentValue.equals(defaultValue)) 181 { 182 FMLLog.finest("Configuration for %s.%s value set to: %s", modClazzName, propertyName, currentValue); 183 f.set(null, currentValue); 184 } 185 } 186 catch (Exception e) 187 { 188 FMLLog.log(Level.SEVERE, e, "Invalid configuration found for %s in %s", propertyName, modConfig.getName()); 189 throw new LoaderException(e); 190 } 191 finally 192 { 193 comments.append(String.format("MLProp : %s (%s:%s", propertyName, f.getType().getName(), defaultValue)); 194 195 if (property.min() != Double.MIN_VALUE) 196 { 197 comments.append(",>=").append(String.format("%.1f", property.min())); 198 } 199 200 if (property.max() != Double.MAX_VALUE) 201 { 202 comments.append(",<=").append(String.format("%.1f", property.max())); 203 } 204 205 comments.append(")"); 206 207 if (!Strings.nullToEmpty(property.info()).isEmpty()) 208 { 209 comments.append(" -- ").append(property.info()); 210 } 211 212 if (propertyValue != null) 213 { 214 props.setProperty(propertyName, extractValue(propertyValue)); 215 } 216 comments.append("\n"); 217 } 218 mlPropFound = true; 219 } 220 } 221 finally 222 { 223 if (!mlPropFound && !existingConfigFound) 224 { 225 FMLLog.fine("No MLProp configuration for %s found or required. No file written", getModId()); 226 return; 227 } 228 229 if (!mlPropFound && existingConfigFound) 230 { 231 File mlPropBackup = new File(modConfig.getParent(),modConfig.getName()+".bak"); 232 FMLLog.fine("MLProp configuration file for %s found but not required. Attempting to rename file to %s", getModId(), mlPropBackup.getName()); 233 boolean renamed = modConfig.renameTo(mlPropBackup); 234 if (renamed) 235 { 236 FMLLog.fine("Unused MLProp configuration file for %s renamed successfully to %s", getModId(), mlPropBackup.getName()); 237 } 238 else 239 { 240 FMLLog.fine("Unused MLProp configuration file for %s renamed UNSUCCESSFULLY to %s", getModId(), mlPropBackup.getName()); 241 } 242 243 return; 244 } 245 try 246 { 247 FileWriter configWriter = new FileWriter(modConfig); 248 props.store(configWriter, comments.toString()); 249 configWriter.close(); 250 FMLLog.fine("Configuration for %s written to %s", getModId(), modConfig.getName()); 251 } 252 catch (IOException e) 253 { 254 FMLLog.log(Level.SEVERE, e, "Error trying to write the config file %s", modConfig.getName()); 255 throw new LoaderException(e); 256 } 257 } 258 } 259 260 private Object parseValue(String val, ModProperty property, Class<?> type, String propertyName) 261 { 262 if (type.isAssignableFrom(String.class)) 263 { 264 return (String)val; 265 } 266 else if (type.isAssignableFrom(Boolean.TYPE) || type.isAssignableFrom(Boolean.class)) 267 { 268 return Boolean.parseBoolean(val); 269 } 270 else if (Number.class.isAssignableFrom(type) || type.isPrimitive()) 271 { 272 Number n = null; 273 274 if (type.isAssignableFrom(Double.TYPE) || Double.class.isAssignableFrom(type)) 275 { 276 n = Double.parseDouble(val); 277 } 278 else if (type.isAssignableFrom(Float.TYPE) || Float.class.isAssignableFrom(type)) 279 { 280 n = Float.parseFloat(val); 281 } 282 else if (type.isAssignableFrom(Long.TYPE) || Long.class.isAssignableFrom(type)) 283 { 284 n = Long.parseLong(val); 285 } 286 else if (type.isAssignableFrom(Integer.TYPE) || Integer.class.isAssignableFrom(type)) 287 { 288 n = Integer.parseInt(val); 289 } 290 else if (type.isAssignableFrom(Short.TYPE) || Short.class.isAssignableFrom(type)) 291 { 292 n = Short.parseShort(val); 293 } 294 else if (type.isAssignableFrom(Byte.TYPE) || Byte.class.isAssignableFrom(type)) 295 { 296 n = Byte.parseByte(val); 297 } 298 else 299 { 300 throw new IllegalArgumentException(String.format("MLProp declared on %s of type %s, an unsupported type",propertyName, type.getName())); 301 } 302 303 double dVal = n.doubleValue(); 304 if ((property.min()!=Double.MIN_VALUE && dVal < property.min()) || (property.max()!=Double.MAX_VALUE && dVal > property.max())) 305 { 306 FMLLog.warning("Configuration for %s.%s found value %s outside acceptable range %s,%s", modClazzName,propertyName, n, property.min(), property.max()); 307 return null; 308 } 309 else 310 { 311 return n; 312 } 313 } 314 315 throw new IllegalArgumentException(String.format("MLProp declared on %s of type %s, an unsupported type",propertyName, type.getName())); 316 } 317 private String extractValue(Object value) 318 { 319 if (String.class.isInstance(value)) 320 { 321 return (String)value; 322 } 323 else if (Number.class.isInstance(value) || Boolean.class.isInstance(value)) 324 { 325 return String.valueOf(value); 326 } 327 else 328 { 329 throw new IllegalArgumentException("MLProp declared on non-standard type"); 330 } 331 } 332 333 @Override 334 public String getName() 335 { 336 return mod != null ? mod.getName() : modId; 337 } 338 339 @Deprecated 340 public static ModContainer findContainerFor(BaseModProxy mod) 341 { 342 return FMLCommonHandler.instance().findContainerFor(mod); 343 } 344 345 @Override 346 public String getSortingRules() 347 { 348 return sortingProperties; 349 } 350 351 @Override 352 public boolean matches(Object mod) 353 { 354 return this.mod == mod; 355 } 356 357 /** 358 * Find all the BaseMods in the system 359 * @param <A> 360 * @return 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 /** 430 * @return 431 */ 432 public BaseModTicker getGameTickHandler() 433 { 434 return this.gameTickHandler; 435 } 436 /** 437 * @return 438 */ 439 public BaseModTicker getGUITickHandler() 440 { 441 return this.guiTickHandler; 442 } 443 444 @Override 445 public String getModId() 446 { 447 return modId; 448 } 449 450 @Override 451 public void bindMetadata(MetadataCollection mc) 452 { 453 Map<String, Object> dummyMetadata = ImmutableMap.<String,Object>builder().put("name", modId).put("version", "1.0").build(); 454 this.metadata = mc.getMetadataForId(modId, dummyMetadata); 455 Loader.instance().computeDependencies(sortingProperties, getRequirements(), getDependencies(), getDependants()); 456 } 457 458 @Override 459 public void setEnabledState(boolean enabled) 460 { 461 this.enabled = enabled; 462 } 463 464 @Override 465 public boolean registerBus(EventBus bus, LoadController controller) 466 { 467 if (this.enabled) 468 { 469 FMLLog.fine("Enabling mod %s", getModId()); 470 this.bus = bus; 471 this.controller = controller; 472 bus.register(this); 473 return true; 474 } 475 else 476 { 477 return false; 478 } 479 } 480 481 // Lifecycle mod events 482 483 @Subscribe 484 public void constructMod(FMLConstructionEvent event) 485 { 486 try 487 { 488 ModClassLoader modClassLoader = event.getModClassLoader(); 489 modClassLoader.addFile(modSource); 490 EnumSet<TickType> ticks = EnumSet.noneOf(TickType.class); 491 this.gameTickHandler = new BaseModTicker(ticks, false); 492 this.guiTickHandler = new BaseModTicker(ticks.clone(), true); 493 Class<? extends BaseModProxy> modClazz = (Class<? extends BaseModProxy>) modClassLoader.loadBaseModClass(modClazzName); 494 configureMod(modClazz, event.getASMHarvestedData()); 495 isNetworkMod = FMLNetworkHandler.instance().registerNetworkMod(this, modClazz, event.getASMHarvestedData()); 496 Constructor<? extends BaseModProxy> ctor = modClazz.getConstructor(); 497 ctor.setAccessible(true); 498 mod = modClazz.newInstance(); 499 if (!isNetworkMod) 500 { 501 FMLLog.fine("Injecting dummy network mod handler for BaseMod %s", getModId()); 502 FMLNetworkHandler.instance().registerNetworkMod(new ModLoaderNetworkHandler(this, mod)); 503 } 504 ProxyInjector.inject(this, event.getASMHarvestedData(), FMLCommonHandler.instance().getSide()); 505 } 506 catch (Exception e) 507 { 508 controller.errorOccurred(this, e); 509 Throwables.propagateIfPossible(e); 510 } 511 } 512 513 @Subscribe 514 public void preInit(FMLPreInitializationEvent event) 515 { 516 try 517 { 518 this.gameTickHandler.setMod(mod); 519 this.guiTickHandler.setMod(mod); 520 TickRegistry.registerTickHandler(this.gameTickHandler, Side.CLIENT); 521 TickRegistry.registerTickHandler(this.guiTickHandler, Side.CLIENT); 522 GameRegistry.registerWorldGenerator(ModLoaderHelper.buildWorldGenHelper(mod)); 523 GameRegistry.registerFuelHandler(ModLoaderHelper.buildFuelHelper(mod)); 524 GameRegistry.registerCraftingHandler(ModLoaderHelper.buildCraftingHelper(mod)); 525 GameRegistry.registerPickupHandler(ModLoaderHelper.buildPickupHelper(mod)); 526 GameRegistry.registerDispenserHandler(ModLoaderHelper.buildDispenseHelper(mod)); 527 NetworkRegistry.instance().registerConnectionHandler(ModLoaderHelper.buildConnectionHelper(mod)); 528 } 529 catch (Exception e) 530 { 531 controller.errorOccurred(this, e); 532 Throwables.propagateIfPossible(e); 533 } 534 } 535 536 537 @Subscribe 538 public void init(FMLInitializationEvent event) 539 { 540 try 541 { 542 mod.load(); 543 } 544 catch (Throwable t) 545 { 546 controller.errorOccurred(this, t); 547 Throwables.propagateIfPossible(t); 548 } 549 } 550 551 @Subscribe 552 public void postInit(FMLPostInitializationEvent event) 553 { 554 try 555 { 556 mod.modsLoaded(); 557 } 558 catch (Throwable t) 559 { 560 controller.errorOccurred(this, t); 561 Throwables.propagateIfPossible(t); 562 } 563 } 564 565 @Subscribe 566 public void loadComplete(FMLLoadCompleteEvent complete) 567 { 568 ModLoaderHelper.finishModLoading(this); 569 } 570 571 @Subscribe 572 public void serverStarting(FMLServerStartingEvent evt) 573 { 574 for (ICommand cmd : serverCommands) 575 { 576 evt.registerServerCommand(cmd); 577 } 578 } 579 @Override 580 public ArtifactVersion getProcessedVersion() 581 { 582 if (processedVersion == null) 583 { 584 processedVersion = new DefaultArtifactVersion(modId, getVersion()); 585 } 586 return processedVersion; 587 } 588 589 @Override 590 public boolean isImmutable() 591 { 592 return false; 593 } 594 595 @Override 596 public boolean isNetworkMod() 597 { 598 return this.isNetworkMod; 599 } 600 601 @Override 602 public String getDisplayVersion() 603 { 604 return metadata!=null ? metadata.version : getVersion(); 605 } 606 607 public void addServerCommand(ICommand command) 608 { 609 serverCommands .add(command); 610 } 611 }