ForgePatch/src/main/java/net/minecraftforge/fml/server/ServerLifecycleHooks.java

220 lines
10 KiB
Java

/*
* Minecraft Forge
* Copyright (c) 2016-2020.
*
* 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.server;
import static net.minecraftforge.fml.Logging.CORE;
import java.nio.file.Path;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import net.minecraft.resources.IPackNameDecorator;
import net.minecraft.world.storage.FolderName;
import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.fml.LogicalSidedProvider;
import net.minecraftforge.fml.ModLoader;
import net.minecraftforge.fml.ModLoadingStage;
import net.minecraftforge.fml.ModLoadingWarning;
import net.minecraftforge.fml.network.ConnectionType;
import net.minecraftforge.fml.network.FMLNetworkConstants;
import net.minecraftforge.fml.network.FMLStatusPing;
import net.minecraftforge.fml.network.NetworkHooks;
import net.minecraftforge.fml.network.NetworkRegistry;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.ProtocolType;
import net.minecraft.network.handshake.client.CHandshakePacket;
import net.minecraft.network.login.server.SDisconnectLoginPacket;
import net.minecraft.resources.ResourcePackInfo;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.text.StringTextComponent;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.config.ConfigTracker;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.event.server.FMLServerAboutToStartEvent;
import net.minecraftforge.fml.event.server.FMLServerStartedEvent;
import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
import net.minecraftforge.fml.event.server.FMLServerStoppedEvent;
import net.minecraftforge.fml.event.server.FMLServerStoppingEvent;
import net.minecraftforge.fml.loading.FileUtils;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.packs.ModFileResourcePack;
import net.minecraftforge.fml.packs.ResourcePackLoader;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.network.VanillaConnectionNetworkFilter;
import net.minecraftforge.registries.GameData;
public class ServerLifecycleHooks
{
private static final Logger LOGGER = LogManager.getLogger();
private static final Marker SERVERHOOKS = MarkerManager.getMarker("SERVERHOOKS");
private static final FolderName SERVERCONFIG = new FolderName("serverconfig");
private static volatile CountDownLatch exitLatch = null;
private static MinecraftServer currentServer;
private static Path getServerConfigPath(final MinecraftServer server)
{
final Path serverConfig = server.func_240776_a_(SERVERCONFIG);
FileUtils.getOrCreateDirectory(serverConfig, "serverconfig");
return serverConfig;
}
public static boolean handleServerAboutToStart(final MinecraftServer server)
{
currentServer = server;
currentServer.getServerStatusResponse().setForgeData(new FMLStatusPing()); //gathers NetworkRegistry data
// on the dedi server we need to force the stuff to setup properly
LogicalSidedProvider.setServer(()->server);
ConfigTracker.INSTANCE.loadConfigs(ModConfig.Type.SERVER, getServerConfigPath(server));
return !MinecraftForge.EVENT_BUS.post(new FMLServerAboutToStartEvent(server));
}
public static boolean handleServerStarting(final MinecraftServer server)
{
DistExecutor.runWhenOn(Dist.DEDICATED_SERVER, ()->()->LanguageHook.loadLanguagesOnServer(server));
return !MinecraftForge.EVENT_BUS.post(new FMLServerStartingEvent(server));
}
public static void handleServerStarted(final MinecraftServer server)
{
MinecraftForge.EVENT_BUS.post(new FMLServerStartedEvent(server));
allowLogins.set(true);
}
public static void handleServerStopping(final MinecraftServer server)
{
allowLogins.set(false);
MinecraftForge.EVENT_BUS.post(new FMLServerStoppingEvent(server));
}
public static void expectServerStopped()
{
exitLatch = new CountDownLatch(1);
}
public static void handleServerStopped(final MinecraftServer server)
{
if (!server.isDedicatedServer()) GameData.revertToFrozen();
MinecraftForge.EVENT_BUS.post(new FMLServerStoppedEvent(server));
currentServer = null;
LogicalSidedProvider.setServer(null);
CountDownLatch latch = exitLatch;
if (latch != null)
{
latch.countDown();
exitLatch = null;
}
ConfigTracker.INSTANCE.unloadConfigs(ModConfig.Type.SERVER, getServerConfigPath(server));
}
public static MinecraftServer getCurrentServer()
{
return currentServer;
}
private static AtomicBoolean allowLogins = new AtomicBoolean(false);
public static boolean handleServerLogin(final CHandshakePacket packet, final NetworkManager manager) {
if (!allowLogins.get())
{
StringTextComponent text = new StringTextComponent("Server is still starting! Please wait before reconnecting.");
LOGGER.info(SERVERHOOKS,"Disconnecting Player (server is still starting): {}", text.getUnformattedComponentText());
manager.sendPacket(new SDisconnectLoginPacket(text));
manager.closeChannel(text);
return false;
}
if (packet.getRequestedState() == ProtocolType.LOGIN) {
final ConnectionType connectionType = ConnectionType.forVersionFlag(packet.getFMLVersion());
final int versionNumber = connectionType.getFMLVersionNumber(packet.getFMLVersion());
// HACK: Check for missing leading 0. If it's not there, we know the client isn't patched.
if (connectionType == ConnectionType.MODDED && packet.getFMLVersion().substring(FMLNetworkConstants.FMLNETMARKER.length()).charAt(0) != '0') {
rejectConnection(manager, connectionType, "Your client appears to be vulnerable to CVE-2021-44228, 45046, 45105, and/or 44832. To connect to this server, you must install ~keith's patched Forge version: https://bytes.keithhacks.cyou/keith/ForgePatch/releases/");
return false;
}
if (connectionType == ConnectionType.MODDED && versionNumber != FMLNetworkConstants.FMLNETVERSION) {
rejectConnection(manager, connectionType, "This modded server is not network compatible with your modded client. Please verify your Forge version closely matches the server. Got net version "+ versionNumber + " this server is net version "+FMLNetworkConstants.FMLNETVERSION);
return false;
}
if (connectionType == ConnectionType.VANILLA && !NetworkRegistry.acceptsVanillaClientConnections()) {
rejectConnection(manager, connectionType, "This server has mods that require Forge to be installed on the client. Contact your server admin for more details.");
return false;
}
}
if (packet.getRequestedState() == ProtocolType.STATUS) return true;
NetworkHooks.registerServerLoginChannel(manager, packet);
VanillaConnectionNetworkFilter.injectIfNecessary(manager);
return true;
}
private static void rejectConnection(final NetworkManager manager, ConnectionType type, String message) {
manager.setConnectionState(ProtocolType.LOGIN);
LOGGER.info(SERVERHOOKS, "Disconnecting {} connection attempt: {}", type, message);
StringTextComponent text = new StringTextComponent(message);
manager.sendPacket(new SDisconnectLoginPacket(text));
manager.closeChannel(text);
}
public static void handleExit(int retVal)
{
System.exit(retVal);
}
//INTERNAL MODDERS DO NOT USE
@Deprecated
public static ResourcePackLoader.IPackInfoFinder buildPackFinder(Map<ModFile, ? extends ModFileResourcePack> modResourcePacks, BiConsumer<? super ModFileResourcePack, ResourcePackInfo> packSetter) {
return (packList, factory) -> serverPackFinder(modResourcePacks, packSetter, packList, factory);
}
private static void serverPackFinder(Map<ModFile, ? extends ModFileResourcePack> modResourcePacks, BiConsumer<? super ModFileResourcePack, ResourcePackInfo> packSetter, Consumer<ResourcePackInfo> consumer, ResourcePackInfo.IFactory factory) {
for (Entry<ModFile, ? extends ModFileResourcePack> e : modResourcePacks.entrySet())
{
IModInfo mod = e.getKey().getModInfos().get(0);
if (Objects.equals(mod.getModId(), "minecraft")) continue; // skip the minecraft "mod"
final String name = "mod:" + mod.getModId();
final ResourcePackInfo packInfo = ResourcePackInfo.createResourcePack(name, false, e::getValue, factory, ResourcePackInfo.Priority.BOTTOM, IPackNameDecorator.PLAIN);
if (packInfo == null) {
// Vanilla only logs an error, instead of propagating, so handle null and warn that something went wrong
ModLoader.get().addWarning(new ModLoadingWarning(mod, ModLoadingStage.ERROR, "fml.modloading.brokenresources", e.getKey()));
continue;
}
packSetter.accept(e.getValue(), packInfo);
LOGGER.debug(CORE, "Generating PackInfo named {} for mod file {}", name, e.getKey().getFilePath());
consumer.accept(packInfo);
}
}
}