diff --git a/fabric-mod-protocol-api-v1/build.gradle b/fabric-mod-protocol-api-v1/build.gradle new file mode 100644 index 0000000000..60da64c69a --- /dev/null +++ b/fabric-mod-protocol-api-v1/build.gradle @@ -0,0 +1,3 @@ +version = getSubprojectVersion(project) + +moduleDependencies(project, ['fabric-api-base', 'fabric-networking-api-v1']) diff --git a/fabric-mod-protocol-api-v1/src/client/java/net/fabricmc/fabric/api/client/modprotocol/v1/ClientModProtocolLookup.java b/fabric-mod-protocol-api-v1/src/client/java/net/fabricmc/fabric/api/client/modprotocol/v1/ClientModProtocolLookup.java new file mode 100644 index 0000000000..51a37200b4 --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/client/java/net/fabricmc/fabric/api/client/modprotocol/v1/ClientModProtocolLookup.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.client.modprotocol.v1; + +import it.unimi.dsi.fastutil.objects.Object2IntMap; + +import net.minecraft.client.network.ClientCommonNetworkHandler; +import net.minecraft.network.ClientConnection; +import net.minecraft.server.network.ServerCommonNetworkHandler; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.modprotocol.v1.ModProtocol; +import net.fabricmc.fabric.api.modprotocol.v1.ModProtocolIds; +import net.fabricmc.fabric.impl.modprotocol.RemoteProtocolStorage; + +/** + * Utility methods allowing to get protocol versions supported by the server. + * + *

Protocol identifiers can be any valid {@link Identifier}. The default is {@code mod:(mod ID)}. + * @see ModProtocolIds + */ +public final class ClientModProtocolLookup { + /** + * A protocol version returned by {@code getSupportedProtocol} methods, when the server doesn't support the requested protocol. + */ + public static final int UNSUPPORTED = -1; + private ClientModProtocolLookup() { } + + /** + * Gets the protocol version supported by the server. + * + * @param handler the network handler connected to the server + * @param protocolId protocol's id + * @return the protocol version supported by the server + */ + public static int getSupportedProtocol(ClientCommonNetworkHandler handler, Identifier protocolId) { + return RemoteProtocolStorage.getProtocol(handler, protocolId); + } + + /** + * Gets the protocol version supported by the server. + * + * @param connection the ClientConnection connected to the server + * @param protocolId protocol's id + * @return the protocol version supported by the server + */ + public static int getSupportedProtocol(ClientConnection connection, Identifier protocolId) { + return RemoteProtocolStorage.getProtocol(connection, protocolId); + } + + /** + * Gets the protocol version supported by the server. + * + * @param handler the network handler connected to the server + * @param protocol protocol to check against + * @return the protocol version supported by the server + */ + public static int getSupportedProtocol(ClientCommonNetworkHandler handler, ModProtocol protocol) { + return RemoteProtocolStorage.getProtocol(handler, protocol.id()); + } + + /** + * Gets the protocol version supported by the server. + * + * @param connection the ClientConnection connected to the server + * @param protocol protocol to check against + * @return the protocol version supported by the server + */ + public static int getSupportedProtocol(ClientConnection connection, ModProtocol protocol) { + return RemoteProtocolStorage.getProtocol(connection, protocol.id()); + } + + /** + * Gets all protocols supported by the server. + * + * @param handler the network handler connected to the server + * @return the map of protocols to the versions supported by the server + */ + public static Object2IntMap getAllSupportedProtocols(ServerCommonNetworkHandler handler) { + return RemoteProtocolStorage.getMap(handler); + } + + /** + * Gets all protocols supported by the server. + * + * @param connection the ClientConnection connected to the server + * @return the map of protocols to the versions supported by the server + */ + public static Object2IntMap getAllSupportedProtocols(ClientConnection connection) { + return RemoteProtocolStorage.getMap(connection); + } +} diff --git a/fabric-mod-protocol-api-v1/src/client/java/net/fabricmc/fabric/api/client/modprotocol/v1/package-info.java b/fabric-mod-protocol-api-v1/src/client/java/net/fabricmc/fabric/api/client/modprotocol/v1/package-info.java new file mode 100644 index 0000000000..f39de72ade --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/client/java/net/fabricmc/fabric/api/client/modprotocol/v1/package-info.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * The Mod Protocol API (client side), version 1. + * + *

See {@link net.fabricmc.fabric.api.modprotocol.v1}

