diff options
author | Linnea Gräf <nea@nea.moe> | 2024-05-29 21:09:07 +0200 |
---|---|---|
committer | Linnea Gräf <nea@nea.moe> | 2024-06-01 01:34:34 +0200 |
commit | e630426656b713ec67bfe7ede2f2081751e349db (patch) | |
tree | 63eff82e16d400a42b5a48bfcb6c89ec81c6763e | |
parent | fac6103658b2c8d6bab3598606d57041cfe16e0c (diff) | |
download | firmament-e630426656b713ec67bfe7ede2f2081751e349db.tar.gz firmament-e630426656b713ec67bfe7ede2f2081751e349db.tar.bz2 firmament-e630426656b713ec67bfe7ede2f2081751e349db.zip |
[WIP] Add mod api
11 files changed, 457 insertions, 0 deletions
diff --git a/src/main/java/moe/nea/firmament/mixins/CustomPayloadEventDispatcher.java b/src/main/java/moe/nea/firmament/mixins/CustomPayloadEventDispatcher.java new file mode 100644 index 0000000..66710eb --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/CustomPayloadEventDispatcher.java @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.mixins; + +import moe.nea.firmament.apis.ingame.FirmamentCustomPayload; +import moe.nea.firmament.events.FirmamentCustomPayloadEvent; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.network.packet.CustomPayload; +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; + +@Mixin(ClientPlayNetworkHandler.class) +public class CustomPayloadEventDispatcher { + @Inject(method = "onCustomPayload", at = @At("HEAD"), cancellable = true) + private void handleFirmamentParsedPayload(CustomPayload payload, CallbackInfo ci) { + if (payload instanceof FirmamentCustomPayload customPayload) { + FirmamentCustomPayloadEvent.Companion.publish(new FirmamentCustomPayloadEvent(customPayload)); + ci.cancel(); + } + } +} diff --git a/src/main/java/moe/nea/firmament/mixins/WrapCustomPayloadC2SPacketCodec.java b/src/main/java/moe/nea/firmament/mixins/WrapCustomPayloadC2SPacketCodec.java new file mode 100644 index 0000000..150611e --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/WrapCustomPayloadC2SPacketCodec.java @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.mixins; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import moe.nea.firmament.apis.ingame.InGameCodecWrapper; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.packet.CustomPayload; +import net.minecraft.network.packet.c2s.common.CustomPayloadC2SPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.List; + +@Mixin(priority = 1001, value = CustomPayloadC2SPacket.class) +public class WrapCustomPayloadC2SPacketCodec { + + @WrapOperation(method = "<clinit>", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/CustomPayload;createCodec(Lnet/minecraft/network/packet/CustomPayload$CodecFactory;Ljava/util/List;)Lnet/minecraft/network/codec/PacketCodec;")) + private static PacketCodec<PacketByteBuf, CustomPayload> wrapFactory( + CustomPayload.CodecFactory<PacketByteBuf> unknownCodecFactory, + List<CustomPayload.Type<PacketByteBuf, ?>> types, + Operation<PacketCodec<PacketByteBuf, CustomPayload>> original) { + + var originalCodec = original.call(unknownCodecFactory, types); + + return new InGameCodecWrapper(originalCodec, InGameCodecWrapper.Direction.C2S); + } +} diff --git a/src/main/java/moe/nea/firmament/mixins/WrapCustomPayloadS2CPacketCodec.java b/src/main/java/moe/nea/firmament/mixins/WrapCustomPayloadS2CPacketCodec.java new file mode 100644 index 0000000..7cb8f47 --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/WrapCustomPayloadS2CPacketCodec.java @@ -0,0 +1,55 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.mixins; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import moe.nea.firmament.apis.ingame.InGameCodecWrapper; +import moe.nea.firmament.apis.ingame.JoinedCustomPayload; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.listener.ClientCommonPacketListener; +import net.minecraft.network.packet.CustomPayload; +import net.minecraft.network.packet.s2c.common.CustomPayloadS2CPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.List; + +@Mixin(priority = 1001, value = CustomPayloadS2CPacket.class) +public abstract class WrapCustomPayloadS2CPacketCodec { + + @Shadow + public abstract CustomPayload payload(); + + @WrapOperation(method = "<clinit>", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/CustomPayload;createCodec(Lnet/minecraft/network/packet/CustomPayload$CodecFactory;Ljava/util/List;)Lnet/minecraft/network/codec/PacketCodec;")) + private static PacketCodec<PacketByteBuf, CustomPayload> wrapFactory( + CustomPayload.CodecFactory<PacketByteBuf> unknownCodecFactory, + List<CustomPayload.Type<PacketByteBuf, ?>> types, + Operation<PacketCodec<PacketByteBuf, CustomPayload>> original) { + + var originalCodec = original.call(unknownCodecFactory, types); + + return new InGameCodecWrapper(originalCodec, InGameCodecWrapper.Direction.S2C); + } + + + // TODO: move to own class + @Inject(method = "apply(Lnet/minecraft/network/listener/ClientCommonPacketListener;)V", at = @At("HEAD"), cancellable = true) + private void onApply(ClientCommonPacketListener clientCommonPacketListener, CallbackInfo ci) { + if (payload() instanceof JoinedCustomPayload joinedCustomPayload) { + new CustomPayloadS2CPacket(joinedCustomPayload.getOriginal()).apply(clientCommonPacketListener); + new CustomPayloadS2CPacket(joinedCustomPayload.getSmuggled()).apply(clientCommonPacketListener); + ci.cancel(); + } + } + + +} diff --git a/src/main/java/moe/nea/firmament/mixins/devenv/WarnForUnknownCustomPayloadSends.java b/src/main/java/moe/nea/firmament/mixins/devenv/WarnForUnknownCustomPayloadSends.java new file mode 100644 index 0000000..1e99285 --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/devenv/WarnForUnknownCustomPayloadSends.java @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.mixins.devenv; + +import moe.nea.firmament.Firmament; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.packet.UnknownCustomPayload; +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; + +@Mixin(UnknownCustomPayload.class) +public class WarnForUnknownCustomPayloadSends { + @Inject(method = "method_56493", at = @At("HEAD")) + private static void warn(UnknownCustomPayload value, PacketByteBuf buf, CallbackInfo ci) { + Firmament.INSTANCE.getLogger().warn("Unknown custom payload is being sent: {}", value); + } +} diff --git a/src/main/kotlin/moe/nea/firmament/apis/ingame/FirmamentCustomPayload.kt b/src/main/kotlin/moe/nea/firmament/apis/ingame/FirmamentCustomPayload.kt new file mode 100644 index 0000000..34e39ab --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/apis/ingame/FirmamentCustomPayload.kt @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.apis.ingame + +import io.netty.buffer.ByteBuf +import net.minecraft.network.codec.PacketCodec +import net.minecraft.network.packet.CustomPayload +import net.minecraft.util.Identifier + +interface FirmamentCustomPayload : CustomPayload { + + class Unhandled private constructor(val identifier: Identifier) : FirmamentCustomPayload { + override fun getId(): CustomPayload.Id<out CustomPayload> { + return CustomPayload.id(identifier.toString()) + } + + companion object { + fun <B : ByteBuf> createCodec(identifier: Identifier): PacketCodec<B, Unhandled> { + return object : PacketCodec<B, Unhandled> { + override fun decode(buf: B): Unhandled { + return Unhandled(identifier) + } + + override fun encode(buf: B, value: Unhandled) { + // we will never send an unhandled packet stealthy + } + } + } + } + + } +} diff --git a/src/main/kotlin/moe/nea/firmament/apis/ingame/HypixelModAPI.kt b/src/main/kotlin/moe/nea/firmament/apis/ingame/HypixelModAPI.kt new file mode 100644 index 0000000..460df91 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/apis/ingame/HypixelModAPI.kt @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.apis.ingame + +import net.minecraft.network.packet.c2s.common.CustomPayloadC2SPacket +import net.minecraft.text.Text +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.apis.ingame.packets.PartyInfoRequest +import moe.nea.firmament.apis.ingame.packets.PartyInfoResponse +import moe.nea.firmament.commands.thenExecute +import moe.nea.firmament.events.CommandEvent +import moe.nea.firmament.events.FirmamentCustomPayloadEvent +import moe.nea.firmament.events.subscription.SubscriptionOwner +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.features.debug.DeveloperFeatures +import moe.nea.firmament.util.MC + + +object HypixelModAPI : SubscriptionOwner { + init { + InGameCodecWrapper.Direction.C2S.customCodec = + InGameCodecWrapper.createStealthyCodec( + PartyInfoRequest.intoType() + ) + InGameCodecWrapper.Direction.S2C.customCodec = + InGameCodecWrapper.createStealthyCodec( + PartyInfoResponse.intoType() + ) + } + + @JvmStatic + fun sendRequest(packet: FirmamentCustomPayload) { + MC.networkHandler?.sendPacket(CustomPayloadC2SPacket(packet)) + } + + @Subscribe + fun testCommand(event: CommandEvent.SubCommand) { + event.subcommand("sendpartyrequest") { + thenExecute { + sendRequest(PartyInfoRequest(1)) + } + } + } + + @Subscribe + fun logEvents(event: FirmamentCustomPayloadEvent) { + MC.sendChat(Text.stringifiedTranslatable("firmament.modapi.event", event.toString())) + } + + override val delegateFeature: FirmamentFeature + get() = DeveloperFeatures +} diff --git a/src/main/kotlin/moe/nea/firmament/apis/ingame/InGameCodecWrapper.kt b/src/main/kotlin/moe/nea/firmament/apis/ingame/InGameCodecWrapper.kt new file mode 100644 index 0000000..0720dbf --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/apis/ingame/InGameCodecWrapper.kt @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.apis.ingame + +import net.minecraft.network.PacketByteBuf +import net.minecraft.network.codec.PacketCodec +import net.minecraft.network.packet.CustomPayload + +class InGameCodecWrapper( + val wrapped: PacketCodec<PacketByteBuf, CustomPayload>, + val direction: Direction, +) : PacketCodec<PacketByteBuf, CustomPayload> { + enum class Direction { + S2C, + C2S, + ; + + var customCodec: PacketCodec<PacketByteBuf, FirmamentCustomPayload> = createStealthyCodec() + } + + companion object { + fun createStealthyCodec(vararg codecs: CustomPayload.Type<PacketByteBuf, out FirmamentCustomPayload>): PacketCodec<PacketByteBuf, FirmamentCustomPayload> { + return CustomPayload.createCodec( + { FirmamentCustomPayload.Unhandled.createCodec(it) }, + codecs.toList() + ) as PacketCodec<PacketByteBuf, FirmamentCustomPayload> + } + + } + + override fun decode(buf: PacketByteBuf): CustomPayload { + val duplicateBuffer = PacketByteBuf(buf.slice()) + val original = wrapped.decode(buf) + val duplicate = direction.customCodec.decode(duplicateBuffer) + if (duplicate is FirmamentCustomPayload.Unhandled) + return original + return JoinedCustomPayload(original, duplicate) + } + + override fun encode(buf: PacketByteBuf, value: CustomPayload) { + if (value is FirmamentCustomPayload) { + direction.customCodec.encode(buf, value) + } else { + wrapped.encode(buf, value) + } + } +} diff --git a/src/main/kotlin/moe/nea/firmament/apis/ingame/JoinedCustomPayload.kt b/src/main/kotlin/moe/nea/firmament/apis/ingame/JoinedCustomPayload.kt new file mode 100644 index 0000000..c673264 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/apis/ingame/JoinedCustomPayload.kt @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.apis.ingame + +import net.minecraft.network.packet.CustomPayload + +/** + * A class to smuggle two parsed instances of the same custom payload packet. + */ +class JoinedCustomPayload( + val original: CustomPayload, + val smuggled: FirmamentCustomPayload +) : CustomPayload { + companion object { + val joinedId = CustomPayload.id<JoinedCustomPayload>("firmament:joined") + } + + override fun getId(): CustomPayload.Id<out JoinedCustomPayload> { + return joinedId + } +} diff --git a/src/main/kotlin/moe/nea/firmament/apis/ingame/packets/PartyInfoRequest.kt b/src/main/kotlin/moe/nea/firmament/apis/ingame/packets/PartyInfoRequest.kt new file mode 100644 index 0000000..2b3d234 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/apis/ingame/packets/PartyInfoRequest.kt @@ -0,0 +1,134 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.apis.ingame.packets + +import io.netty.buffer.ByteBuf +import java.util.UUID +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.Uuids +import moe.nea.firmament.apis.ingame.FirmamentCustomPayload + +interface FirmamentCustomPayloadMeta<T : FirmamentCustomPayload> { + val ID: CustomPayload.Id<T> + val CODEC: PacketCodec<PacketByteBuf, T> + + fun intoType(): CustomPayload.Type<PacketByteBuf, T> { + return CustomPayload.Type(ID, CODEC) + } +} + +data class PartyInfoRequest(val version: Int) : FirmamentCustomPayload { + companion object : FirmamentCustomPayloadMeta<PartyInfoRequest> { + override val ID = CustomPayload.id<PartyInfoRequest>("hypixel:party_info") + override val CODEC = + PacketCodecs.VAR_INT.cast<PacketByteBuf>() + .xmap(::PartyInfoRequest, PartyInfoRequest::version) + } + + override fun getId(): CustomPayload.Id<out CustomPayload> { + return ID + } +} + +sealed interface PartyInfoResponseV +sealed interface HypixelVersionedPacketData<out T> +data class HypixelSuccessfulResponse<T>(val data: T) : HypixelVersionedPacketData<T> +data class HypixelUnknownVersion(val version: Int) : HypixelVersionedPacketData<Nothing> +data class HypixelApiError(val label: String, val errorId: Int) : HypixelVersionedPacketData<Nothing> { + companion object { + fun <B : ByteBuf> createCodec(label: String): PacketCodec<B, HypixelApiError> { + return PacketCodecs.VAR_INT + .cast<B>() + .xmap({ HypixelApiError(label, it) }, HypixelApiError::errorId) + } + } +} + +object CodecUtils { + fun <B : PacketByteBuf, T> dispatchVersioned( + versions: Map<Int, PacketCodec<B, out T>>, + errorCodec: PacketCodec<B, HypixelApiError> + ): PacketCodec<B, HypixelVersionedPacketData<T>> { + return object : PacketCodec<B, HypixelVersionedPacketData<T>> { + override fun decode(buf: B): HypixelVersionedPacketData<T> { + if (!buf.readBoolean()) { + return errorCodec.decode(buf) + } + val version = buf.readVarInt() + val versionCodec = versions[version] + ?: return HypixelUnknownVersion(version) + return HypixelSuccessfulResponse(versionCodec.decode(buf)) + } + + override fun encode(buf: B, value: HypixelVersionedPacketData<T>?) { + error("Cannot encode a hypixel packet") + } + } + } + + fun <B : PacketByteBuf, T> dispatchS2CBoolean( + ifTrue: PacketCodec<B, out T>, + ifFalse: PacketCodec<B, out T> + ): PacketCodec<B, T> { + return object : PacketCodec<B, T> { + override fun decode(buf: B): T { + return if (buf.readBoolean()) { + ifTrue.decode(buf) + } else { + ifFalse.decode(buf) + } + } + + override fun encode(buf: B, value: T) { + error("Cannot reverse dispatch boolean") + } + } + } + +} + + +data object PartyInfoResponseVUnknown : PartyInfoResponseV +data class PartyInfoResponseV1( + val leader: UUID?, + val members: Set<UUID>, +) : PartyInfoResponseV { + data object PartyMember + companion object { + val CODEC: PacketCodec<PacketByteBuf, PartyInfoResponseV1> = + CodecUtils.dispatchS2CBoolean( + PacketCodec.tuple( + Uuids.PACKET_CODEC, PartyInfoResponseV1::leader, + Uuids.PACKET_CODEC.collect(PacketCodecs.toCollection(::HashSet)), PartyInfoResponseV1::members, + ::PartyInfoResponseV1 + ), + PacketCodec.unit(PartyInfoResponseV1(null, setOf()))) + } +} + + +data class PartyInfoResponse(val data: HypixelVersionedPacketData<PartyInfoResponseV>) : FirmamentCustomPayload { + companion object : FirmamentCustomPayloadMeta<PartyInfoResponse> { + override val ID: CustomPayload.Id<PartyInfoResponse> = CustomPayload.id("hypixel:party_info") + override val CODEC = + CodecUtils + .dispatchVersioned<PacketByteBuf, PartyInfoResponseV>( + mapOf( + 1 to PartyInfoResponseV1.CODEC, + ), + HypixelApiError.createCodec("PartyInfoResponse")) + .xmap(::PartyInfoResponse, PartyInfoResponse::data) + + } + + override fun getId(): CustomPayload.Id<out CustomPayload> { + return ID + } +} diff --git a/src/main/kotlin/moe/nea/firmament/events/FirmamentCustomPayloadEvent.kt b/src/main/kotlin/moe/nea/firmament/events/FirmamentCustomPayloadEvent.kt new file mode 100644 index 0000000..057fcef --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/events/FirmamentCustomPayloadEvent.kt @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.events + +import moe.nea.firmament.apis.ingame.FirmamentCustomPayload + +data class FirmamentCustomPayloadEvent( + val payload: FirmamentCustomPayload +) : FirmamentEvent() { + companion object : FirmamentEventBus<FirmamentCustomPayloadEvent>() +} diff --git a/src/main/resources/assets/firmament/lang/en_us.json b/src/main/resources/assets/firmament/lang/en_us.json index d20fd8e..089a223 100644 --- a/src/main/resources/assets/firmament/lang/en_us.json +++ b/src/main/resources/assets/firmament/lang/en_us.json @@ -153,6 +153,7 @@ "firmament.config.fixes.player-skins": "Fix unsigned Player Skins", "firmament.config.power-user.show-item-id": "Show SkyBlock Ids", "firmament.config.power-user.copy-item-id": "Copy SkyBlock Id", + "firmament.modapi.event": "Received mod API event: %s", "firmament.config.power-user.copy-texture-pack-id": "Copy Texture Pack Id", "firmament.config.power-user.copy-skull-texture": "Copy Placed Skull Id", "firmament.config.power-user.copy-nbt-data": "Copy NBT data", |