/* * This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion * Copyright (C) 2016-2021 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viaversion.protocol; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.google.common.collect.Range; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.data.MappingDataLoader; import com.viaversion.viaversion.api.protocol.ProtocolManager; import com.viaversion.viaversion.api.protocol.ProtocolPathEntry; import com.viaversion.viaversion.api.protocol.ProtocolPathKey; import com.viaversion.viaversion.api.protocol.Protocol; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; import com.viaversion.viaversion.api.protocol.version.ServerProtocolVersion; import com.viaversion.viaversion.protocol.packet.PacketWrapperImpl; import com.viaversion.viaversion.protocols.base.BaseProtocol; import com.viaversion.viaversion.protocols.base.BaseProtocol1_16; import com.viaversion.viaversion.protocols.base.BaseProtocol1_7; import com.viaversion.viaversion.protocols.protocol1_10to1_9_3.Protocol1_10To1_9_3_4; import com.viaversion.viaversion.protocols.protocol1_11_1to1_11.Protocol1_11_1To1_11; import com.viaversion.viaversion.protocols.protocol1_11to1_10.Protocol1_11To1_10; import com.viaversion.viaversion.protocols.protocol1_12_1to1_12.Protocol1_12_1To1_12; import com.viaversion.viaversion.protocols.protocol1_12_2to1_12_1.Protocol1_12_2To1_12_1; import com.viaversion.viaversion.protocols.protocol1_12to1_11_1.Protocol1_12To1_11_1; import com.viaversion.viaversion.protocols.protocol1_13_1to1_13.Protocol1_13_1To1_13; import com.viaversion.viaversion.protocols.protocol1_13_2to1_13_1.Protocol1_13_2To1_13_1; import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; import com.viaversion.viaversion.protocols.protocol1_14_1to1_14.Protocol1_14_1To1_14; import com.viaversion.viaversion.protocols.protocol1_14_2to1_14_1.Protocol1_14_2To1_14_1; import com.viaversion.viaversion.protocols.protocol1_14_3to1_14_2.Protocol1_14_3To1_14_2; import com.viaversion.viaversion.protocols.protocol1_14_4to1_14_3.Protocol1_14_4To1_14_3; import com.viaversion.viaversion.protocols.protocol1_14to1_13_2.Protocol1_14To1_13_2; import com.viaversion.viaversion.protocols.protocol1_15_1to1_15.Protocol1_15_1To1_15; import com.viaversion.viaversion.protocols.protocol1_15_2to1_15_1.Protocol1_15_2To1_15_1; import com.viaversion.viaversion.protocols.protocol1_15to1_14_4.Protocol1_15To1_14_4; import com.viaversion.viaversion.protocols.protocol1_16_1to1_16.Protocol1_16_1To1_16; import com.viaversion.viaversion.protocols.protocol1_16_2to1_16_1.Protocol1_16_2To1_16_1; import com.viaversion.viaversion.protocols.protocol1_16_3to1_16_2.Protocol1_16_3To1_16_2; import com.viaversion.viaversion.protocols.protocol1_16_4to1_16_3.Protocol1_16_4To1_16_3; import com.viaversion.viaversion.protocols.protocol1_16to1_15_2.Protocol1_16To1_15_2; import com.viaversion.viaversion.protocols.protocol1_17to1_16_4.Protocol1_17To1_16_4; import com.viaversion.viaversion.protocols.protocol1_9_1_2to1_9_3_4.Protocol1_9_1_2To1_9_3_4; import com.viaversion.viaversion.protocols.protocol1_9_1to1_9.Protocol1_9_1To1_9; import com.viaversion.viaversion.protocols.protocol1_9_3to1_9_1_2.Protocol1_9_3To1_9_1_2; import com.viaversion.viaversion.protocols.protocol1_9to1_8.Protocol1_9To1_8; import com.viaversion.viaversion.protocols.protocol1_9to1_9_1.Protocol1_9To1_9_1; import com.viaversion.viaversion.util.Pair; import io.netty.buffer.ByteBuf; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.checkerframework.checker.nullness.qual.Nullable; import us.myles.ViaVersion.api.protocol.ProtocolRegistry; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Function; public class ProtocolManagerImpl implements ProtocolManager { private static final Protocol BASE_PROTOCOL = new BaseProtocol(); // Input Version -> Output Version & Protocol (Allows fast lookup) private final Int2ObjectMap> registryMap = new Int2ObjectOpenHashMap<>(32); private final Map, Protocol> protocols = new HashMap<>(); private final Map> pathCache = new ConcurrentHashMap<>(); private final Set supportedVersions = new HashSet<>(); private final List, Protocol>> baseProtocols = Lists.newCopyOnWriteArrayList(); private final List registerList = new ArrayList<>(); private final ReadWriteLock mappingLoaderLock = new ReentrantReadWriteLock(); private Map, CompletableFuture> mappingLoaderFutures = new HashMap<>(); private ThreadPoolExecutor mappingLoaderExecutor; private boolean mappingsLoaded; private ServerProtocolVersion serverProtocolVersion = new ServerProtocolVersionSingleton(-1); private int maxProtocolPathSize = 50; public ProtocolManagerImpl() { ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("Via-Mappingloader-%d").build(); mappingLoaderExecutor = new ThreadPoolExecutor(5, 16, 45L, TimeUnit.SECONDS, new SynchronousQueue<>(), threadFactory); mappingLoaderExecutor.allowCoreThreadTimeOut(true); } public void registerProtocols() { // Base Protocol registerBaseProtocol(BASE_PROTOCOL, Range.lessThan(Integer.MIN_VALUE)); registerBaseProtocol(new BaseProtocol1_7(), Range.lessThan(ProtocolVersion.v1_16.getVersion())); registerBaseProtocol(new BaseProtocol1_16(), Range.atLeast(ProtocolVersion.v1_16.getVersion())); registerProtocol(new Protocol1_9To1_8(), ProtocolVersion.v1_9, ProtocolVersion.v1_8); registerProtocol(new Protocol1_9_1To1_9(), Arrays.asList(ProtocolVersion.v1_9_1.getVersion(), ProtocolVersion.v1_9_2.getVersion()), ProtocolVersion.v1_9.getVersion()); registerProtocol(new Protocol1_9_3To1_9_1_2(), ProtocolVersion.v1_9_3, ProtocolVersion.v1_9_2); registerProtocol(new Protocol1_9To1_9_1(), ProtocolVersion.v1_9, ProtocolVersion.v1_9_1); registerProtocol(new Protocol1_9_1_2To1_9_3_4(), Arrays.asList(ProtocolVersion.v1_9_1.getVersion(), ProtocolVersion.v1_9_2.getVersion()), ProtocolVersion.v1_9_3.getVersion()); registerProtocol(new Protocol1_10To1_9_3_4(), ProtocolVersion.v1_10, ProtocolVersion.v1_9_3); registerProtocol(new Protocol1_11To1_10(), ProtocolVersion.v1_11, ProtocolVersion.v1_10); registerProtocol(new Protocol1_11_1To1_11(), ProtocolVersion.v1_11_1, ProtocolVersion.v1_11); registerProtocol(new Protocol1_12To1_11_1(), ProtocolVersion.v1_12, ProtocolVersion.v1_11_1); registerProtocol(new Protocol1_12_1To1_12(), ProtocolVersion.v1_12_1, ProtocolVersion.v1_12); registerProtocol(new Protocol1_12_2To1_12_1(), ProtocolVersion.v1_12_2, ProtocolVersion.v1_12_1); registerProtocol(new Protocol1_13To1_12_2(), ProtocolVersion.v1_13, ProtocolVersion.v1_12_2); registerProtocol(new Protocol1_13_1To1_13(), ProtocolVersion.v1_13_1, ProtocolVersion.v1_13); registerProtocol(new Protocol1_13_2To1_13_1(), ProtocolVersion.v1_13_2, ProtocolVersion.v1_13_1); registerProtocol(new Protocol1_14To1_13_2(), ProtocolVersion.v1_14, ProtocolVersion.v1_13_2); registerProtocol(new Protocol1_14_1To1_14(), ProtocolVersion.v1_14_1, ProtocolVersion.v1_14); registerProtocol(new Protocol1_14_2To1_14_1(), ProtocolVersion.v1_14_2, ProtocolVersion.v1_14_1); registerProtocol(new Protocol1_14_3To1_14_2(), ProtocolVersion.v1_14_3, ProtocolVersion.v1_14_2); registerProtocol(new Protocol1_14_4To1_14_3(), ProtocolVersion.v1_14_4, ProtocolVersion.v1_14_3); registerProtocol(new Protocol1_15To1_14_4(), ProtocolVersion.v1_15, ProtocolVersion.v1_14_4); registerProtocol(new Protocol1_15_1To1_15(), ProtocolVersion.v1_15_1, ProtocolVersion.v1_15); registerProtocol(new Protocol1_15_2To1_15_1(), ProtocolVersion.v1_15_2, ProtocolVersion.v1_15_1); registerProtocol(new Protocol1_16To1_15_2(), ProtocolVersion.v1_16, ProtocolVersion.v1_15_2); registerProtocol(new Protocol1_16_1To1_16(), ProtocolVersion.v1_16_1, ProtocolVersion.v1_16); registerProtocol(new Protocol1_16_2To1_16_1(), ProtocolVersion.v1_16_2, ProtocolVersion.v1_16_1); registerProtocol(new Protocol1_16_3To1_16_2(), ProtocolVersion.v1_16_3, ProtocolVersion.v1_16_2); registerProtocol(new Protocol1_16_4To1_16_3(), ProtocolVersion.v1_16_4, ProtocolVersion.v1_16_3); registerProtocol(new Protocol1_17To1_16_4(), ProtocolVersion.v1_17, ProtocolVersion.v1_16_4); } @Override public void registerProtocol(Protocol protocol, ProtocolVersion supported, ProtocolVersion output) { registerProtocol(protocol, Collections.singletonList(supported.getVersion()), output.getVersion()); } @Override public void registerProtocol(Protocol protocol, List supported, int output) { // Clear cache as this may make new routes. if (!pathCache.isEmpty()) { pathCache.clear(); } protocols.put(protocol.getClass(), protocol); for (int version : supported) { Int2ObjectMap protocolMap = registryMap.computeIfAbsent(version, s -> new Int2ObjectOpenHashMap<>(2)); protocolMap.put(output, protocol); } if (Via.getPlatform().isPluginEnabled()) { protocol.register(Via.getManager().getProviders()); refreshVersions(); } else { registerList.add(protocol); } if (protocol.hasMappingDataToLoad()) { if (mappingLoaderExecutor != null) { // Submit mapping data loading addMappingLoaderFuture(protocol.getClass(), protocol::loadMappingData); } else { // Late protocol adding - just do it on the current thread protocol.loadMappingData(); } } } @Override public void registerBaseProtocol(Protocol baseProtocol, Range supportedProtocols) { Preconditions.checkArgument(baseProtocol.isBaseProtocol(), "Protocol is not a base protocol"); baseProtocols.add(new Pair<>(supportedProtocols, baseProtocol)); if (Via.getPlatform().isPluginEnabled()) { baseProtocol.register(Via.getManager().getProviders()); refreshVersions(); } else { registerList.add(baseProtocol); } } public void refreshVersions() { supportedVersions.clear(); supportedVersions.add(serverProtocolVersion.lowestSupportedVersion()); for (ProtocolVersion versions : ProtocolVersion.getProtocols()) { List paths = getProtocolPath(versions.getVersion(), serverProtocolVersion.lowestSupportedVersion()); if (paths == null) continue; supportedVersions.add(versions.getVersion()); for (ProtocolPathEntry path : paths) { supportedVersions.add(path.getOutputProtocolVersion()); } } } @Override public @Nullable List getProtocolPath(int clientVersion, int serverVersion) { ProtocolPathKey protocolKey = new ProtocolPathKeyImpl(clientVersion, serverVersion); // Check cache List protocolList = pathCache.get(protocolKey); if (protocolList != null) { return protocolList; } // Generate path List outputPath = getProtocolPath(new ArrayList<>(), clientVersion, serverVersion); // If it found a path, cache it. if (outputPath != null) { pathCache.put(protocolKey, outputPath); } return outputPath; } /** * Calculates a path to get from an input protocol to the server's protocol. * * @param current current items in the path * @param clientVersion current input version * @param serverVersion desired output version * @return path that has been generated, null if failed */ private @Nullable List getProtocolPath(List current, int clientVersion, int serverVersion) { if (clientVersion == serverVersion) return null; // We're already there if (current.size() > maxProtocolPathSize) return null; // Fail safe, protocol too complicated. // First check if there is any protocols for this Int2ObjectMap inputMap = registryMap.get(clientVersion); if (inputMap == null) { return null; // Not supported } // Next check there isn't an obvious path Protocol protocol = inputMap.get(serverVersion); if (protocol != null) { current.add(new ProtocolPathEntryImpl(serverVersion, protocol)); return current; // Easy solution } // There might be a more advanced solution... So we'll see if any of the others can get us there List shortest = null; for (Int2ObjectMap.Entry entry : inputMap.int2ObjectEntrySet()) { // Ensure it wasn't caught by the other loop if (entry.getIntKey() == serverVersion) continue; ProtocolPathEntry pathEntry = new ProtocolPathEntryImpl(entry.getIntKey(), entry.getValue()); // Ensure no recursion if (current.contains(pathEntry)) continue; // Create a copy List newCurrent = new ArrayList<>(current); newCurrent.add(pathEntry); // Calculate the rest of the protocol using the current path entry newCurrent = getProtocolPath(newCurrent, entry.getIntKey(), serverVersion); // If it's shorter then choose it if (newCurrent != null && (shortest == null || shortest.size() > newCurrent.size())) { shortest = newCurrent; } } return shortest; // null if none found } @Override public @Nullable T getProtocol(Class protocolClass) { return (T) protocols.get(protocolClass); } @Override public Protocol getBaseProtocol(int serverVersion) { for (Pair, Protocol> rangeProtocol : Lists.reverse(baseProtocols)) { if (rangeProtocol.getKey().contains(serverVersion)) { return rangeProtocol.getValue(); } } throw new IllegalStateException("No Base Protocol for " + serverVersion); } @Override public boolean isBaseProtocol(Protocol protocol) { return protocol.isBaseProtocol(); } @Override public ServerProtocolVersion getServerProtocolVersion() { return serverProtocolVersion; } public void setServerProtocol(ServerProtocolVersion serverProtocolVersion) { this.serverProtocolVersion = serverProtocolVersion; //noinspection deprecation ProtocolRegistry.SERVER_PROTOCOL = serverProtocolVersion.lowestSupportedVersion(); } @Override public boolean isWorkingPipe() { for (Int2ObjectMap map : registryMap.values()) { for (int protocolVersion : serverProtocolVersion.supportedVersions()) { if (map.containsKey(protocolVersion)) { return true; } } } return false; // No destination for protocol } @Override public SortedSet getSupportedVersions() { return Collections.unmodifiableSortedSet(new TreeSet<>(supportedVersions)); } @Override public int getMaxProtocolPathSize() { return maxProtocolPathSize; } @Override public void setMaxProtocolPathSize(int maxProtocolPathSize) { this.maxProtocolPathSize = maxProtocolPathSize; } @Override public Protocol getBaseProtocol() { return BASE_PROTOCOL; } @Override public void completeMappingDataLoading(Class protocolClass) throws Exception { if (mappingsLoaded) return; CompletableFuture future = getMappingLoaderFuture(protocolClass); if (future != null) { // Wait for completion future.get(); } } @Override public boolean checkForMappingCompletion() { mappingLoaderLock.readLock().lock(); try { if (mappingsLoaded) return false; for (CompletableFuture future : mappingLoaderFutures.values()) { // Return if any future hasn't completed yet if (!future.isDone()) { return false; } } shutdownLoaderExecutor(); return true; } finally { mappingLoaderLock.readLock().unlock(); } } @Override public void addMappingLoaderFuture(Class protocolClass, Runnable runnable) { CompletableFuture future = CompletableFuture.runAsync(runnable, mappingLoaderExecutor).exceptionally(mappingLoaderThrowable(protocolClass)); mappingLoaderLock.writeLock().lock(); try { mappingLoaderFutures.put(protocolClass, future); } finally { mappingLoaderLock.writeLock().unlock(); } } @Override public void addMappingLoaderFuture(Class protocolClass, Class dependsOn, Runnable runnable) { CompletableFuture future = getMappingLoaderFuture(dependsOn) .whenCompleteAsync((v, throwable) -> runnable.run(), mappingLoaderExecutor).exceptionally(mappingLoaderThrowable(protocolClass)); mappingLoaderLock.writeLock().lock(); try { mappingLoaderFutures.put(protocolClass, future); } finally { mappingLoaderLock.writeLock().unlock(); } } @Override public @Nullable CompletableFuture getMappingLoaderFuture(Class protocolClass) { mappingLoaderLock.readLock().lock(); try { return mappingsLoaded ? null : mappingLoaderFutures.get(protocolClass); } finally { mappingLoaderLock.readLock().unlock(); } } @Override public PacketWrapper createPacketWrapper(int packetId, ByteBuf buf, UserConnection connection) { return new PacketWrapperImpl(packetId, buf, connection); } /** * Called when the server is enabled, to register any non-registered listeners. */ public void onServerLoaded() { for (Protocol protocol : registerList) { protocol.register(Via.getManager().getProviders()); } registerList.clear(); } private void shutdownLoaderExecutor() { Preconditions.checkArgument(!mappingsLoaded); Via.getPlatform().getLogger().info("Finished mapping loading, shutting down loader executor!"); mappingsLoaded = true; mappingLoaderExecutor.shutdown(); mappingLoaderExecutor = null; mappingLoaderFutures.clear(); mappingLoaderFutures = null; if (MappingDataLoader.isCacheJsonMappings()) { MappingDataLoader.getMappingsCache().clear(); } } private Function mappingLoaderThrowable(Class protocolClass) { return throwable -> { Via.getPlatform().getLogger().severe("Error during mapping loading of " + protocolClass.getSimpleName()); throwable.printStackTrace(); return null; }; } }