+ */ + +@ApiStatus.Experimental +package net.fabricmc.fabric.api.client.modprotocol.v1; + +import org.jetbrains.annotations.ApiStatus; diff --git a/fabric-mod-protocol-api-v1/src/client/java/net/fabricmc/fabric/impl/modprotocol/client/ClientModProtocolInit.java b/fabric-mod-protocol-api-v1/src/client/java/net/fabricmc/fabric/impl/modprotocol/client/ClientModProtocolInit.java new file mode 100644 index 0000000000..c3716930be --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/client/java/net/fabricmc/fabric/impl/modprotocol/client/ClientModProtocolInit.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.modprotocol.client; + +import java.util.HashMap; + +import net.minecraft.util.Identifier; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking; +import net.fabricmc.fabric.impl.modprotocol.ModProtocolImpl; +import net.fabricmc.fabric.impl.modprotocol.ModProtocolInit; +import net.fabricmc.fabric.impl.modprotocol.ModProtocolManager; +import net.fabricmc.fabric.impl.modprotocol.RemoteProtocolStorage; +import net.fabricmc.fabric.impl.modprotocol.payload.ModProtocolRequestS2CPayload; +import net.fabricmc.fabric.impl.modprotocol.payload.ModProtocolResponseC2SPayload; + +public final class ClientModProtocolInit implements ClientModInitializer { + public void onInitializeClient() { + ClientConfigurationNetworking.registerGlobalReceiver(ModProtocolRequestS2CPayload.ID, (payload, context) -> { + var map = new HashMap(payload.modProtocol().size()); + + for (ModProtocolImpl protocol : payload.modProtocol()) { + map.put(protocol.id(), protocol); + } + + ModProtocolManager.ValidationResult validate = ModProtocolManager.validateClient(map); + + if (validate.isSuccess()) { + ((RemoteProtocolStorage) context.networkHandler()).fabric$setRemoteProtocol(validate.supportedProtocols()); + context.responseSender().sendPacket(new ModProtocolResponseC2SPayload(validate.supportedProtocols())); + return; + } + + var b = new StringBuilder(); + b.append("Disconnected due to mismatched protocols!").append('\n'); + b.append("Missing entries:").append('\n'); + ModProtocolManager.appendTextEntries(validate.missing(), ModProtocolManager.LOCAL_MOD_PROTOCOLS_BY_ID, -1, text -> b.append(" - ").append(text.getString())); + + context.responseSender().disconnect(ModProtocolManager.constructMessage(validate.missing(), ModProtocolManager.LOCAL_MOD_PROTOCOLS_BY_ID)); + ModProtocolInit.LOGGER.warn(b.toString()); + }); + } +} diff --git a/fabric-mod-protocol-api-v1/src/client/java/net/fabricmc/fabric/mixin/modprotocol/client/ClientCommonNetworkHandlerMixin.java b/fabric-mod-protocol-api-v1/src/client/java/net/fabricmc/fabric/mixin/modprotocol/client/ClientCommonNetworkHandlerMixin.java new file mode 100644 index 0000000000..33cca449e6 --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/client/java/net/fabricmc/fabric/mixin/modprotocol/client/ClientCommonNetworkHandlerMixin.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.modprotocol.client; + +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import net.minecraft.client.network.ClientCommonNetworkHandler; +import net.minecraft.network.ClientConnection; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.impl.modprotocol.RemoteProtocolStorage; + +@Mixin(ClientCommonNetworkHandler.class) +public class ClientCommonNetworkHandlerMixin implements RemoteProtocolStorage { + @Shadow + @Final + protected ClientConnection connection; + + @Override + public Object2IntMap fabric$getRemoteProtocol() { + return ((RemoteProtocolStorage) this.connection).fabric$getRemoteProtocol(); + } + + @Override + public void fabric$setRemoteProtocol(Object2IntMap protocol) { + ((RemoteProtocolStorage) this.connection).fabric$setRemoteProtocol(protocol); + } +} diff --git a/fabric-mod-protocol-api-v1/src/client/java/net/fabricmc/fabric/mixin/modprotocol/client/ClientConfigurationNetworkHandlerMixin.java b/fabric-mod-protocol-api-v1/src/client/java/net/fabricmc/fabric/mixin/modprotocol/client/ClientConfigurationNetworkHandlerMixin.java new file mode 100644 index 0000000000..96bac544b5 --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/client/java/net/fabricmc/fabric/mixin/modprotocol/client/ClientConfigurationNetworkHandlerMixin.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.modprotocol.client; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientCommonNetworkHandler; +import net.minecraft.client.network.ClientConfigurationNetworkHandler; +import net.minecraft.client.network.ClientConnectionState; +import net.minecraft.network.ClientConnection; +import net.minecraft.network.packet.s2c.config.SelectKnownPacksS2CPacket; + +import net.fabricmc.fabric.impl.modprotocol.ModProtocolManager; +import net.fabricmc.fabric.impl.modprotocol.RemoteProtocolStorage; + +@Mixin(ClientConfigurationNetworkHandler.class) +public abstract class ClientConfigurationNetworkHandlerMixin extends ClientCommonNetworkHandler { + protected ClientConfigurationNetworkHandlerMixin(MinecraftClient client, ClientConnection connection, ClientConnectionState connectionState) { + super(client, connection, connectionState); + } + + @Inject(method = "onSelectKnownPacks", at = @At("HEAD"), cancellable = true) + private void preventJoiningIncompatibleServers(SelectKnownPacksS2CPacket packet, CallbackInfo ci) { + if (((RemoteProtocolStorage) this.connection).fabric$getRemoteProtocol() == null && !ModProtocolManager.SERVER_REQUIRED.isEmpty()) { + this.client.execute(() -> this.connection.disconnect(ModProtocolManager.constructMessage(ModProtocolManager.SERVER_REQUIRED, ModProtocolManager.LOCAL_MOD_PROTOCOLS_BY_ID))); + ci.cancel(); + } + } +} diff --git a/fabric-mod-protocol-api-v1/src/client/resources/fabric-mod-protocol-api-v1.client.mixins.json b/fabric-mod-protocol-api-v1/src/client/resources/fabric-mod-protocol-api-v1.client.mixins.json new file mode 100644 index 0000000000..13c1d91ba3 --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/client/resources/fabric-mod-protocol-api-v1.client.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "package": "net.fabricmc.fabric.mixin.modprotocol.client", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "ClientCommonNetworkHandlerMixin" + ], + "injectors": { + "defaultRequire": 1 + }, + "client": [ + "ClientConfigurationNetworkHandlerMixin" + ] +} diff --git a/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/api/modprotocol/v1/ModProtocol.java b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/api/modprotocol/v1/ModProtocol.java new file mode 100644 index 0000000000..8f2bf4ab20 --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/api/modprotocol/v1/ModProtocol.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.modprotocol.v1; + +import it.unimi.dsi.fastutil.ints.IntList; +import org.jetbrains.annotations.ApiStatus; + +import net.minecraft.util.Identifier; + +/** + * Interface representing registered ModProtocol. Can be used for further lookups. + */ +@ApiStatus.NonExtendable +public interface ModProtocol { + /** + * @return Identifier associated with this Mod Protocol + */ + Identifier id(); + /** + * @return Display name of this protocol + */ + String name(); + /** + * @return Display version of this protocol + */ + String version(); + /** + * @return Protocol versions supported by this protocol + */ + IntList protocol(); + /** + * @return Client requirement of this protocol + */ + boolean requireClient(); + /** + * @return Server requirement of this protocol + */ + boolean requireServer(); +} diff --git a/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/api/modprotocol/v1/ModProtocolIds.java b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/api/modprotocol/v1/ModProtocolIds.java new file mode 100644 index 0000000000..ccdada3882 --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/api/modprotocol/v1/ModProtocolIds.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.modprotocol.v1; + +import net.minecraft.util.Identifier; + +/** + * Utility methods allowing to create Identifiers targeting default protocols. + */ +public final class ModProtocolIds { + public static final String MOD = "mod"; + public static final String SPECIAL = "special"; + public static final String FEATURE = "feature"; + private ModProtocolIds() { } + + public static Identifier mod(String modId) { + return Identifier.of(MOD, modId); + } + + public static Identifier special(String path) { + return Identifier.of(SPECIAL, path); + } + + public static Identifier feature(String path) { + return Identifier.of(FEATURE, path); + } +} diff --git a/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/api/modprotocol/v1/ModProtocolRegistry.java b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/api/modprotocol/v1/ModProtocolRegistry.java new file mode 100644 index 0000000000..9471a364e0 --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/api/modprotocol/v1/ModProtocolRegistry.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.modprotocol.v1; + +import java.util.Collection; +import java.util.Collections; + +import it.unimi.dsi.fastutil.ints.IntList; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.impl.modprotocol.ModProtocolImpl; +import net.fabricmc.fabric.impl.modprotocol.ModProtocolManager; + +/** + * Utility methods allowing to lookup or register new protocols. + * + *

While lookup can be done anytime, registration is only possible before registries are frozen.

+ */ +public final class ModProtocolRegistry { + private ModProtocolRegistry() { } + + /** + * Allows to get a locally registered protocol. + * @param identifier protocol's id + * @return the requested ModProtocol or {@code null} if not found + */ + @Nullable + public static ModProtocol get(Identifier identifier) { + return ModProtocolManager.LOCAL_MOD_PROTOCOLS_BY_ID.get(identifier); + } + + /** + * @return All registered protocols + */ + public static Collection getAll() { + return Collections.unmodifiableCollection(ModProtocolManager.LOCAL_MOD_PROTOCOLS); + } + + /** + * Registers new mod protocol with its own unique settings. + * + * @param identifier the identifier of protocol + * @param name display name in protocol, shown if it's missing + * @param version display version of the protocol, shown if it's missing + * @param protocol list of protocol versions + * @param requireClient marks protocol as required on client + * @param requireServer marks protocol as required on server + * @return registered Mod Protocol + */ + public static ModProtocol register(Identifier identifier, String name, String version, IntList protocol, boolean requireClient, boolean requireServer) { + return ModProtocolManager.add(null, new ModProtocolImpl(identifier, name, version, IntList.of(protocol.toIntArray()), requireClient, requireServer)); + } + + /** + * Allows to customize priority of namespaces when displaying missing protocols. + * @param firstNamespace namespace that should display first + * @param secondNamespace namespace that should display second + * @return true if change occurred, false if it didn't + */ + public static boolean addDisplayOrdering(String firstNamespace, String secondNamespace) { + return ModProtocolManager.registerOrder(firstNamespace, secondNamespace); + } +} diff --git a/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/api/modprotocol/v1/ServerModProtocolLookup.java b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/api/modprotocol/v1/ServerModProtocolLookup.java new file mode 100644 index 0000000000..63557d79de --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/api/modprotocol/v1/ServerModProtocolLookup.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.modprotocol.v1; + +import it.unimi.dsi.fastutil.objects.Object2IntMap; + +import net.minecraft.network.ClientConnection; +import net.minecraft.server.network.ServerCommonNetworkHandler; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.impl.modprotocol.RemoteProtocolStorage; + +/** + * Utility methods allowing to get protocol versions supported by the player. + * + *

Protocol identifier's can be any valid identifier, through by default mods defining it will use "mod" namespace and path equal to its id. + * See {@link ModProtocolIds} for more information.

+ */ +public final class ServerModProtocolLookup { + /** + * A protocol version returned by {@code getSupportedProtocol} methods, when the player doesn't support the requested protocol. + */ + public static final int UNSUPPORTED = -1; + + private ServerModProtocolLookup() { + } + + /** + * Gets protocol version supported by the player. + * + * @param player the player + * @param protocolId protocol's id + * @return Protocol version supported by the player + */ + public static int getSupportedProtocol(ServerPlayerEntity player, Identifier protocolId) { + return RemoteProtocolStorage.getProtocol(player.networkHandler, protocolId); + } + + /** + * Gets protocol version supported by the player. + * + * @param handler the network handler owned by the player you want to check protocol for + * @param protocolId protocol's id + * @return Protocol version supported by the player + */ + public static int getSupportedProtocol(ServerCommonNetworkHandler handler, Identifier protocolId) { + return RemoteProtocolStorage.getProtocol(handler, protocolId); + } + + /** + * Gets protocol version supported by the server. + * + * @param connection the ClientConnection connected to the server + * @param protocolId protocol's id + * @return Protocol version supported by the server + */ + public static int getSupportedProtocol(ClientConnection connection, Identifier protocolId) { + return RemoteProtocolStorage.getProtocol(connection, protocolId); + } + + /** + * Gets protocol version supported by the player. + * + * @param player the player + * @param protocol protocol to check against + * @return Protocol version supported by the player + */ + public static int getSupportedProtocol(ServerPlayerEntity player, ModProtocol protocol) { + return RemoteProtocolStorage.getProtocol(player.networkHandler, protocol.id()); + } + + /** + * Gets protocol version supported by the player. + * + * @param handler the network handler owned by the player you want to check protocol for + * @param protocol protocol to check against + * @return Protocol version supported by the player + */ + public static int getSupportedProtocol(ServerCommonNetworkHandler handler, ModProtocol protocol) { + return RemoteProtocolStorage.getProtocol(handler, protocol.id()); + } + + /** + * Gets protocol version supported by the server. + * + * @param connection the ClientConnection connected to the server. + * @param protocol protocol to check against + * @return Protocol version supported by the server + */ + public static int getSupportedProtocol(ClientConnection connection, ModProtocol protocol) { + return RemoteProtocolStorage.getProtocol(connection, protocol.id()); + } + + /** + * Gets all protocols supported by the player. + * + * @param player the player + * @return Map of protocols supported by the player + */ + public static Object2IntMap getAllSupportedProtocols(ServerPlayerEntity player) { + return RemoteProtocolStorage.getMap(player.networkHandler); + } + + /** + * Gets all protocols supported by the player. + * + * @param handler the network handler owned by the player you want to check protocol for + * @return Map of protocols supported by the player + */ + public static Object2IntMap getAllSupportedProtocols(ServerCommonNetworkHandler handler) { + return RemoteProtocolStorage.getMap(handler); + } + + /** + * Gets all protocols supported by the player. + * + * @param connection the ClientConnection connected to the server + * @return Map of protocols supported by the player + */ + public static Object2IntMap getAllSupportedProtocols(ClientConnection connection) { + return RemoteProtocolStorage.getMap(connection); + } +} diff --git a/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/api/modprotocol/v1/package-info.java b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/api/modprotocol/v1/package-info.java new file mode 100644 index 0000000000..7b5a75821e --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/api/modprotocol/v1/package-info.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * The Mod Protocol API, version 1. + * + *

