/* * Minecraft Forge * Copyright (c) 2016-2018. * * 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 Software Foundation version 2.1 * of the License. * * 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 A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * 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 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package net.minecraftforge.fml.common; import java.io.File; import java.lang.ref.WeakReference; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.IInventory; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.network.EnumConnectionState; import net.minecraft.network.INetHandler; import net.minecraft.network.NetworkManager; import net.minecraft.network.handshake.client.C00Handshake; import net.minecraft.network.login.server.SPacketDisconnect; import net.minecraft.server.MinecraftServer; import net.minecraft.util.IThreadListener; import net.minecraft.util.text.TextComponentString; import net.minecraft.world.World; import net.minecraft.world.storage.SaveHandler; import net.minecraft.world.storage.WorldInfo; import net.minecraftforge.client.model.animation.Animation; import net.minecraftforge.common.ForgeVersion; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.util.CompoundDataFixer; import net.minecraftforge.fml.LogicalSide; import net.minecraftforge.fml.StartupQuery; import net.minecraftforge.fml.WorldPersistenceHooks; import net.minecraftforge.fml.BrandingControl; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.fml.common.gameevent.InputEvent; import net.minecraftforge.fml.common.gameevent.PlayerEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; import net.minecraftforge.fml.common.gameevent.TickEvent.Phase; import net.minecraftforge.fml.common.network.FMLNetworkEvent; import net.minecraftforge.fml.common.network.NetworkRegistry; import net.minecraftforge.fml.common.thread.SidedThreadGroup; import net.minecraftforge.fml.ModContainer; import net.minecraftforge.fml.server.FMLServerHandler; import org.apache.logging.log4j.Logger; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.MapMaker; import com.google.common.collect.Maps; /** * The main class for non-obfuscated hook handling code * * Anything that doesn't require obfuscated or client/server specific code should * go in this handler * * It also contains a reference to the sided handler instance that is valid * allowing for common code to access specific properties from the obfuscated world * without a direct dependency * * @author cpw * */ public class FMLCommonHandler { /** * The singleton */ private static final FMLCommonHandler INSTANCE = new FMLCommonHandler(); /** * The delegate for side specific data and functions */ private IFMLSidedHandler sidedDelegate; private boolean noForge; private List brandings; private List brandingsNoMC; private Set handlerSet = Collections.newSetFromMap(new MapMaker().weakKeys().makeMap()); private WeakReference handlerToCheck; private IEventBus eventBus = MinecraftForge.EVENT_BUS; private volatile CountDownLatch exitLatch = null; private FMLCommonHandler() { } /** * The FML event bus. Subscribe here for FML related events * * @Deprecated Use {@link MinecraftForge#EVENT_BUS} they're the same thing now * @return the event bus */ @Deprecated public IEventBus bus() { return eventBus; } public List beginLoading(IFMLSidedHandler handler) { sidedDelegate = handler; MinecraftForge.initialize(); // MinecraftForge.registerCrashCallable(); return ImmutableList.of(); } /** * @return the instance */ public static FMLCommonHandler instance() { return INSTANCE; } /** * Find the container that associates with the supplied mod object * @param mod */ public ModContainer findContainerFor(Object mod) { if (mod instanceof String) { return Loader.instance().getIndexedModList().get(mod); } else { return Loader.instance().getReversedModObjectList().get(mod); } } /** * Get the forge mod loader logging instance (goes to the forgemodloader log file) * @return The log instance for the FML log file * * @deprecated Not used in FML, Mods use your own logger, see {@link FMLPreInitializationEvent#getModLog()} */ @Deprecated public Logger getFMLLogger() { return FMLLog.log; } public Side getSide() { return sidedDelegate.getSide(); } /** * Return the effective side for the context in the game. This is dependent * on thread analysis to try and determine whether the code is running in the * server or not. Use at your own risk */ public Side getEffectiveSide() { final ThreadGroup group = Thread.currentThread().getThreadGroup(); return group instanceof SidedThreadGroup ? ((SidedThreadGroup) group).getSide() : Side.CLIENT; } /** * Raise an exception */ public void raiseException(Throwable exception, String message, boolean stopGame) { FMLLog.log.error("Something raised an exception. The message was '{}'. 'stopGame' is {}", stopGame, exception); if (stopGame) { getSidedDelegate().haltGame(message,exception); } } public List getBrandings(boolean includeMC) { if (brandings == null) { BrandingControl.computeBranding(); } return includeMC ? ImmutableList.copyOf(brandings) : ImmutableList.copyOf(brandingsNoMC); } public IFMLSidedHandler getSidedDelegate() { return sidedDelegate; } public void onPostServerTick() { bus().post(new TickEvent.ServerTickEvent(Phase.END)); } /** * Every tick just after world and other ticks occur */ public void onPostWorldTick(World world) { bus().post(new TickEvent.WorldTickEvent(Side.SERVER, Phase.END, world)); } public void onPreServerTick() { bus().post(new TickEvent.ServerTickEvent(Phase.START)); } /** * Every tick just before world and other ticks occur */ public void onPreWorldTick(World world) { bus().post(new TickEvent.WorldTickEvent(Side.SERVER, Phase.START, world)); } public boolean handleServerAboutToStart(MinecraftServer server) { return Loader.instance().serverAboutToStart(server); } public boolean handleServerStarting(MinecraftServer server) { return Loader.instance().serverStarting(server); } public void handleServerStarted() { Loader.instance().serverStarted(); sidedDelegate.allowLogins(); } public void handleServerStopping() { Loader.instance().serverStopping(); } public File getSavesDirectory() { return sidedDelegate.getSavesDirectory(); } public MinecraftServer getMinecraftServerInstance() { return sidedDelegate.getServer(); } public void showGuiScreen(Object clientGuiElement) { sidedDelegate.showGuiScreen(clientGuiElement); } public void queryUser(StartupQuery query) throws InterruptedException { sidedDelegate.queryUser(query); } public void onServerStart(MinecraftServer dedicatedServer) { FMLServerHandler.instance(); sidedDelegate.beginServerLoading(dedicatedServer); } public void onServerStarted() { sidedDelegate.finishServerLoading(); } public void onPreClientTick() { bus().post(new TickEvent.ClientTickEvent(Phase.START)); } public void onPostClientTick() { bus().post(new TickEvent.ClientTickEvent(Phase.END)); } public void onRenderTickStart(float timer) { Animation.setClientPartialTickTime(timer); bus().post(new TickEvent.RenderTickEvent(Phase.START, timer)); } public void onRenderTickEnd(float timer) { bus().post(new TickEvent.RenderTickEvent(Phase.END, timer)); } public void onPlayerPreTick(EntityPlayer player) { bus().post(new TickEvent.PlayerTickEvent(Phase.START, player)); } public void onPlayerPostTick(EntityPlayer player) { bus().post(new TickEvent.PlayerTickEvent(Phase.END, player)); } public void handleWorldDataSave(SaveHandler handler, WorldInfo worldInfo, NBTTagCompound tagCompound) { for (ModContainer mc : Loader.instance().getModList()) { if (mc instanceof InjectedModContainer) { WorldPersistenceHooks.WorldPersistenceHook wac = ((InjectedModContainer)mc).getWrappedWorldAccessContainer(); if (wac != null) { NBTTagCompound dataForWriting = wac.getDataForWriting(handler, worldInfo); tagCompound.setTag(mc.getModId(), dataForWriting); } } } } public void handleWorldDataLoad(SaveHandler handler, WorldInfo worldInfo, NBTTagCompound tagCompound) { if (getEffectiveSide()!=LogicalSide.SERVER) { return; } if (handlerSet.contains(handler)) { return; } handlerSet.add(handler); handlerToCheck = new WeakReference(handler); // for confirmBackupLevelDatUse Map additionalProperties = Maps.newHashMap(); worldInfo.setAdditionalProperties(additionalProperties); for (ModContainer mc : Loader.instance().getModList()) { if (mc instanceof InjectedModContainer) { WorldPersistenceHooks.WorldPersistenceHook wac = ((InjectedModContainer)mc).getWrappedWorldAccessContainer(); if (wac != null) { wac.readData(handler, worldInfo, additionalProperties, tagCompound.getCompoundTag(mc.getModId())); } } } } public void confirmBackupLevelDatUse(SaveHandler handler) { if (handlerToCheck == null || handlerToCheck.get() != handler) { // only run if the save has been initially loaded handlerToCheck = null; return; } String text = "Forge Mod Loader detected that the backup level.dat is being used.\n\n" + "This may happen due to a bug or corruption, continuing can damage\n" + "your world beyond repair or lose data / progress.\n\n" + "It's recommended to create a world backup before continuing."; boolean confirmed = StartupQuery.confirm(text); if (!confirmed) StartupQuery.abort(); } public boolean isDisplayCloseRequested() { return sidedDelegate != null && sidedDelegate.isDisplayCloseRequested(); } public boolean shouldServerBeKilledQuietly() { if (sidedDelegate == null) { return false; } return sidedDelegate.shouldServerShouldBeKilledQuietly(); } /** * Make handleExit() wait for handleServerStopped(). * * For internal use only! */ public void expectServerStopped() { exitLatch = new CountDownLatch(1); } /** * Delayed System.exit() until the server is actually stopped/done saving. * * For internal use only! * * @param retVal Exit code for System.exit() */ public void handleExit(int retVal) { CountDownLatch latch = exitLatch; if (latch != null) { try { FMLLog.log.info("Waiting for the server to terminate/save."); if (!latch.await(10, TimeUnit.SECONDS)) { FMLLog.log.warn("The server didn't stop within 10 seconds, exiting anyway."); } else { FMLLog.log.info("Server terminated."); } } catch (InterruptedException e) { FMLLog.log.warn("Interrupted wait, exiting."); } } System.exit(retVal); } public void handleServerStopped() { sidedDelegate.serverStopped(); MinecraftServer server = getMinecraftServerInstance(); Loader.instance().serverStopped(); // FORCE the internal server to stop: hello optifine workaround! if (server!=null) ObfuscationReflectionHelper.setPrivateValue(MinecraftServer.class, server, false, "field_71316"+"_v", "u", "serverStopped"); // allow any pending exit to continue, clear exitLatch CountDownLatch latch = exitLatch; if (latch != null) { latch.countDown(); exitLatch = null; } } public String getModName() { List modNames = Lists.newArrayListWithExpectedSize(3); modNames.add("fml"); if (!noForge) { modNames.add(ForgeVersion.MOD_ID); } if (Loader.instance().getFMLBrandingProperties().containsKey("snooperbranding")) { modNames.add(Loader.instance().getFMLBrandingProperties().get("snooperbranding")); } return Joiner.on(',').join(modNames); } public void addModToResourcePack(ModContainer container) { sidedDelegate.addModAsResource(container); } public String getCurrentLanguage() { return sidedDelegate.getCurrentLanguage(); } public void bootstrap() { } public NetworkManager getClientToServerNetworkManager() { return sidedDelegate.getClientToServerNetworkManager(); } public void fireMouseInput() { bus().post(new InputEvent.MouseInputEvent()); } public void fireKeyInput() { bus().post(new InputEvent.KeyInputEvent()); } public void firePlayerChangedDimensionEvent(EntityPlayer player, int fromDim, int toDim) { bus().post(new PlayerEvent.PlayerChangedDimensionEvent(player, fromDim, toDim)); } public void firePlayerLoggedIn(EntityPlayer player) { bus().post(new PlayerEvent.PlayerLoggedInEvent(player)); } public void firePlayerLoggedOut(EntityPlayer player) { bus().post(new PlayerEvent.PlayerLoggedOutEvent(player)); } public void firePlayerRespawnEvent(EntityPlayer player, boolean endConquered) { bus().post(new PlayerEvent.PlayerRespawnEvent(player, endConquered)); } public void firePlayerItemPickupEvent(EntityPlayer player, EntityItem item, ItemStack clone) { bus().post(new PlayerEvent.ItemPickupEvent(player, item, clone)); } public void firePlayerCraftingEvent(EntityPlayer player, ItemStack crafted, IInventory craftMatrix) { bus().post(new PlayerEvent.ItemCraftedEvent(player, crafted, craftMatrix)); } public void firePlayerSmeltedEvent(EntityPlayer player, ItemStack smelted) { bus().post(new PlayerEvent.ItemSmeltedEvent(player, smelted)); } public INetHandler getClientPlayHandler() { return sidedDelegate.getClientPlayHandler(); } public void fireNetRegistrationEvent(NetworkManager manager, Set channelSet, String channel, Side side) { sidedDelegate.fireNetRegistrationEvent(bus(), manager, channelSet, channel, side); } public boolean shouldAllowPlayerLogins() { return sidedDelegate.shouldAllowPlayerLogins(); } public void fireServerConnectionEvent(NetworkManager manager) { bus().post(new FMLNetworkEvent.ServerConnectionFromClientEvent(manager)); } /** * Process initial Handshake packet, kicks players from the server if they are connecting while we are starting up. * Also verifies the client has the FML marker. * * @param packet Handshake Packet * @param manager NetworkDirection connection * @return True to allow connection, otherwise False. */ public boolean handleServerHandshake(C00Handshake packet, NetworkManager manager) { if (!shouldAllowPlayerLogins()) { TextComponentString text = new TextComponentString("Server is still starting! Please wait before reconnecting."); LOGGER.info("Disconnecting Player: {}", text.getUnformattedText()); manager.sendPacket(new SPacketDisconnect(text)); manager.closeChannel(text); return false; } if (packet.getRequestedState() == EnumConnectionState.LOGIN && (!NetworkRegistry.INSTANCE.isVanillaAccepted(Side.CLIENT) && !packet.hasFMLMarker())) { manager.setConnectionState(EnumConnectionState.LOGIN); TextComponentString text = new TextComponentString("This server has mods that require FML/Forge to be installed on the client. Contact your server admin for more details."); Collection modNames = NetworkRegistry.INSTANCE.getRequiredMods(Side.CLIENT); FMLLog.log.info("Disconnecting Player: This server has mods that require FML/Forge to be installed on the client: {}", modNames); manager.sendPacket(new SPacketDisconnect(text)); manager.closeChannel(text); return false; } manager.channel().attr(net.minecraftforge.fml.network.NetworkRegistry.FML_MARKER).set(packet.hasFMLMarker()); return true; } public void processWindowMessages() { if (sidedDelegate == null) return; sidedDelegate.processWindowMessages(); } /** * Used to exit from java, with system exit preventions in place. Will be tidy about it and just log a message, * unless debugging is enabled * * @param exitCode The exit code * @param hardExit Perform a halt instead of an exit (only use when the world is unsavable) - read the warnings at {@link Runtime#halt(int)} */ public void exitJava(int exitCode, boolean hardExit) { FMLLog.log.warn("Java has been asked to exit (code {})", exitCode); if (hardExit) { FMLLog.log.warn("This is an abortive exit and could cause world corruption or other things"); } StackTraceElement[] stack = Thread.currentThread().getStackTrace(); FMLLog.log.warn("Exit trace:"); //The first 2 elements are Thread#getStackTrace and FMLCommonHandler#exitJava and aren't relevant for (int i = 2; i < stack.length; i++) { FMLLog.log.warn("\t{}", stack[i]); } if (hardExit) { Runtime.getRuntime().halt(exitCode); } else { Runtime.getRuntime().exit(exitCode); } } public IThreadListener getWorldThread(INetHandler net) { return sidedDelegate.getWorldThread(net); } public static void callFuture(FutureTask task) { try { task.run(); task.get(); // Forces the exception to be thrown if any } catch (InterruptedException | ExecutionException e) { FMLLog.log.fatal("Exception caught executing FutureTask: {}", e.toString(), e); } } public String stripSpecialChars(String message) { return sidedDelegate != null ? sidedDelegate.stripSpecialChars(message) : message; } public void reloadRenderers() { sidedDelegate.reloadRenderers(); } public void fireSidedRegistryEvents() { sidedDelegate.fireSidedRegistryEvents(); } public CompoundDataFixer getDataFixer() { return (CompoundDataFixer)sidedDelegate.getDataFixer(); } public boolean isDisplayVSyncForced() { return sidedDelegate.isDisplayVSyncForced(); } public void resetClientRecipeBook() { this.sidedDelegate.resetClientRecipeBook(); } public void reloadSearchTrees() { this.sidedDelegate.reloadSearchTrees(); } public void reloadCreativeSettings() { this.sidedDelegate.reloadCreativeSettings(); } }