Mod Protocol is an additional syncing system allowing mods to define their protocol, with simple utilities + * allowing to check supported version and use it for networking or safeguards against using mismatched/incompatible + * mod versions on client and server. Exact configuration can differ from mod to mod.

+ * + *

The mod protocol can be defined in two ways: + *

+ *
fabric.mod.json
+ *
This is the simplest way to define it. Can be useful when you don't need to change it depending on mods configuration + * or external dependencies. It is set within custom field of that file under "fabric:mod_protocol" key. + * + * It can be defined in multiple ways: + *
+ *
"fabric:mod_protocol": 1
+ *

This will automatically use mods id with "mod" namespace as the protocol identifier, as a single supported protocol version, + * with display name and version being copied form mods metadata. It also marks the protocol as required on both client and server.

+ *
+ *
"fabric:mod_protocol": { + * "protocol": [1, 2], + * "id: "custom:id", + * "name": "Mod Name", + * "version": "v1.2.3", + * "require_client": false, + * "require_server": true + * }
+ *

Full object. Only required value is "protocol", which can be set directly for single version or as an array for multiple.

+ *

"id" is the protocols identifier, which can have any namespace and path, as long as it's valid. + * It is optional and defaults to an ID with "mod" namespace and path equal to mod's id. + *

+ *

"name" is a name displayed if protocol doesn't match. It's optional and by default it uses one from mod's metadata.

+ *

"version" is a version displayed if protocol doesn't match. It's optional and by default it uses one from mod's metadata.

+ *

"require_client" controls if clients without this protocol can join the server, defaults to true, preventing joining

+ *

"require_server" controls if clients can join servers without this mod, defaults to true, preventing joining

+ *
+ *
"fabric:mod_protocol": [{ + * "protocol": [1, 2], + * "id: "custom:id", + * "name": "Mod Name", + * "version": "v1.2.3", + * "require_client": false, + * "require_server": true + * }]
+ *

Array of full objects. Allows to define multiple versions of the protocol. The inner objects use the same format as single-full object format, + * with main exception being that fields "id", "name" and "version" aren't defaulted and need to be always set

+ *
+ *
+ *
{@link net.fabricmc.fabric.api.modprotocol.v1.ModProtocolRegistry}
+ *
This is the simplest way to define it. Can be useful when you don't need to change it depending on mods configuration + * or external dependencies. It is set within custom field of that file under "fabric:mod_protocol" key. + *
+ *
+ *

+ */ +@ApiStatus.Experimental +package net.fabricmc.fabric.api.modprotocol.v1; + +import org.jetbrains.annotations.ApiStatus; diff --git a/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/ModProtocolHolder.java b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/ModProtocolHolder.java new file mode 100644 index 0000000000..e783a9dc94 --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/ModProtocolHolder.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.modprotocol; + +import java.util.List; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.server.ServerMetadata; + +public interface ModProtocolHolder { + static ModProtocolHolder of(ServerMetadata input) { + return (ModProtocolHolder) (Object) input; + } + + @Nullable + List fabric$getModProtocol(); + void fabric$setModProtocol(List protocol); +} diff --git a/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/ModProtocolImpl.java b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/ModProtocolImpl.java new file mode 100644 index 0000000000..4e13786391 --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/ModProtocolImpl.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.modprotocol; + +import java.util.List; +import java.util.stream.IntStream; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import it.unimi.dsi.fastutil.ints.IntList; + +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.modprotocol.v1.ModProtocol; + +public record ModProtocolImpl(Identifier id, String name, String version, IntList protocol, boolean requireClient, boolean requireServer) implements ModProtocol { + public static final int UNSUPPORTED = -1; + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Identifier.CODEC.fieldOf("id").forGetter(ModProtocolImpl::id), + Codec.STRING.fieldOf("name").forGetter(ModProtocolImpl::name), + Codec.STRING.fieldOf("version").forGetter(ModProtocolImpl::version), + Codec.INT_STREAM.xmap(x -> IntList.of(x.toArray()), x -> IntStream.of(x.toIntArray())).fieldOf("protocol").forGetter(ModProtocolImpl::protocol), + Codec.BOOL.fieldOf("require_client").forGetter(ModProtocolImpl::requireClient), + Codec.BOOL.fieldOf("require_server").forGetter(ModProtocolImpl::requireServer) + ).apply(instance, ModProtocolImpl::new)); + public static final Codec> LIST_CODEC = CODEC.listOf(); + public static final PacketCodec PACKET_CODEC = PacketCodec.ofStatic(ModProtocolImpl::encode, ModProtocolImpl::decode); + + private static ModProtocolImpl decode(PacketByteBuf buf) { + Identifier id = buf.readIdentifier(); + String name = buf.readString(); + String version = buf.readString(); + IntList protocols = IntList.of(buf.readIntArray()); + byte b = buf.readByte(); + boolean requireClient = (b & 0b10) != 0; + boolean requireServer = (b & 0b01) != 0; + return new ModProtocolImpl(id, name, version, protocols, requireClient, requireServer); + } + + private static void encode(PacketByteBuf buf, ModProtocolImpl protocol) { + buf.writeIdentifier(protocol.id); + buf.writeString(protocol.name); + buf.writeString(protocol.version); + buf.writeIntArray(protocol.protocol.toIntArray()); + buf.writeByte((protocol.requireClient ? 0b10 : 0) | (protocol.requireServer ? 0b01 : 0)); + } + + public int getHighestVersion(IntList list) { + int value = UNSUPPORTED; + int size = list.size(); + + for (int i = 0; i < size; i++) { + int proto = list.getInt(i); + + if (this.protocol.contains(proto) && value < proto) { + value = proto; + } + } + + return value; + } + + public boolean syncWithServerMetadata() { + return this.requireClient() || this.requireServer(); + } +} diff --git a/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/ModProtocolInit.java b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/ModProtocolInit.java new file mode 100644 index 0000000000..8019b52aef --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/ModProtocolInit.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.modprotocol; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.minecraft.util.Identifier; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; +import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents; +import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking; +import net.fabricmc.fabric.impl.modprotocol.payload.ModProtocolRequestS2CPayload; +import net.fabricmc.fabric.impl.modprotocol.payload.ModProtocolResponseC2SPayload; + +public final class ModProtocolInit implements ModInitializer { + public static final String MOD_ID = "fabric-mod-protocol-api-v1"; + public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + public static boolean frozen = false; + + public void onInitialize() { + Identifier phase = Identifier.of("fabric", "mod_protocol"); + Identifier registrySync = Identifier.of("fabric", "registry_sync"); + + ServerConfigurationConnectionEvents.BEFORE_CONFIGURE.addPhaseOrdering(phase, Event.DEFAULT_PHASE); + ServerConfigurationConnectionEvents.BEFORE_CONFIGURE.addPhaseOrdering(phase, registrySync); + ServerConfigurationConnectionEvents.BEFORE_CONFIGURE.register(phase, ModProtocolManager::setupClient); + + PayloadTypeRegistry.configurationC2S().register(ModProtocolResponseC2SPayload.ID, ModProtocolResponseC2SPayload.PACKET_CODEC); + PayloadTypeRegistry.configurationS2C().register(ModProtocolRequestS2CPayload.ID, ModProtocolRequestS2CPayload.PACKET_CODEC); + ServerConfigurationNetworking.registerGlobalReceiver(ModProtocolResponseC2SPayload.ID, (payload, context) -> { + ((RemoteProtocolStorage) context.networkHandler()).fabric$setRemoteProtocol(payload.supported()); + context.networkHandler().completeTask(ModProtocolManager.SyncConfigurationTask.KEY); + }); + + ModProtocolManager.collectModProtocols(); + } +} diff --git a/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/ModProtocolLocator.java b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/ModProtocolLocator.java new file mode 100644 index 0000000000..fa15d54d87 --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/ModProtocolLocator.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.modprotocol; + +import java.util.function.BiConsumer; + +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; + +import net.minecraft.util.Identifier; + +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import net.fabricmc.loader.api.metadata.CustomValue; +import net.fabricmc.loader.api.metadata.ModMetadata; + +public class ModProtocolLocator { + public static void provide(BiConsumer consumer) { + for (ModContainer mod : FabricLoader.getInstance().getAllMods()) { + create(mod, consumer); + } + } + + private static void create(ModContainer container, BiConsumer consumer) { + ModMetadata meta = container.getMetadata(); + CustomValue definition = meta.getCustomValue("fabric:mod_protocol"); + + if (definition == null) { + return; + } + + if (definition.getType() == CustomValue.CvType.ARRAY) { + for (CustomValue entry : definition.getAsArray()) { + consumer.accept(container, decodeFullDefinition(entry, meta, true)); + } + } else if (definition.getType() == CustomValue.CvType.NUMBER) { + consumer.accept(container, new ModProtocolImpl(Identifier.of("mod", meta.getId()), meta.getName(), meta.getVersion().getFriendlyString(), IntList.of(definition.getAsNumber().intValue()), true, true)); + } else { + consumer.accept(container, decodeFullDefinition(definition, meta, false)); + } + } + + public static ModProtocolImpl decodeFullDefinition(CustomValue entry, ModMetadata meta, boolean requireFullData) { + if (entry.getType() != CustomValue.CvType.OBJECT) { + throw new RuntimeException("Mod Protocol entry provided by '" + meta.getId() + "' is not valid!"); + } + + CustomValue.CvObject object = entry.getAsObject(); + Identifier id; + String name; + String version; + boolean requiredClient; + boolean requiredServer; + IntList protocols = new IntArrayList(); + + CustomValue idField = object.get("id"); + CustomValue nameField = object.get("name"); + CustomValue versionField = object.get("version"); + CustomValue protocolField = object.get("protocol"); + CustomValue requiredClientField = object.get("require_client"); + CustomValue requiredServerField = object.get("require_server"); + + if (!requireFullData && idField == null) { + id = Identifier.of("mod", meta.getId()); + } else if (idField != null && idField.getType() == CustomValue.CvType.STRING) { + id = Identifier.of(idField.getAsString()); + } else { + throw new RuntimeException("Mod Protocol entry provided by '" + meta.getId() + "' is not valid!"); + } + + if (protocolField != null && protocolField.getType() == CustomValue.CvType.NUMBER) { + protocols.add(protocolField.getAsNumber().intValue()); + } else if (protocolField != null && protocolField.getType() == CustomValue.CvType.ARRAY) { + for (CustomValue value : protocolField.getAsArray()) { + if (value.getType() == CustomValue.CvType.NUMBER) { + protocols.add(value.getAsNumber().intValue()); + } else { + throw new RuntimeException("Mod Protocol entry provided by '" + meta.getId() + "' is not valid!"); + } + } + } else { + throw new RuntimeException("Mod Protocol entry provided by '" + meta.getId() + "' is not valid!"); + } + + if (!requireFullData && nameField == null) { + name = meta.getName(); + } else if (nameField != null && nameField.getType() == CustomValue.CvType.STRING) { + name = nameField.getAsString(); + } else { + throw new RuntimeException("Mod Protocol entry provided by '" + meta.getId() + "' is not valid!"); + } + + if (!requireFullData && versionField == null) { + version = meta.getName(); + } else if (versionField != null && versionField.getType() == CustomValue.CvType.STRING) { + version = versionField.getAsString(); + } else { + throw new RuntimeException("Mod Protocol entry provided by '" + meta.getId() + "' is not valid!"); + } + + if (requiredClientField == null) { + requiredClient = true; + } else if (requiredClientField.getType() == CustomValue.CvType.BOOLEAN) { + requiredClient = requiredClientField.getAsBoolean(); + } else { + throw new RuntimeException("Mod Protocol entry provided by '" + meta.getId() + "' is not valid!"); + } + + if (requiredServerField == null) { + requiredServer = true; + } else if (requiredServerField.getType() == CustomValue.CvType.BOOLEAN) { + requiredServer = requiredServerField.getAsBoolean(); + } else { + throw new RuntimeException("Mod Protocol entry provided by '" + meta.getId() + "' is not valid!"); + } + + return new ModProtocolImpl(id, name, version, IntList.of(protocols.toIntArray()), requiredClient, requiredServer); + } +} diff --git a/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/ModProtocolManager.java b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/ModProtocolManager.java new file mode 100644 index 0000000000..3d22b9fb14 --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/ModProtocolManager.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.modprotocol; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.network.packet.Packet; +import net.minecraft.network.packet.s2c.common.CustomPayloadS2CPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerConfigurationNetworkHandler; +import net.minecraft.server.network.ServerPlayerConfigurationTask; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking; +import net.fabricmc.fabric.impl.modprotocol.payload.ModProtocolRequestS2CPayload; +import net.fabricmc.loader.api.ModContainer; + +public final class ModProtocolManager { + public static final List NAMESPACE_PRIORITY = new ArrayList<>(List.of("special", "mod", "feature")); + public static final Comparator MOD_PROTOCOL_COMPARATOR = Comparator.comparingInt(x -> { + int out = NAMESPACE_PRIORITY.indexOf(x.id().getNamespace()); + return out == -1 ? NAMESPACE_PRIORITY.size() : out; + }).thenComparing(ModProtocolImpl::id); + + public static final Map LOCAL_MOD_PROTOCOLS_BY_ID = new HashMap<>(); + public static final List LOCAL_MOD_PROTOCOLS = new ArrayList<>(); + public static final List PING_SYNCED_PROTOCOLS = new ArrayList<>(); + public static final List CLIENT_REQUIRED = new ArrayList<>(); + public static final List SERVER_REQUIRED = new ArrayList<>(); + + public static void setupClient(ServerConfigurationNetworkHandler handler, MinecraftServer server) { + if (!ServerConfigurationNetworking.canSend(handler, ModProtocolRequestS2CPayload.ID)) { + if (CLIENT_REQUIRED.isEmpty()) { + return; + } else { + handler.disconnect(constructMessage(new ArrayList<>(CLIENT_REQUIRED), Map.of())); + } + } + + handler.addTask(new SyncConfigurationTask()); + } + + public static Text constructMessage(List missingProtocols, Map localProtocols) { + MutableText text = Text.empty(); + text.append(TextUtil.translatable("text.fabric.mod_protocol.mismatched.title").formatted(Formatting.GOLD)).append("\n"); + text.append(TextUtil.translatable("text.fabric.mod_protocol.mismatched.desc").formatted(Formatting.YELLOW)).append("\n\n"); + text.append(TextUtil.translatable("text.fabric.mod_protocol.mismatched.entries.title").formatted(Formatting.RED)).append("\n"); + appendTextEntries(missingProtocols, localProtocols, 6, text::append); + return text; + } + + public static void appendTextEntries(List missingProtocols, Map localProtocols, int limit, Consumer consumer) { + missingProtocols.sort(MOD_PROTOCOL_COMPARATOR); + + if (limit == -1) { + limit = missingProtocols.size(); + } + + int size = Math.min(limit, missingProtocols.size()); + + for (int i = 0; i < size; i++) { + ModProtocolImpl protocol = missingProtocols.get(i); + ModProtocolImpl local = localProtocols.get(protocol.id()); + Text localVersion = local == null ? TextUtil.translatable("text.fabric.mod_protocol.missing").formatted(Formatting.DARK_RED) + : Text.literal(local.version()).formatted(Formatting.YELLOW); + Text remoteVersion = local == protocol ? TextUtil.translatable("text.fabric.mod_protocol.missing").formatted(Formatting.DARK_RED) + : Text.literal(protocol.version()).formatted(Formatting.YELLOW); + + MutableText text = TextUtil.translatable("text.fabric.mod_protocol.entry", + Text.literal(protocol.name()).formatted(Formatting.WHITE), localVersion, remoteVersion).formatted(Formatting.GRAY); + + if (i + 1 < size) { + text.append("\n"); + } + + consumer.accept(text); + } + + if (limit < missingProtocols.size()) { + consumer.accept(Text.literal("\n").append(TextUtil.translatable("text.fabric.mod_protocol.and_x_more", missingProtocols.size() - size).formatted(Formatting.GRAY, Formatting.ITALIC))); + } + } + + public static ValidationResult validateClient(Map received) { + return validate(received, LOCAL_MOD_PROTOCOLS_BY_ID, SERVER_REQUIRED); + } + + public static ValidationResult validate(Map received, Map localById, List requiredRemote) { + var supported = new Object2IntOpenHashMap(); + var missingLocal = new ArrayList(); + var missingRemote = new ArrayList(); + + for (ModProtocolImpl modProtocol : received.values()) { + ModProtocolImpl local = localById.get(modProtocol.id()); + + if (local != null) { + int version = local.getHighestVersion(modProtocol.protocol()); + + if (version != -1) { + supported.put(modProtocol.id(), version); + } else if (modProtocol.requireClient()) { + missingLocal.add(modProtocol); + } + } else if (modProtocol.requireClient()) { + missingLocal.add(modProtocol); + } + } + + for (ModProtocolImpl modProtocol : requiredRemote) { + ModProtocolImpl remote = received.get(modProtocol.id()); + + if (remote == null) { + missingRemote.add(modProtocol); + } + } + + return new ValidationResult(supported, missingLocal, missingRemote); + } + + public static void collectModProtocols() { + ModProtocolLocator.provide(ModProtocolManager::add); + } + + public static ModProtocolImpl add(@Nullable ModContainer container, ModProtocolImpl protocol) { + if (LOCAL_MOD_PROTOCOLS_BY_ID.containsKey(protocol.id())) { + if (container != null) { + ModProtocolInit.LOGGER.warn("Found duplicate protocol id '{}' provided by mod '{}'", protocol.id(), container.getMetadata().getId()); + } else { + ModProtocolInit.LOGGER.warn("Found duplicate protocol id '{}' registered by a mod!'", protocol.id(), new RuntimeException()); + } + + return LOCAL_MOD_PROTOCOLS_BY_ID.get(protocol.id()); + } + + LOCAL_MOD_PROTOCOLS_BY_ID.put(protocol.id(), protocol); + LOCAL_MOD_PROTOCOLS.add(protocol); + + if (protocol.requireClient()) { + CLIENT_REQUIRED.add(protocol); + } + + if (protocol.requireServer()) { + SERVER_REQUIRED.add(protocol); + } + + if (protocol.syncWithServerMetadata()) { + PING_SYNCED_PROTOCOLS.add(protocol); + } + + return null; + } + + @SuppressWarnings("ConstantValue") + public static boolean registerOrder(String firstNamespace, String secondNamespace) { + if (firstNamespace.equals(secondNamespace)) { + return false; + } + + int firstIndex = NAMESPACE_PRIORITY.indexOf(firstNamespace); + int secondIndex = NAMESPACE_PRIORITY.indexOf(secondNamespace); + + if (firstIndex != -1 && secondIndex != -1) { + if (firstIndex > secondIndex) { + ModProtocolInit.LOGGER.warn("Protocol '{}' is already set to display after '{}'!", firstNamespace, secondNamespace); + return false; + } + + return true; + } else if (firstIndex == -1) { + NAMESPACE_PRIORITY.add(secondIndex, firstNamespace); + } else if (secondIndex == -1) { + NAMESPACE_PRIORITY.add(firstIndex + 1, secondNamespace); + } else { + NAMESPACE_PRIORITY.add(firstNamespace); + NAMESPACE_PRIORITY.add(secondNamespace); + } + + return true; + } + + public static class SyncConfigurationTask implements ServerPlayerConfigurationTask { + public static final Key KEY = new Key("fabric:mod_protocol_sync"); + + @Override + public void sendPacket(Consumer> sender) { + sender.accept(new CustomPayloadS2CPacket(new ModProtocolRequestS2CPayload(LOCAL_MOD_PROTOCOLS))); + } + + @Override + public Key getKey() { + return KEY; + } + } + + public record ValidationResult(Object2IntMap supportedProtocols, List missingLocal, List missingRemote) { + public boolean isSuccess() { + return missingLocal.isEmpty() && missingRemote.isEmpty(); + } + + public List missing() { + var arr = new ArrayList(); + arr.addAll(missingLocal); + arr.addAll(missingRemote); + return arr; + } + } +} diff --git a/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/RemoteProtocolStorage.java b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/RemoteProtocolStorage.java new file mode 100644 index 0000000000..860bdd1c2d --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/RemoteProtocolStorage.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.modprotocol; + +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntMaps; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.util.Identifier; + +public interface RemoteProtocolStorage { + static int getProtocol(Object object, Identifier identifier) { + if (object instanceof RemoteProtocolStorage storage) { + Object2IntMap map = storage.fabric$getRemoteProtocol(); + + if (map != null) { + return map.getOrDefault(identifier, -1); + } + } + + return -1; + } + + static Object2IntMap getMap(Object object) { + if (object instanceof RemoteProtocolStorage storage) { + Object2IntMap map = storage.fabric$getRemoteProtocol(); + + if (map != null) { + return Object2IntMaps.unmodifiable(map); + } + } + + return Object2IntMaps.emptyMap(); + } + + @Nullable + Object2IntMap fabric$getRemoteProtocol(); + void fabric$setRemoteProtocol(Object2IntMap protocol); +} diff --git a/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/TextUtil.java b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/TextUtil.java new file mode 100644 index 0000000000..d9d13c1a3c --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/TextUtil.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.modprotocol; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; + +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; + +public class TextUtil { + private static final Map FALLBACK_TRANSLATIONS = new HashMap<>(); + + static { + try { + Optional container = FabricLoader.getInstance().getModContainer(ModProtocolInit.MOD_ID); + Optional path = container.get().findPath("assets/fabric-mod-protocol-api-v1/lang/en_us.json"); + JsonObject lang = JsonParser.parseString(Files.readString(path.get())).getAsJsonObject(); + + for (String key : lang.keySet()) { + FALLBACK_TRANSLATIONS.put(key, lang.get(key).getAsString()); + } + } catch (Throwable e) { + ModProtocolInit.LOGGER.error("Failed to load translation file!", e); + } + } + + public static MutableText translatable(String key, Object... args) { + return Text.translatableWithFallback(key, FALLBACK_TRANSLATIONS.get(key), args); + } + + public static MutableText translatable(String key) { + return Text.translatableWithFallback(key, FALLBACK_TRANSLATIONS.get(key)); + } +} diff --git a/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/payload/ModProtocolRequestS2CPayload.java b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/payload/ModProtocolRequestS2CPayload.java new file mode 100644 index 0000000000..d170f00d94 --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/payload/ModProtocolRequestS2CPayload.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.modprotocol.payload; + +import java.util.List; + +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.codec.PacketCodecs; +import net.minecraft.network.packet.CustomPayload; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.impl.modprotocol.ModProtocolImpl; + +public record ModProtocolRequestS2CPayload(List modProtocol) implements CustomPayload { + public static final Id ID = new Id<>(Identifier.of("fabric", "mod_protocol_request")); + public static final PacketCodec PACKET_CODEC = ModProtocolImpl.PACKET_CODEC.collect(PacketCodecs.toList()) + .xmap(ModProtocolRequestS2CPayload::new, ModProtocolRequestS2CPayload::modProtocol); + + @Override + public Id getId() { + return ID; + } +} diff --git a/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/payload/ModProtocolResponseC2SPayload.java b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/payload/ModProtocolResponseC2SPayload.java new file mode 100644 index 0000000000..b16f9cdf0b --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/payload/ModProtocolResponseC2SPayload.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.modprotocol.payload; + +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; + +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.codec.PacketCodecs; +import net.minecraft.network.packet.CustomPayload; +import net.minecraft.util.Identifier; + +public record ModProtocolResponseC2SPayload(Object2IntMap supported) implements CustomPayload { + public static final Id ID = new Id<>(Identifier.of("fabric", "mod_protocol/response")); + public static final PacketCodec PACKET_CODEC = + PacketCodecs.map(ModProtocolResponseC2SPayload::createMap, Identifier.PACKET_CODEC, PacketCodecs.INTEGER) + .xmap(ModProtocolResponseC2SPayload::new, ModProtocolResponseC2SPayload::supported).cast(); + + private static Object2IntMap createMap(int i) { + return new Object2IntOpenHashMap<>(i); + } + + @Override + public Id getId() { + return ID; + } +} diff --git a/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/mixin/modprotocol/ClientConnectionMixin.java b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/mixin/modprotocol/ClientConnectionMixin.java new file mode 100644 index 0000000000..822ebb9dfd --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/mixin/modprotocol/ClientConnectionMixin.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.modprotocol; + +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; + +import net.minecraft.network.ClientConnection; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.impl.modprotocol.RemoteProtocolStorage; + +@Mixin(ClientConnection.class) +public class ClientConnectionMixin implements RemoteProtocolStorage { + @Unique + @Nullable + private Object2IntMap remoteProtocol = null; + + @Override + public Object2IntMap fabric$getRemoteProtocol() { + return this.remoteProtocol; + } + + @Override + public void fabric$setRemoteProtocol(Object2IntMap protocol) { + this.remoteProtocol = protocol; + } +} diff --git a/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/mixin/modprotocol/MinecraftServerMixin.java b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/mixin/modprotocol/MinecraftServerMixin.java new file mode 100644 index 0000000000..c7fd9b2b7b --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/mixin/modprotocol/MinecraftServerMixin.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.modprotocol; + +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.ServerMetadata; + +import net.fabricmc.fabric.impl.modprotocol.ModProtocolHolder; +import net.fabricmc.fabric.impl.modprotocol.ModProtocolManager; + +@Mixin(MinecraftServer.class) +public class MinecraftServerMixin { + @ModifyReturnValue(method = "createMetadata", at = @At("RETURN")) + private ServerMetadata addModProtocol(ServerMetadata original) { + if (!ModProtocolManager.PING_SYNCED_PROTOCOLS.isEmpty()) { + ModProtocolHolder.of(original).fabric$setModProtocol(ModProtocolManager.PING_SYNCED_PROTOCOLS); + } + + return original; + } +} diff --git a/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/mixin/modprotocol/RegistriesMixin.java b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/mixin/modprotocol/RegistriesMixin.java new file mode 100644 index 0000000000..affb107254 --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/mixin/modprotocol/RegistriesMixin.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.modprotocol; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.registry.Registries; + +import net.fabricmc.fabric.impl.modprotocol.ModProtocolInit; + +@Mixin(Registries.class) +public class RegistriesMixin { + @Inject(method = "freezeRegistries", at = @At("TAIL")) + private static void onRegistryFrozen(CallbackInfo ci) { + ModProtocolInit.frozen = true; + } +} diff --git a/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/mixin/modprotocol/ServerCommonNetworkHandlerMixin.java b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/mixin/modprotocol/ServerCommonNetworkHandlerMixin.java new file mode 100644 index 0000000000..9b07f101c3 --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/mixin/modprotocol/ServerCommonNetworkHandlerMixin.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.modprotocol; + +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import net.minecraft.network.ClientConnection; +import net.minecraft.server.network.ServerCommonNetworkHandler; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.impl.modprotocol.RemoteProtocolStorage; + +@Mixin(ServerCommonNetworkHandler.class) +public class ServerCommonNetworkHandlerMixin implements RemoteProtocolStorage { + @Shadow + @Final + protected ClientConnection connection; + + @Override + public Object2IntMap fabric$getRemoteProtocol() { + return ((RemoteProtocolStorage) this.connection).fabric$getRemoteProtocol(); + } + + @Override + public void fabric$setRemoteProtocol(Object2IntMap protocol) { + ((RemoteProtocolStorage) this.connection).fabric$setRemoteProtocol(protocol); + } +} diff --git a/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/mixin/modprotocol/ServerMetadataMixin.java b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/mixin/modprotocol/ServerMetadataMixin.java new file mode 100644 index 0000000000..c2c4a0833f --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/mixin/modprotocol/ServerMetadataMixin.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.modprotocol; + +import java.util.List; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +import net.minecraft.server.ServerMetadata; + +import net.fabricmc.fabric.impl.modprotocol.ModProtocolHolder; +import net.fabricmc.fabric.impl.modprotocol.ModProtocolImpl; + +@Mixin(ServerMetadata.class) +public class ServerMetadataMixin implements ModProtocolHolder { + @Unique + @Nullable + private List modProtocol; + + @Override + public List fabric$getModProtocol() { + return this.modProtocol; + } + + @Override + public void fabric$setModProtocol(List protocol) { + this.modProtocol = protocol; + } + + @ModifyExpressionValue(method = "", at = @At(value = "INVOKE", target = "Lcom/mojang/serialization/codecs/RecordCodecBuilder;create(Ljava/util/function/Function;)Lcom/mojang/serialization/Codec;")) + private static Codec extendCodec(Codec original) { + return new Codec<>() { + @Override + public DataResult> decode(DynamicOps ops, T input) { + DataResult> decoded = original.decode(ops, input); + + if (decoded.isSuccess()) { + DataResult protocol = ops.get(input, "fabric:mod_protocol"); + + if (protocol.isSuccess()) { + DataResult, T>> result = ModProtocolImpl.LIST_CODEC.decode(ops, protocol.getOrThrow()); + + if (result.isSuccess()) { + ModProtocolHolder.of(decoded.getOrThrow().getFirst()).fabric$setModProtocol(result.getOrThrow().getFirst()); + } + } + } + + return decoded; + } + + @Override + public DataResult encode(ServerMetadata input, DynamicOps ops, T prefix) { + DataResult encode = original.encode(input, ops, prefix); + + if (encode.isSuccess() && ModProtocolHolder.of(input).fabric$getModProtocol() != null) { + DataResult protocol = ModProtocolImpl.LIST_CODEC.encodeStart(ops, ModProtocolHolder.of(input).fabric$getModProtocol()); + + if (protocol.isSuccess()) { + encode = ops.mergeToMap(encode.getOrThrow(), ops.createString("fabric:mod_protocol"), protocol.getOrThrow()); + } + } + + return encode; + } + }; + } +} diff --git a/fabric-mod-protocol-api-v1/src/main/resources/assets/fabric-mod-protocol-api-v1/icon.png b/fabric-mod-protocol-api-v1/src/main/resources/assets/fabric-mod-protocol-api-v1/icon.png new file mode 100644 index 0000000000..2931efbf61 Binary files /dev/null and b/fabric-mod-protocol-api-v1/src/main/resources/assets/fabric-mod-protocol-api-v1/icon.png differ diff --git a/fabric-mod-protocol-api-v1/src/main/resources/assets/fabric-mod-protocol-api-v1/lang/en_us.json b/fabric-mod-protocol-api-v1/src/main/resources/assets/fabric-mod-protocol-api-v1/lang/en_us.json new file mode 100644 index 0000000000..02b67a2a94 --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/main/resources/assets/fabric-mod-protocol-api-v1/lang/en_us.json @@ -0,0 +1,8 @@ +{ + "text.fabric.mod_protocol.mismatched.title": "Required protocols are mismatched!", + "text.fabric.mod_protocol.mismatched.desc": "This usually means mods on your client don't match with the server!", + "text.fabric.mod_protocol.mismatched.entries.title": "== Mismatched entries ==", + "text.fabric.mod_protocol.and_x_more": "...and %s more...", + "text.fabric.mod_protocol.missing": "Missing!", + "text.fabric.mod_protocol.entry": "%s (Local: %s, Remote: %s)" +} diff --git a/fabric-mod-protocol-api-v1/src/main/resources/fabric-mod-protocol-api-v1.mixins.json b/fabric-mod-protocol-api-v1/src/main/resources/fabric-mod-protocol-api-v1.mixins.json new file mode 100644 index 0000000000..af5ef83df3 --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/main/resources/fabric-mod-protocol-api-v1.mixins.json @@ -0,0 +1,15 @@ +{ + "required": true, + "package": "net.fabricmc.fabric.mixin.modprotocol", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "ClientConnectionMixin", + "MinecraftServerMixin", + "RegistriesMixin", + "ServerCommonNetworkHandlerMixin", + "ServerMetadataMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/fabric-mod-protocol-api-v1/src/main/resources/fabric.mod.json b/fabric-mod-protocol-api-v1/src/main/resources/fabric.mod.json new file mode 100644 index 0000000000..2cf3c76866 --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/main/resources/fabric.mod.json @@ -0,0 +1,42 @@ +{ + "schemaVersion": 1, + "id": "fabric-mod-protocol-api-v1", + "name": "Fabric Mod Protocol API (v1)", + "description": "Versioned network protocol sync, validation and requirement system.", + "version": "${version}", + "environment": "*", + "license": "Apache-2.0", + "icon": "assets/fabric-mod-protocol-api-v1/icon.png", + "contact": { + "homepage": "https://fabricmc.net", + "irc": "irc://irc.esper.net:6667/fabric", + "issues": "https://github.com/FabricMC/fabric/issues", + "sources": "https://github.com/FabricMC/fabric" + }, + "authors": [ + "FabricMC" + ], + "entrypoints": { + "main": [ + "net.fabricmc.fabric.impl.modprotocol.ModProtocolInit" + ], + "client": [ + "net.fabricmc.fabric.impl.modprotocol.client.ClientModProtocolInit" + ] + }, + "mixins": [ + "fabric-mod-protocol-api-v1.mixins.json", + { + "config": "fabric-mod-protocol-api-v1.client.mixins.json", + "environment": "client" + } + ], + "depends": { + "fabricloader": ">=0.15.11", + "fabric-api-base": "*", + "fabric-networking-api-v1": "*" + }, + "custom": { + "fabric-api:module-lifecycle": "experimental" + } +} diff --git a/fabric-mod-protocol-api-v1/src/test/java/net/fabricmc/fabric/test/modprotocol/unit/VersionMatchingTests.java b/fabric-mod-protocol-api-v1/src/test/java/net/fabricmc/fabric/test/modprotocol/unit/VersionMatchingTests.java new file mode 100644 index 0000000000..1ec71c4a69 --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/test/java/net/fabricmc/fabric/test/modprotocol/unit/VersionMatchingTests.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.modprotocol.unit; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; + +public class VersionMatchingTests { + @BeforeAll + static void beforeAll() { } + + @BeforeEach + void setUp() { } +} diff --git a/fabric-mod-protocol-api-v1/src/testmod/java/net/fabricmc/fabric/test/modprotocol/ModProtocolTestmods.java b/fabric-mod-protocol-api-v1/src/testmod/java/net/fabricmc/fabric/test/modprotocol/ModProtocolTestmods.java new file mode 100644 index 0000000000..b9c7760e94 --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/testmod/java/net/fabricmc/fabric/test/modprotocol/ModProtocolTestmods.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.modprotocol; + +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.minecraft.util.Identifier; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.modprotocol.v1.ModProtocolIds; +import net.fabricmc.fabric.api.modprotocol.v1.ModProtocolRegistry; +import net.fabricmc.fabric.api.modprotocol.v1.ServerModProtocolLookup; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.fabricmc.fabric.impl.modprotocol.ModProtocolLocator; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import net.fabricmc.loader.api.metadata.CustomValue; + +public final class ModProtocolTestmods implements ModInitializer { + public static final String ID = "fabric-mod-protocol-api-v1-testmod"; + public static final Logger LOGGER = LoggerFactory.getLogger(ID); + + public static Identifier id(String name) { + return Identifier.of(ID, name); + } + + public void onInitialize() { + ModContainer modContainer = FabricLoader.getInstance().getModContainer(ID).get(); + + ModProtocolRegistry.register(ModProtocolIds.special("test_modification"), "Hello there", "1.12.2", IntList.of(1, 2, 3), true, false); + ModProtocolRegistry.register(ModProtocolIds.special("test_modification2"), "Test2", "1.0", IntList.of(1), false, true); + ModProtocolRegistry.register(ModProtocolIds.special("test_modification3"), "Test3", "2.0", IntList.of(2), true, true); + ModProtocolRegistry.register(ModProtocolIds.special("test_modification4"), "Test4", "2.0", IntList.of(2), true, true); + //ModProtocolRegistry.register(ModProtocolIds.special("test_modification5"), "Test5", "1.0", IntList.of(1), true, true); + ModProtocolRegistry.register(ModProtocolIds.special("test_modification6"), "Test6", "1.0", IntList.of(1), true, true); + ModProtocolRegistry.register(ModProtocolIds.special("test_modification7"), "Test7", "1.0", IntList.of(1), true, true); + + CustomValue testificate = modContainer.getMetadata().getCustomValue("test_fabric:mod_protocol"); + CustomValue defaulted = modContainer.getMetadata().getCustomValue("test2_fabric:mod_protocol"); + + ServerPlayConnectionEvents.JOIN.register(((handler, sender, server) -> { + Object2IntMap protocols = ServerModProtocolLookup.getAllSupportedProtocols(handler); + LOGGER.info("Protocols supported by {}", handler.getDebugProfile().getName()); + + for (Object2IntMap.Entry entry : protocols.object2IntEntrySet()) { + LOGGER.info(" - {}: {}", entry.getKey(), entry.getIntValue()); + } + })); + + LOGGER.info("Parser full array-like, {}", ModProtocolLocator.decodeFullDefinition(testificate, modContainer.getMetadata(), true)); + LOGGER.info("Parser default, no defaults: {}", ModProtocolLocator.decodeFullDefinition(testificate, modContainer.getMetadata(), false)); + LOGGER.info("Parser default, with defaults: {}", ModProtocolLocator.decodeFullDefinition(defaulted, modContainer.getMetadata(), false)); + } +} diff --git a/fabric-mod-protocol-api-v1/src/testmod/resources/assets/fabric-networking-api-v1-testmod/lang/en_us.json b/fabric-mod-protocol-api-v1/src/testmod/resources/assets/fabric-networking-api-v1-testmod/lang/en_us.json new file mode 100644 index 0000000000..e8773318a1 --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/testmod/resources/assets/fabric-networking-api-v1-testmod/lang/en_us.json @@ -0,0 +1,5 @@ +{ + "key.category.fabric-networking-api-v1-testmod": "Fabric Network Test", + "key.fabric-networking-api-v1-testmod.test": "Send test packet", + "key.fabric-networking-api-v1-testmod.open": "Open channel tester" +} diff --git a/fabric-mod-protocol-api-v1/src/testmod/resources/fabric.mod.json b/fabric-mod-protocol-api-v1/src/testmod/resources/fabric.mod.json new file mode 100644 index 0000000000..e75a481e0c --- /dev/null +++ b/fabric-mod-protocol-api-v1/src/testmod/resources/fabric.mod.json @@ -0,0 +1,30 @@ +{ + "schemaVersion": 1, + "id": "fabric-mod-protocol-api-v1-testmod", + "name": "Fabric Mod Protocol API (v1) Test Mod", + "version": "1.0.0", + "environment": "*", + "license": "Apache-2.0", + "depends": { + "fabric-mod-protocol-api-v1": "*" + }, + "entrypoints": { + "main": [ + "net.fabricmc.fabric.test.modprotocol.ModProtocolTestmods" + ] + }, + "custom": { + "fabric:mod_protocol": 5, + "test_fabric:mod_protocol": { + "name": "Name", + "id": "special:id", + "protocol": [0, 1, 2, 3], + "version": "Tater", + "require_client": false, + "require_server": false + }, + "test2_fabric:mod_protocol": { + "protocol": [0, 1, 2, 3] + } + } +} diff --git a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/api/client/networking/v1/ClientConfigurationNetworking.java b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/api/client/networking/v1/ClientConfigurationNetworking.java index 4bf48a9948..ac8b0cb663 100644 --- a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/api/client/networking/v1/ClientConfigurationNetworking.java +++ b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/api/client/networking/v1/ClientConfigurationNetworking.java @@ -23,6 +23,7 @@ import org.jetbrains.annotations.Nullable; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientConfigurationNetworkHandler; import net.minecraft.network.packet.CustomPayload; import net.minecraft.util.Identifier; import net.minecraft.util.thread.ThreadExecutor; @@ -280,6 +281,11 @@ public interface Context { */ MinecraftClient client(); + /** + * @return The ClientConfigurationNetworkHandler instance + */ + ClientConfigurationNetworkHandler networkHandler(); + /** * @return The packet sender */ diff --git a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientConfigurationNetworkAddon.java b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientConfigurationNetworkAddon.java index d391223bb3..17cbcab37b 100644 --- a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientConfigurationNetworkAddon.java +++ b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientConfigurationNetworkAddon.java @@ -44,7 +44,7 @@ public final class ClientConfigurationNetworkAddon extends ClientCommonNetworkAd public ClientConfigurationNetworkAddon(ClientConfigurationNetworkHandler handler, MinecraftClient client) { super(ClientNetworkingImpl.CONFIGURATION, ((ClientCommonNetworkHandlerAccessor) handler).getConnection(), "ClientPlayNetworkAddon for " + ((ClientConfigurationNetworkHandlerAccessor) handler).getProfile().getName(), handler, client); - this.context = new ContextImpl(client, this); + this.context = new ContextImpl(client, handler, this); // Must register pending channels via lateinit this.registerPendingChannels((ChannelInfoHolder) this.connection, NetworkPhase.CONFIGURATION); @@ -128,9 +128,10 @@ public ChannelInfoHolder getChannelInfoHolder() { return (ChannelInfoHolder) ((ClientCommonNetworkHandlerAccessor) handler).getConnection(); } - private record ContextImpl(MinecraftClient client, PacketSender responseSender) implements ClientConfigurationNetworking.Context { + private record ContextImpl(MinecraftClient client, ClientConfigurationNetworkHandler networkHandler, PacketSender responseSender) implements ClientConfigurationNetworking.Context { private ContextImpl { Objects.requireNonNull(client, "client"); + Objects.requireNonNull(networkHandler, "networkHandler"); Objects.requireNonNull(responseSender, "responseSender"); } } diff --git a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientLoginNetworkAddon.java b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientLoginNetworkAddon.java index e050c0326c..f4e46131aa 100644 --- a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientLoginNetworkAddon.java +++ b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientLoginNetworkAddon.java @@ -103,6 +103,11 @@ protected void handleRegistration(Identifier channelName) { protected void handleUnregistration(Identifier channelName) { } + @Override + public @Nullable String getBrand() { + return null; + } + @Override protected void invokeDisconnectEvent() { ClientLoginConnectionEvents.DISCONNECT.invoker().onLoginDisconnect(this.handler, this.client); diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerConfigurationNetworking.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerConfigurationNetworking.java index 7f6c36cef8..79d8e7632c 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerConfigurationNetworking.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerConfigurationNetworking.java @@ -224,6 +224,19 @@ public static void send(ServerConfigurationNetworkHandler handler, CustomPayload // Helper methods + /** + * Gets the brand of the client the player connected with. + * + * @param handler the network handler, representing the connection to the player/client + * @return client's brand, or {@code null} if not sent + */ + @Nullable + public static String getBrand(ServerConfigurationNetworkHandler handler) { + Objects.requireNonNull(handler, "Server configuration network handler cannot be null"); + + return ServerNetworkingImpl.getAddon(handler).getBrand(); + } + /** * Returns the Minecraft Server of a server configuration network handler. * diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerPlayNetworking.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerPlayNetworking.java index 23d415919b..9b498c35fa 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerPlayNetworking.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerPlayNetworking.java @@ -293,6 +293,32 @@ public static void send(ServerPlayerEntity player, CustomPayload payload) { player.networkHandler.sendPacket(createS2CPacket(payload)); } + /** + * Gets the brand of the client the player connected with. + * + * @param player the player + * @return client's brand, or {@code null} if not sent + */ + @Nullable + public static String getBrand(ServerPlayerEntity player) { + Objects.requireNonNull(player, "Server player entity cannot be null"); + + return getBrand(player.networkHandler); + } + + /** + * Gets the brand of the client the player connected with. + * + * @param handler the network handler, representing the connection to the player/client + * @return client's brand, or {@code null} if not sent + */ + @Nullable + public static String getBrand(ServerPlayNetworkHandler handler) { + Objects.requireNonNull(handler, "Server play network handler cannot be null"); + + return ServerNetworkingImpl.getAddon(handler).getBrand(); + } + private ServerPlayNetworking() { } diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/AbstractChanneledNetworkAddon.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/AbstractChanneledNetworkAddon.java index efae4a474b..de59ad37f3 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/AbstractChanneledNetworkAddon.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/AbstractChanneledNetworkAddon.java @@ -29,6 +29,7 @@ import net.minecraft.network.ClientConnection; import net.minecraft.network.NetworkPhase; import net.minecraft.network.PacketCallbacks; +import net.minecraft.network.packet.BrandCustomPayload; import net.minecraft.network.packet.CustomPayload; import net.minecraft.network.packet.Packet; import net.minecraft.text.Text; @@ -52,6 +53,8 @@ public abstract class AbstractChanneledNetworkAddon extends AbstractNetworkAd protected final Set sendableChannels; protected int commonVersion = -1; + @Nullable + protected String brand = null; protected AbstractChanneledNetworkAddon(GlobalReceiverRegistry receiver, ClientConnection connection, String description) { super(receiver, description); @@ -87,6 +90,10 @@ public boolean handle(CustomPayload payload) { } } + if (payload instanceof BrandCustomPayload brand) { + this.brand = brand.brand(); + } + @Nullable H handler = this.getHandler(channelName); if (handler == null) { @@ -218,6 +225,15 @@ public CommonRegisterPayload createRegisterPayload() { return new CommonRegisterPayload(getNegotiatedVersion(), getPhase(), this.getReceivableChannels()); } + @Nullable + public String getBrand() { + return this.brand; + } + + public void setBrand(String brand) { + this.brand = brand; + } + @Override public int getNegotiatedVersion() { if (commonVersion == -1) { diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/AbstractNetworkAddon.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/AbstractNetworkAddon.java index 3ce0a675c9..2266c8b8c9 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/AbstractNetworkAddon.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/AbstractNetworkAddon.java @@ -155,6 +155,9 @@ public Set getReceivableChannels() { protected abstract void handleUnregistration(Identifier channelName); + @Nullable + public abstract String getBrand(); + public final void handleDisconnect() { if (disconnected.compareAndSet(false, true)) { invokeDisconnectEvent(); diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerLoginNetworkAddon.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerLoginNetworkAddon.java index 8615467f45..0d214db343 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerLoginNetworkAddon.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerLoginNetworkAddon.java @@ -195,6 +195,11 @@ protected void handleRegistration(Identifier channelName) { protected void handleUnregistration(Identifier channelName) { } + @Override + public @Nullable String getBrand() { + return null; + } + @Override protected void invokeDisconnectEvent() { ServerLoginConnectionEvents.DISCONNECT.invoker().onLoginDisconnect(this.handler, this.server); diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerPlayNetworkHandlerMixin.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerPlayNetworkHandlerMixin.java index 7fae773f1d..d2e0ae5967 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerPlayNetworkHandlerMixin.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerPlayNetworkHandlerMixin.java @@ -16,6 +16,7 @@ package net.fabricmc.fabric.mixin.networking; +import com.llamalad7.mixinextras.sugar.Local; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -44,9 +45,13 @@ abstract class ServerPlayNetworkHandlerMixin extends ServerCommonNetworkHandler } @Inject(method = "", at = @At("RETURN")) - private void initAddon(CallbackInfo ci) { + private void initAddon(CallbackInfo ci, @Local(argsOnly = true) ClientConnection connection) { this.addon = new ServerPlayNetworkAddon((ServerPlayNetworkHandler) (Object) this, connection, server); + if (connection.getPacketListener() instanceof NetworkHandlerExtensions extension) { + this.addon.setBrand(extension.getAddon().getBrand()); + } + if (!(this instanceof UntrackedNetworkHandler)) { // A bit of a hack but it allows the field above to be set in case someone registers handlers during INIT event which refers to said field this.addon.lateInit(); diff --git a/fabric-networking-api-v1/src/test/java/net/fabricmc/fabric/test/networking/unit/CommonPacketTests.java b/fabric-networking-api-v1/src/test/java/net/fabricmc/fabric/test/networking/unit/CommonPacketTests.java index 1674ef82cc..16ff78a53b 100644 --- a/fabric-networking-api-v1/src/test/java/net/fabricmc/fabric/test/networking/unit/CommonPacketTests.java +++ b/fabric-networking-api-v1/src/test/java/net/fabricmc/fabric/test/networking/unit/CommonPacketTests.java @@ -138,6 +138,11 @@ public MinecraftClient client() { return null; } + @Override + public ClientConfigurationNetworkHandler networkHandler() { + return null; + } + @Override public PacketSender responseSender() { return packetSender; diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/FabricRegistryInit.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/FabricRegistryInit.java index ef20ab4160..3bc0a7b039 100644 --- a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/FabricRegistryInit.java +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/FabricRegistryInit.java @@ -17,8 +17,10 @@ package net.fabricmc.fabric.impl.registry.sync; import net.minecraft.registry.Registries; +import net.minecraft.util.Identifier; import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.registry.RegistryAttribute; import net.fabricmc.fabric.api.event.registry.RegistryAttributeHolder; import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; @@ -32,7 +34,10 @@ public void onInitialize() { PayloadTypeRegistry.configurationC2S().register(SyncCompletePayload.ID, SyncCompletePayload.CODEC); PayloadTypeRegistry.configurationS2C().register(DirectRegistryPacketHandler.Payload.ID, DirectRegistryPacketHandler.Payload.CODEC); - ServerConfigurationConnectionEvents.BEFORE_CONFIGURE.register(RegistrySyncManager::configureClient); + Identifier registrySync = Identifier.of("fabric", "registry_sync"); + + ServerConfigurationConnectionEvents.BEFORE_CONFIGURE.addPhaseOrdering(registrySync, Event.DEFAULT_PHASE); + ServerConfigurationConnectionEvents.BEFORE_CONFIGURE.register(registrySync, RegistrySyncManager::configureClient); ServerConfigurationNetworking.registerGlobalReceiver(SyncCompletePayload.ID, (payload, context) -> { context.networkHandler().completeTask(RegistrySyncManager.SyncConfigurationTask.KEY); }); diff --git a/gradle.properties b/gradle.properties index a98e51cb8e..e1545cd567 100644 --- a/gradle.properties +++ b/gradle.properties @@ -38,6 +38,7 @@ fabric-lifecycle-events-v1-version=2.3.12 fabric-loot-api-v2-version=3.0.14 fabric-loot-api-v3-version=1.0.2 fabric-message-api-v1-version=6.0.13 +fabric-mod-protocol-api-v1-version=1.0.0 fabric-model-loading-api-v1-version=2.0.0 fabric-networking-api-v1-version=4.2.2 fabric-object-builder-api-v1-version=15.2.0 diff --git a/settings.gradle b/settings.gradle index 30f7ff72cb..a033c2d74c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -40,6 +40,7 @@ include 'fabric-lifecycle-events-v1' include 'fabric-loot-api-v3' include 'fabric-message-api-v1' include 'fabric-model-loading-api-v1' +include 'fabric-mod-protocol-api-v1' include 'fabric-networking-api-v1' include 'fabric-object-builder-api-v1' include 'fabric-particles-v1'