From 1ea760bc3f732f7a86affbd780be5143e3f3e139 Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Mon, 19 Dec 2022 15:44:58 -0700 Subject: [PATCH 01/10] implement over-engineered coinflip --- .../clientcommands/ClientCommands.java | 1 + .../clientcommands/c2c/C2CPacket.java | 7 +- .../clientcommands/c2c/CCNetworkHandler.java | 19 ++- .../clientcommands/c2c/CCPacketHandler.java | 11 +- .../clientcommands/c2c/CCPacketListener.java | 6 + .../clientcommands/c2c/StringBuf.java | 45 ------ .../c2c/packets/CoinflipC2CPackets.java | 71 +++++++++ .../c2c/packets/MessageC2CPacket.java | 8 +- .../command/CoinflipCommand.java | 150 ++++++++++++++++++ .../clientcommands/mixin/MixinChatHud.java | 19 ++- .../assets/clientcommands/lang/en_us.json | 7 + 11 files changed, 284 insertions(+), 60 deletions(-) delete mode 100644 src/main/java/net/earthcomputer/clientcommands/c2c/StringBuf.java create mode 100644 src/main/java/net/earthcomputer/clientcommands/c2c/packets/CoinflipC2CPackets.java create mode 100644 src/main/java/net/earthcomputer/clientcommands/command/CoinflipCommand.java diff --git a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java index b47556953..b195d9c25 100644 --- a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java +++ b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java @@ -32,6 +32,7 @@ public void onInitializeClient() { public static void registerCommands(CommandDispatcher dispatcher, CommandRegistryAccess registryAccess) { BookCommand.register(dispatcher); + CoinflipCommand.register(dispatcher); LookCommand.register(dispatcher); NoteCommand.register(dispatcher); ShrugCommand.register(dispatcher); diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacket.java b/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacket.java index 13ac1f2f5..b64c1ed50 100644 --- a/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacket.java +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacket.java @@ -1,7 +1,10 @@ package net.earthcomputer.clientcommands.c2c; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.network.PacketByteBuf; + public interface C2CPacket { - void write(StringBuf buf); + void write(PacketByteBuf buf); - void apply(CCPacketListener listener); + void apply(CCPacketListener listener) throws CommandSyntaxException; } diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/CCNetworkHandler.java b/src/main/java/net/earthcomputer/clientcommands/c2c/CCNetworkHandler.java index 14fe4a280..0bdb2f4d5 100644 --- a/src/main/java/net/earthcomputer/clientcommands/c2c/CCNetworkHandler.java +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/CCNetworkHandler.java @@ -3,9 +3,13 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.logging.LogUtils; +import io.netty.buffer.Unpooled; +import net.earthcomputer.clientcommands.c2c.packets.CoinflipC2CPackets; import net.earthcomputer.clientcommands.c2c.packets.MessageC2CPacket; +import net.earthcomputer.clientcommands.command.CoinflipCommand; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.PlayerListEntry; +import net.minecraft.network.PacketByteBuf; import net.minecraft.network.encryption.PlayerPublicKey; import net.minecraft.network.encryption.PublicPlayerSession; import net.minecraft.text.MutableText; @@ -13,6 +17,7 @@ import net.minecraft.util.Formatting; import org.slf4j.Logger; +import java.nio.ByteBuffer; import java.security.PublicKey; public class CCNetworkHandler implements CCPacketListener { @@ -47,10 +52,10 @@ public void sendPacket(C2CPacket packet, PlayerListEntry recipient) throws Comma throw PUBLIC_KEY_NOT_FOUND_EXCEPTION.create(); } PublicKey key = ppk.data().key(); - StringBuf buf = new StringBuf(); + PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer()); buf.writeInt(id); packet.write(buf); - byte[] compressed = ConversionHelper.Gzip.compress(buf.bytes()); + byte[] compressed = ConversionHelper.Gzip.compress(buf.getWrittenBytes()); if (compressed.length > 245) { throw MESSAGE_TOO_LONG_EXCEPTION.create(); } @@ -79,4 +84,14 @@ public void onMessageC2CPacket(MessageC2CPacket packet) { Text text = prefix.append(Text.translatable("ccpacket.messageC2CPacket.incoming", sender, message).formatted(Formatting.GRAY)); MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(text); } + + @Override + public void onCoinflipInitC2CPacket(CoinflipC2CPackets.CoinflipInitC2CPacket packet) throws CommandSyntaxException { + CoinflipCommand.acceptCoinflip(packet); + } + + @Override + public void onCoinflipResultC2CPacket(CoinflipC2CPackets.CoinflipResultC2CPacket packet) { + CoinflipCommand.completeCoinflip(packet); + } } diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/CCPacketHandler.java b/src/main/java/net/earthcomputer/clientcommands/c2c/CCPacketHandler.java index f3903b6cf..63e7c4342 100644 --- a/src/main/java/net/earthcomputer/clientcommands/c2c/CCPacketHandler.java +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/CCPacketHandler.java @@ -2,7 +2,9 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import net.earthcomputer.clientcommands.c2c.packets.CoinflipC2CPackets; import net.earthcomputer.clientcommands.c2c.packets.MessageC2CPacket; +import net.minecraft.network.PacketByteBuf; import net.minecraft.util.Util; import org.jetbrains.annotations.Nullable; @@ -13,13 +15,14 @@ public class CCPacketHandler { private static final Object2IntMap> packetIds = Util.make(new Object2IntOpenHashMap<>(), map -> map.defaultReturnValue(-1)); - private static final List> packetFactories = new ArrayList<>(); + private static final List> packetFactories = new ArrayList<>(); static { CCPacketHandler.register(MessageC2CPacket.class, MessageC2CPacket::new); + CoinflipC2CPackets.register(); } - public static

void register(Class

packet, Function packetFactory) { + public static

void register(Class

packet, Function packetFactory) { int id = packetFactories.size(); int i = packetIds.put(packet, id); if (i != -1) { @@ -36,8 +39,8 @@ public static

Integer getId(Class

packet) { } @Nullable - public static C2CPacket createPacket(int id, StringBuf buf) { - Function function = packetFactories.get(id); + public static C2CPacket createPacket(int id, PacketByteBuf buf) { + Function function = packetFactories.get(id); return function == null ? null : function.apply(buf); } } diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/CCPacketListener.java b/src/main/java/net/earthcomputer/clientcommands/c2c/CCPacketListener.java index 734cb6e6c..1850cb485 100644 --- a/src/main/java/net/earthcomputer/clientcommands/c2c/CCPacketListener.java +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/CCPacketListener.java @@ -1,7 +1,13 @@ package net.earthcomputer.clientcommands.c2c; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.earthcomputer.clientcommands.c2c.packets.CoinflipC2CPackets; import net.earthcomputer.clientcommands.c2c.packets.MessageC2CPacket; public interface CCPacketListener { void onMessageC2CPacket(MessageC2CPacket packet); + + void onCoinflipInitC2CPacket(CoinflipC2CPackets.CoinflipInitC2CPacket packet) throws CommandSyntaxException; + + void onCoinflipResultC2CPacket(CoinflipC2CPackets.CoinflipResultC2CPacket packet); } diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/StringBuf.java b/src/main/java/net/earthcomputer/clientcommands/c2c/StringBuf.java deleted file mode 100644 index 23909f489..000000000 --- a/src/main/java/net/earthcomputer/clientcommands/c2c/StringBuf.java +++ /dev/null @@ -1,45 +0,0 @@ -package net.earthcomputer.clientcommands.c2c; - -import java.nio.charset.StandardCharsets; - -public class StringBuf { - - private final StringBuilder buffer; - private int cursor = 0; - - public StringBuf(String string) { - this.buffer = new StringBuilder(string); - } - - public StringBuf() { - this(""); - } - - public byte[] bytes() { - return this.buffer.toString().getBytes(StandardCharsets.UTF_8); - } - - public int getRemainingLength() { - return this.buffer.length() - this.cursor; - } - - public String readString() { - int start = this.cursor; - while (this.buffer.charAt(this.cursor) != '\0') { - this.cursor++; - } - return this.buffer.substring(start, this.cursor++); - } - - public int readInt() { - return Integer.parseInt(this.readString()); - } - - public void writeString(String string) { - this.buffer.append(string).append('\0'); - } - - public void writeInt(int integer) { - this.buffer.append(integer).append('\0'); - } -} diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/packets/CoinflipC2CPackets.java b/src/main/java/net/earthcomputer/clientcommands/c2c/packets/CoinflipC2CPackets.java new file mode 100644 index 000000000..a3bed3589 --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/packets/CoinflipC2CPackets.java @@ -0,0 +1,71 @@ +package net.earthcomputer.clientcommands.c2c.packets; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.earthcomputer.clientcommands.c2c.C2CPacket; +import net.earthcomputer.clientcommands.c2c.CCPacketHandler; +import net.earthcomputer.clientcommands.c2c.CCPacketListener; +import net.minecraft.network.PacketByteBuf; + +import java.math.BigInteger; +import java.util.BitSet; + +public class CoinflipC2CPackets { + // use a diffie hellman key exchange in order to ensure that the coinflip is fair + + public static class CoinflipInitC2CPacket implements C2CPacket { + public final String sender; + public final BigInteger AB; + + public CoinflipInitC2CPacket(String sender, BigInteger publicKey) { + this.sender = sender; + this.AB = publicKey; + } + + public CoinflipInitC2CPacket(PacketByteBuf stringBuf) { + this.sender = stringBuf.readString(); + this.AB = new BigInteger(stringBuf.readBitSet().toByteArray()); + } + + @Override + public void write(PacketByteBuf buf) { + buf.writeString(this.sender); + buf.writeBitSet(BitSet.valueOf(this.AB.toByteArray())); + } + + @Override + public void apply(CCPacketListener listener) throws CommandSyntaxException { + listener.onCoinflipInitC2CPacket(this); + } + } + + public static class CoinflipResultC2CPacket implements C2CPacket { + public final String sender; + public final BigInteger s; + + public CoinflipResultC2CPacket(String sender, BigInteger s) { + this.sender = sender; + this.s = s; + } + + public CoinflipResultC2CPacket(PacketByteBuf stringBuf) { + this.sender = stringBuf.readString(); + this.s = new BigInteger(stringBuf.readBitSet().toByteArray()); + } + + @Override + public void write(PacketByteBuf buf) { + buf.writeString(this.sender); + buf.writeBitSet(BitSet.valueOf(this.s.toByteArray())); + } + + @Override + public void apply(CCPacketListener listener) { + listener.onCoinflipResultC2CPacket(this); + } + } + + public static void register() { + CCPacketHandler.register(CoinflipInitC2CPacket.class, CoinflipInitC2CPacket::new); + CCPacketHandler.register(CoinflipResultC2CPacket.class, CoinflipResultC2CPacket::new); + } +} diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/packets/MessageC2CPacket.java b/src/main/java/net/earthcomputer/clientcommands/c2c/packets/MessageC2CPacket.java index f61f3b62b..99db8c976 100644 --- a/src/main/java/net/earthcomputer/clientcommands/c2c/packets/MessageC2CPacket.java +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/packets/MessageC2CPacket.java @@ -2,7 +2,9 @@ import net.earthcomputer.clientcommands.c2c.C2CPacket; import net.earthcomputer.clientcommands.c2c.CCPacketListener; -import net.earthcomputer.clientcommands.c2c.StringBuf; +import net.minecraft.network.PacketByteBuf; + +import java.nio.ByteBuffer; public class MessageC2CPacket implements C2CPacket { @@ -14,13 +16,13 @@ public MessageC2CPacket(String sender, String message) { this.message = message; } - public MessageC2CPacket(StringBuf raw) { + public MessageC2CPacket(PacketByteBuf raw) { this.sender = raw.readString(); this.message = raw.readString(); } @Override - public void write(StringBuf buf) { + public void write(PacketByteBuf buf) { buf.writeString(this.sender); buf.writeString(this.message); } diff --git a/src/main/java/net/earthcomputer/clientcommands/command/CoinflipCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/CoinflipCommand.java new file mode 100644 index 000000000..0aaaa9b75 --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/command/CoinflipCommand.java @@ -0,0 +1,150 @@ +package net.earthcomputer.clientcommands.command; + +import com.mojang.authlib.GameProfile; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import com.mojang.logging.LogUtils; +import net.earthcomputer.clientcommands.c2c.CCNetworkHandler; +import net.earthcomputer.clientcommands.c2c.packets.CoinflipC2CPackets; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.PlayerListEntry; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.SecureRandom; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import static dev.xpple.clientarguments.arguments.CGameProfileArgumentType.gameProfile; +import static dev.xpple.clientarguments.arguments.CGameProfileArgumentType.getCProfileArgument; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; + +public class CoinflipCommand { + private static final Logger LOGGER = LogUtils.getLogger(); + private static final SimpleCommandExceptionType PLAYER_NOT_FOUND_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("commands.cwe.playerNotFound")); + + private static final BigInteger p = new BigInteger(1, + intsToBytes(new int[] { + 0xFFFFFFFF, 0xFFFFFFFF, 0xC90FDAA2, 0x2168C234, 0xC4C6628B, 0x80DC1CD1, + 0x29024E08, 0x8A67CC74, 0x020BBEA6, 0x3B139B22, 0x514A0879, 0x8E3404DD, + 0xEF9519B3, 0xCD3A431B, 0x302B0A6D, 0xF25F1437, 0x4FE1356D, 0x6D51C245, + 0xE485B576, 0x625E7EC6, 0xF44C42E9, 0xA63A3620, 0xFFFFFFFF, 0xFFFFFFFF + }) + ); + + private static final BigInteger g = BigInteger.TWO; + + //TODO: make these clear old failed ones to not leak memory + private static final Map initializedCoinflips = new HashMap<>(); + private static final Map result = new HashMap<>(); + + private static final SecureRandom random = new SecureRandom(); + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(literal("ccoinflip") + .executes(ctx -> localCoinflip(ctx.getSource())) + .then(argument("player", gameProfile()) + .executes(ctx -> coinflip(ctx.getSource(), getCProfileArgument(ctx, "player"))))); + } + + private static int localCoinflip(FabricClientCommandSource source) { + source.sendFeedback(random.nextBoolean() ? Text.translatable("commands.coinflip.heads") : Text.translatable("commands.coinflip.tails")); + return Command.SINGLE_SUCCESS; + } + + private static int coinflip(FabricClientCommandSource source, Collection profiles) throws CommandSyntaxException { + assert source.getClient().getNetworkHandler() != null; + if (profiles.size() != 1) { + throw PLAYER_NOT_FOUND_EXCEPTION.create(); + } + PlayerListEntry recipient = source.getClient().getNetworkHandler().getPlayerList().stream() + .filter(p -> p.getProfile().getName().equalsIgnoreCase(profiles.iterator().next().getName())) + .findFirst() + .orElseThrow(PLAYER_NOT_FOUND_EXCEPTION::create); + + BigInteger a = new BigInteger(729, random).abs(); + + BigInteger A = g.modPow(a, p); + + CoinflipC2CPackets.CoinflipInitC2CPacket packet = new CoinflipC2CPackets.CoinflipInitC2CPacket(source.getClient().getNetworkHandler().getProfile().getName(), A); + CCNetworkHandler.getInstance().sendPacket(packet, recipient); + initializedCoinflips.put(recipient.getProfile().getName(), a); + MutableText message = Text.translatable("commands.coinflip.sent", recipient.getProfile().getName()); + source.sendFeedback(message); + return Command.SINGLE_SUCCESS; + } + + public static void acceptCoinflip(CoinflipC2CPackets.CoinflipInitC2CPacket packet) throws CommandSyntaxException { + if (!initializedCoinflips.containsKey(packet.sender)) { + BigInteger b = new BigInteger(729, random).abs(); + BigInteger B = g.modPow(b, p); + + BigInteger s = packet.AB.modPow(b, p); + result.put(packet.sender, s); + + CoinflipC2CPackets.CoinflipInitC2CPacket response = new CoinflipC2CPackets.CoinflipInitC2CPacket(MinecraftClient.getInstance().getNetworkHandler().getProfile().getName(), B); + + // get sender from name + PlayerListEntry sender = MinecraftClient.getInstance().getNetworkHandler().getPlayerList().stream() + .filter(p -> p.getProfile().getName().equalsIgnoreCase(packet.sender)) + .findFirst() + .orElseThrow(PLAYER_NOT_FOUND_EXCEPTION::create); + + CCNetworkHandler.getInstance().sendPacket(response, sender); + MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.translatable("commands.coinflip.received", packet.sender)); + + CoinflipC2CPackets.CoinflipResultC2CPacket resultPacket = new CoinflipC2CPackets.CoinflipResultC2CPacket(MinecraftClient.getInstance().getNetworkHandler().getProfile().getName(), s); + CCNetworkHandler.getInstance().sendPacket(resultPacket, sender); + } else { + BigInteger a = initializedCoinflips.get(packet.sender); + BigInteger s = packet.AB.modPow(a, p); + initializedCoinflips.remove(packet.sender); + result.put(packet.sender, s); + + // get sender from name + PlayerListEntry sender = MinecraftClient.getInstance().getNetworkHandler().getPlayerList().stream() + .filter(p -> p.getProfile().getName().equalsIgnoreCase(packet.sender)) + .findFirst() + .orElseThrow(PLAYER_NOT_FOUND_EXCEPTION::create); + + CoinflipC2CPackets.CoinflipResultC2CPacket response = new CoinflipC2CPackets.CoinflipResultC2CPacket(MinecraftClient.getInstance().getNetworkHandler().getProfile().getName(), s); + CCNetworkHandler.getInstance().sendPacket(response, sender); + } + } + + public static void completeCoinflip(CoinflipC2CPackets.CoinflipResultC2CPacket packet) { + if (result.containsKey(packet.sender)) { + if (result.get(packet.sender).equals(packet.s)) { + LOGGER.info("Coinflip val: " + packet.s.toString(16)); + MutableText message = Text.translatable("commands.coinflip.value", packet.sender, Text.translatable(packet.s.mod(BigInteger.TWO).equals(BigInteger.ONE) ? "commands.coinflip.heads" : "commands.coinflip.tails")); + MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(message); + } else { + LOGGER.info("Coinflip val: " + result.get(packet.sender).toString(16)); + LOGGER.info("Remote val: " + packet.s.toString(16)); + MutableText message = Text.translatable("commands.coinflip.cheater", packet.sender).formatted(Formatting.RED); + MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(message); + } + result.remove(packet.sender); + } else { + throw new IllegalStateException("Coinflip result packet received before init packet"); + } + } + + private static byte[] intsToBytes(int[] ints) { + ByteBuffer buffer = ByteBuffer.allocate(ints.length * Integer.BYTES); + for (int i : ints) { + buffer.putInt(i); + } + return buffer.array(); + } +} diff --git a/src/main/java/net/earthcomputer/clientcommands/mixin/MixinChatHud.java b/src/main/java/net/earthcomputer/clientcommands/mixin/MixinChatHud.java index 0267cc188..c65fffc07 100644 --- a/src/main/java/net/earthcomputer/clientcommands/mixin/MixinChatHud.java +++ b/src/main/java/net/earthcomputer/clientcommands/mixin/MixinChatHud.java @@ -1,11 +1,14 @@ package net.earthcomputer.clientcommands.mixin; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import io.netty.buffer.Unpooled; import net.earthcomputer.clientcommands.TempRules; import net.earthcomputer.clientcommands.c2c.*; import net.earthcomputer.clientcommands.interfaces.IHasPrivateKey; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.hud.ChatHud; import net.minecraft.client.gui.hud.MessageIndicator; +import net.minecraft.network.PacketByteBuf; import net.minecraft.network.message.MessageSignatureData; import net.minecraft.text.HoverEvent; import net.minecraft.text.Text; @@ -17,7 +20,6 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import java.nio.charset.StandardCharsets; import java.security.PrivateKey; import java.util.Arrays; import java.util.Optional; @@ -74,16 +76,25 @@ private static boolean handleC2CPacket(String content) { return false; } byte[] uncompressed = ConversionHelper.Gzip.uncompress(decrypted); - StringBuf buf = new StringBuf(new String(uncompressed, StandardCharsets.UTF_8)); + PacketByteBuf buf = new PacketByteBuf(Unpooled.wrappedBuffer(uncompressed)); int id = buf.readInt(); C2CPacket c2CPacket = CCPacketHandler.createPacket(id, buf); if (c2CPacket == null) { return false; } - if (buf.getRemainingLength() > 0) { + if (buf.readableBytes() > 0) { return false; } - c2CPacket.apply(CCNetworkHandler.getInstance()); + try { + c2CPacket.apply(CCNetworkHandler.getInstance()); + } catch (CommandSyntaxException e) { + if (e.getRawMessage() instanceof Text) { + MinecraftClient.getInstance().inGameHud.getChatHud().addMessage((Text) e.getRawMessage()); + } + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } return true; } } diff --git a/src/main/resources/assets/clientcommands/lang/en_us.json b/src/main/resources/assets/clientcommands/lang/en_us.json index 2526ec8d0..22967c8b8 100644 --- a/src/main/resources/assets/clientcommands/lang/en_us.json +++ b/src/main/resources/assets/clientcommands/lang/en_us.json @@ -108,6 +108,13 @@ "commands.chotbar.notCreative": "Player must be in creative mode to restore item hotbars", "commands.chotbar.restoredHotbar": "Restored item hotbar %d", + "commands.coinflip.sent": "Sent coin flip request to %s", + "commands.coinflip.received": "Received coin flip request from %s", + "commands.coinflip.value": "Coin flip result from %s: %s", + "commands.coinflip.heads": "Heads", + "commands.coinflip.tails": "Tails", + "commands.coinflip.cheater": "I think %s is cheating, they got a different value.", + "commands.citemgroup.notFound": "Item group \"%s\" not found", "commands.citemgroup.outOfBounds": "Index %d is out of bounds", "commands.citemgroup.saveFile.failed": "Could not save item groups file", From d818e3cc49363ef12926e6443ac5534c63319a5c Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Tue, 20 Dec 2022 10:40:39 -0700 Subject: [PATCH 02/10] exchange hashes first so player2 cant easily cheat via guess/check --- .../clientcommands/c2c/CCNetworkHandler.java | 6 +- .../clientcommands/c2c/CCPacketListener.java | 2 + .../c2c/packets/CoinflipC2CPackets.java | 33 ++++- .../command/CoinflipCommand.java | 130 ++++++++++++------ 4 files changed, 128 insertions(+), 43 deletions(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/CCNetworkHandler.java b/src/main/java/net/earthcomputer/clientcommands/c2c/CCNetworkHandler.java index 0bdb2f4d5..cf23b2910 100644 --- a/src/main/java/net/earthcomputer/clientcommands/c2c/CCNetworkHandler.java +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/CCNetworkHandler.java @@ -17,7 +17,6 @@ import net.minecraft.util.Formatting; import org.slf4j.Logger; -import java.nio.ByteBuffer; import java.security.PublicKey; public class CCNetworkHandler implements CCPacketListener { @@ -87,6 +86,11 @@ public void onMessageC2CPacket(MessageC2CPacket packet) { @Override public void onCoinflipInitC2CPacket(CoinflipC2CPackets.CoinflipInitC2CPacket packet) throws CommandSyntaxException { + CoinflipCommand.initCoinflip(packet); + } + + @Override + public void onCoinflipAcceptedC2CPacket(CoinflipC2CPackets.CoinflipAcceptedC2CPacket packet) throws CommandSyntaxException { CoinflipCommand.acceptCoinflip(packet); } diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/CCPacketListener.java b/src/main/java/net/earthcomputer/clientcommands/c2c/CCPacketListener.java index 1850cb485..047101864 100644 --- a/src/main/java/net/earthcomputer/clientcommands/c2c/CCPacketListener.java +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/CCPacketListener.java @@ -9,5 +9,7 @@ public interface CCPacketListener { void onCoinflipInitC2CPacket(CoinflipC2CPackets.CoinflipInitC2CPacket packet) throws CommandSyntaxException; + void onCoinflipAcceptedC2CPacket(CoinflipC2CPackets.CoinflipAcceptedC2CPacket packet) throws CommandSyntaxException; + void onCoinflipResultC2CPacket(CoinflipC2CPackets.CoinflipResultC2CPacket packet); } diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/packets/CoinflipC2CPackets.java b/src/main/java/net/earthcomputer/clientcommands/c2c/packets/CoinflipC2CPackets.java index a3bed3589..04880e7cd 100644 --- a/src/main/java/net/earthcomputer/clientcommands/c2c/packets/CoinflipC2CPackets.java +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/packets/CoinflipC2CPackets.java @@ -13,15 +13,41 @@ public class CoinflipC2CPackets { // use a diffie hellman key exchange in order to ensure that the coinflip is fair public static class CoinflipInitC2CPacket implements C2CPacket { + public final String sender; + public final byte[] ABHash; + + public CoinflipInitC2CPacket(String sender, byte[] ABHash) { + this.sender = sender; + this.ABHash = ABHash; + } + + public CoinflipInitC2CPacket(PacketByteBuf raw) { + this.sender = raw.readString(); + this.ABHash = raw.readByteArray(); + } + + @Override + public void write(PacketByteBuf buf) { + buf.writeString(this.sender); + buf.writeByteArray(this.ABHash); + } + + @Override + public void apply(CCPacketListener listener) throws CommandSyntaxException { + listener.onCoinflipInitC2CPacket(this); + } + } + + public static class CoinflipAcceptedC2CPacket implements C2CPacket { public final String sender; public final BigInteger AB; - public CoinflipInitC2CPacket(String sender, BigInteger publicKey) { + public CoinflipAcceptedC2CPacket(String sender, BigInteger publicKey) { this.sender = sender; this.AB = publicKey; } - public CoinflipInitC2CPacket(PacketByteBuf stringBuf) { + public CoinflipAcceptedC2CPacket(PacketByteBuf stringBuf) { this.sender = stringBuf.readString(); this.AB = new BigInteger(stringBuf.readBitSet().toByteArray()); } @@ -34,7 +60,7 @@ public void write(PacketByteBuf buf) { @Override public void apply(CCPacketListener listener) throws CommandSyntaxException { - listener.onCoinflipInitC2CPacket(this); + listener.onCoinflipAcceptedC2CPacket(this); } } @@ -66,6 +92,7 @@ public void apply(CCPacketListener listener) { public static void register() { CCPacketHandler.register(CoinflipInitC2CPacket.class, CoinflipInitC2CPacket::new); + CCPacketHandler.register(CoinflipAcceptedC2CPacket.class, CoinflipAcceptedC2CPacket::new); CCPacketHandler.register(CoinflipResultC2CPacket.class, CoinflipResultC2CPacket::new); } } diff --git a/src/main/java/net/earthcomputer/clientcommands/command/CoinflipCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/CoinflipCommand.java index 0aaaa9b75..fb9b0085c 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/CoinflipCommand.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/CoinflipCommand.java @@ -15,14 +15,13 @@ import net.minecraft.text.Text; import net.minecraft.util.Formatting; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.math.BigInteger; import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import static dev.xpple.clientarguments.arguments.CGameProfileArgumentType.gameProfile; import static dev.xpple.clientarguments.arguments.CGameProfileArgumentType.getCProfileArgument; @@ -44,8 +43,15 @@ public class CoinflipCommand { private static final BigInteger g = BigInteger.TWO; - //TODO: make these clear old failed ones to not leak memory - private static final Map initializedCoinflips = new HashMap<>(); + // TODO: make these clear old failed ones to not leak memory + + // hash's of opponent's ABs + private static final Map opponentHash = new HashMap<>(); + + // oppoent name -> your AB + private static final Map playerAB = new HashMap<>(); + + // result to compare with opponent's private static final Map result = new HashMap<>(); private static final SecureRandom random = new SecureRandom(); @@ -62,6 +68,17 @@ private static int localCoinflip(FabricClientCommandSource source) { return Command.SINGLE_SUCCESS; } + public static byte[] toSHA1(byte[] convertme) { + MessageDigest md = null; + try { + md = MessageDigest.getInstance("SHA-1"); + } + catch(NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + return md.digest(convertme); + } + private static int coinflip(FabricClientCommandSource source, Collection profiles) throws CommandSyntaxException { assert source.getClient().getNetworkHandler() != null; if (profiles.size() != 1) { @@ -72,54 +89,89 @@ private static int coinflip(FabricClientCommandSource source, Collection p.getProfile().getName().equalsIgnoreCase(packet.sender)) + .findFirst() + .orElseThrow(PLAYER_NOT_FOUND_EXCEPTION::create); - // get sender from name - PlayerListEntry sender = MinecraftClient.getInstance().getNetworkHandler().getPlayerList().stream() - .filter(p -> p.getProfile().getName().equalsIgnoreCase(packet.sender)) - .findFirst() - .orElseThrow(PLAYER_NOT_FOUND_EXCEPTION::create); + opponentHash.put(packet.sender, packet.ABHash); + if (!playerAB.containsKey(packet.sender)) { + BigInteger b = new BigInteger(729, random).abs(); + BigInteger B = g.modPow(b, p); + playerAB.put(packet.sender, b); + + CoinflipC2CPackets.CoinflipInitC2CPacket response = new CoinflipC2CPackets.CoinflipInitC2CPacket(MinecraftClient.getInstance().getNetworkHandler().getProfile().getName(), toSHA1(B.toByteArray())); CCNetworkHandler.getInstance().sendPacket(response, sender); MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.translatable("commands.coinflip.received", packet.sender)); + } - CoinflipC2CPackets.CoinflipResultC2CPacket resultPacket = new CoinflipC2CPackets.CoinflipResultC2CPacket(MinecraftClient.getInstance().getNetworkHandler().getProfile().getName(), s); - CCNetworkHandler.getInstance().sendPacket(resultPacket, sender); - } else { - BigInteger a = initializedCoinflips.get(packet.sender); - BigInteger s = packet.AB.modPow(a, p); - initializedCoinflips.remove(packet.sender); - result.put(packet.sender, s); - - // get sender from name - PlayerListEntry sender = MinecraftClient.getInstance().getNetworkHandler().getPlayerList().stream() - .filter(p -> p.getProfile().getName().equalsIgnoreCase(packet.sender)) - .findFirst() - .orElseThrow(PLAYER_NOT_FOUND_EXCEPTION::create); - - CoinflipC2CPackets.CoinflipResultC2CPacket response = new CoinflipC2CPackets.CoinflipResultC2CPacket(MinecraftClient.getInstance().getNetworkHandler().getProfile().getName(), s); - CCNetworkHandler.getInstance().sendPacket(response, sender); + BigInteger b = playerAB.get(packet.sender); + BigInteger B = g.modPow(b, p); + + CoinflipC2CPackets.CoinflipAcceptedC2CPacket response = new CoinflipC2CPackets.CoinflipAcceptedC2CPacket(MinecraftClient.getInstance().getNetworkHandler().getProfile().getName(), B); + CCNetworkHandler.getInstance().sendPacket(response, sender); + } + + public static void acceptCoinflip(CoinflipC2CPackets.CoinflipAcceptedC2CPacket packet) throws CommandSyntaxException { + if (!opponentHash.containsKey(packet.sender)) { + throw PLAYER_NOT_FOUND_EXCEPTION.create(); + } + + // get sender from name + PlayerListEntry sender = MinecraftClient.getInstance().getNetworkHandler().getPlayerList().stream() + .filter(p -> p.getProfile().getName().equalsIgnoreCase(packet.sender)) + .findFirst() + .orElseThrow(PLAYER_NOT_FOUND_EXCEPTION::create); + + BigInteger a = playerAB.get(packet.sender); + BigInteger B = packet.AB; + + // check if hash matches + if (!Arrays.equals(opponentHash.get(packet.sender), toSHA1(B.toByteArray()))) { + System.out.println("expected: " + byteArrayToHexString(opponentHash.get(packet.sender))); + System.out.println("actual: " + byteArrayToHexString(toSHA1(B.toByteArray()))); + MutableText message = Text.translatable("commands.coinflip.cheater", packet.sender).formatted(Formatting.RED); + MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(message); + opponentHash.remove(packet.sender); + playerAB.remove(packet.sender); + return; } + + BigInteger s = B.modPow(a, p); + result.put(packet.sender, s); + + CoinflipC2CPackets.CoinflipResultC2CPacket response = new CoinflipC2CPackets.CoinflipResultC2CPacket(MinecraftClient.getInstance().getNetworkHandler().getProfile().getName(), s); + CCNetworkHandler.getInstance().sendPacket(response, sender); } public static void completeCoinflip(CoinflipC2CPackets.CoinflipResultC2CPacket packet) { @@ -129,8 +181,8 @@ public static void completeCoinflip(CoinflipC2CPackets.CoinflipResultC2CPacket p MutableText message = Text.translatable("commands.coinflip.value", packet.sender, Text.translatable(packet.s.mod(BigInteger.TWO).equals(BigInteger.ONE) ? "commands.coinflip.heads" : "commands.coinflip.tails")); MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(message); } else { - LOGGER.info("Coinflip val: " + result.get(packet.sender).toString(16)); - LOGGER.info("Remote val: " + packet.s.toString(16)); + LOGGER.info("expected: " + result.get(packet.sender).toString(16)); + LOGGER.info("actual: " + packet.s.toString(16)); MutableText message = Text.translatable("commands.coinflip.cheater", packet.sender).formatted(Formatting.RED); MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(message); } From 186fba9fa1574f463ce6868c3ef3359d4423f84c Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Tue, 20 Dec 2022 10:55:25 -0700 Subject: [PATCH 03/10] fix self coinflip and multi coinflips with new method --- .../clientcommands/command/CoinflipCommand.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/command/CoinflipCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/CoinflipCommand.java index fb9b0085c..5f41af06e 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/CoinflipCommand.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/CoinflipCommand.java @@ -89,18 +89,16 @@ private static int coinflip(FabricClientCommandSource source, Collection Date: Tue, 20 Dec 2022 11:52:09 -0700 Subject: [PATCH 04/10] generalize to dice rolling --- .../clientcommands/ClientCommands.java | 2 +- .../clientcommands/c2c/CCNetworkHandler.java | 16 +-- .../clientcommands/c2c/CCPacketHandler.java | 4 +- .../clientcommands/c2c/CCPacketListener.java | 8 +- ...2CPackets.java => DiceRollC2CPackets.java} | 30 +++--- ...nflipCommand.java => DiceRollCommand.java} | 98 +++++++++++++------ .../assets/clientcommands/lang/en_us.json | 14 +-- 7 files changed, 107 insertions(+), 65 deletions(-) rename src/main/java/net/earthcomputer/clientcommands/c2c/packets/{CoinflipC2CPackets.java => DiceRollC2CPackets.java} (69%) rename src/main/java/net/earthcomputer/clientcommands/command/{CoinflipCommand.java => DiceRollCommand.java} (59%) diff --git a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java index b195d9c25..5b927eb6b 100644 --- a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java +++ b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java @@ -32,7 +32,7 @@ public void onInitializeClient() { public static void registerCommands(CommandDispatcher dispatcher, CommandRegistryAccess registryAccess) { BookCommand.register(dispatcher); - CoinflipCommand.register(dispatcher); + DiceRollCommand.register(dispatcher); LookCommand.register(dispatcher); NoteCommand.register(dispatcher); ShrugCommand.register(dispatcher); diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/CCNetworkHandler.java b/src/main/java/net/earthcomputer/clientcommands/c2c/CCNetworkHandler.java index cf23b2910..c793a7c3b 100644 --- a/src/main/java/net/earthcomputer/clientcommands/c2c/CCNetworkHandler.java +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/CCNetworkHandler.java @@ -4,9 +4,9 @@ import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.logging.LogUtils; import io.netty.buffer.Unpooled; -import net.earthcomputer.clientcommands.c2c.packets.CoinflipC2CPackets; +import net.earthcomputer.clientcommands.c2c.packets.DiceRollC2CPackets; import net.earthcomputer.clientcommands.c2c.packets.MessageC2CPacket; -import net.earthcomputer.clientcommands.command.CoinflipCommand; +import net.earthcomputer.clientcommands.command.DiceRollCommand; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.PlayerListEntry; import net.minecraft.network.PacketByteBuf; @@ -85,17 +85,17 @@ public void onMessageC2CPacket(MessageC2CPacket packet) { } @Override - public void onCoinflipInitC2CPacket(CoinflipC2CPackets.CoinflipInitC2CPacket packet) throws CommandSyntaxException { - CoinflipCommand.initCoinflip(packet); + public void onCoinflipInitC2CPacket(DiceRollC2CPackets.DiceRollInitC2CPacket packet) throws CommandSyntaxException { + DiceRollCommand.initCoinflip(packet); } @Override - public void onCoinflipAcceptedC2CPacket(CoinflipC2CPackets.CoinflipAcceptedC2CPacket packet) throws CommandSyntaxException { - CoinflipCommand.acceptCoinflip(packet); + public void onCoinflipAcceptedC2CPacket(DiceRollC2CPackets.DiceRollAcceptedC2CPacket packet) throws CommandSyntaxException { + DiceRollCommand.acceptCoinflip(packet); } @Override - public void onCoinflipResultC2CPacket(CoinflipC2CPackets.CoinflipResultC2CPacket packet) { - CoinflipCommand.completeCoinflip(packet); + public void onCoinflipResultC2CPacket(DiceRollC2CPackets.DiceRollResultC2CPacket packet) { + DiceRollCommand.completeCoinflip(packet); } } diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/CCPacketHandler.java b/src/main/java/net/earthcomputer/clientcommands/c2c/CCPacketHandler.java index 63e7c4342..2e497d99d 100644 --- a/src/main/java/net/earthcomputer/clientcommands/c2c/CCPacketHandler.java +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/CCPacketHandler.java @@ -2,7 +2,7 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import net.earthcomputer.clientcommands.c2c.packets.CoinflipC2CPackets; +import net.earthcomputer.clientcommands.c2c.packets.DiceRollC2CPackets; import net.earthcomputer.clientcommands.c2c.packets.MessageC2CPacket; import net.minecraft.network.PacketByteBuf; import net.minecraft.util.Util; @@ -19,7 +19,7 @@ public class CCPacketHandler { static { CCPacketHandler.register(MessageC2CPacket.class, MessageC2CPacket::new); - CoinflipC2CPackets.register(); + DiceRollC2CPackets.register(); } public static

void register(Class

packet, Function packetFactory) { diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/CCPacketListener.java b/src/main/java/net/earthcomputer/clientcommands/c2c/CCPacketListener.java index 047101864..a235f7dc0 100644 --- a/src/main/java/net/earthcomputer/clientcommands/c2c/CCPacketListener.java +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/CCPacketListener.java @@ -1,15 +1,15 @@ package net.earthcomputer.clientcommands.c2c; import com.mojang.brigadier.exceptions.CommandSyntaxException; -import net.earthcomputer.clientcommands.c2c.packets.CoinflipC2CPackets; +import net.earthcomputer.clientcommands.c2c.packets.DiceRollC2CPackets; import net.earthcomputer.clientcommands.c2c.packets.MessageC2CPacket; public interface CCPacketListener { void onMessageC2CPacket(MessageC2CPacket packet); - void onCoinflipInitC2CPacket(CoinflipC2CPackets.CoinflipInitC2CPacket packet) throws CommandSyntaxException; + void onCoinflipInitC2CPacket(DiceRollC2CPackets.DiceRollInitC2CPacket packet) throws CommandSyntaxException; - void onCoinflipAcceptedC2CPacket(CoinflipC2CPackets.CoinflipAcceptedC2CPacket packet) throws CommandSyntaxException; + void onCoinflipAcceptedC2CPacket(DiceRollC2CPackets.DiceRollAcceptedC2CPacket packet) throws CommandSyntaxException; - void onCoinflipResultC2CPacket(CoinflipC2CPackets.CoinflipResultC2CPacket packet); + void onCoinflipResultC2CPacket(DiceRollC2CPackets.DiceRollResultC2CPacket packet); } diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/packets/CoinflipC2CPackets.java b/src/main/java/net/earthcomputer/clientcommands/c2c/packets/DiceRollC2CPackets.java similarity index 69% rename from src/main/java/net/earthcomputer/clientcommands/c2c/packets/CoinflipC2CPackets.java rename to src/main/java/net/earthcomputer/clientcommands/c2c/packets/DiceRollC2CPackets.java index 04880e7cd..8466f6dfc 100644 --- a/src/main/java/net/earthcomputer/clientcommands/c2c/packets/CoinflipC2CPackets.java +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/packets/DiceRollC2CPackets.java @@ -9,26 +9,30 @@ import java.math.BigInteger; import java.util.BitSet; -public class CoinflipC2CPackets { +public class DiceRollC2CPackets { // use a diffie hellman key exchange in order to ensure that the coinflip is fair - public static class CoinflipInitC2CPacket implements C2CPacket { + public static class DiceRollInitC2CPacket implements C2CPacket { public final String sender; + public final int sides; public final byte[] ABHash; - public CoinflipInitC2CPacket(String sender, byte[] ABHash) { + public DiceRollInitC2CPacket(String sender, int sides, byte[] ABHash) { this.sender = sender; + this.sides = sides; this.ABHash = ABHash; } - public CoinflipInitC2CPacket(PacketByteBuf raw) { + public DiceRollInitC2CPacket(PacketByteBuf raw) { this.sender = raw.readString(); + this.sides = raw.readInt(); this.ABHash = raw.readByteArray(); } @Override public void write(PacketByteBuf buf) { buf.writeString(this.sender); + buf.writeInt(this.sides); buf.writeByteArray(this.ABHash); } @@ -38,16 +42,16 @@ public void apply(CCPacketListener listener) throws CommandSyntaxException { } } - public static class CoinflipAcceptedC2CPacket implements C2CPacket { + public static class DiceRollAcceptedC2CPacket implements C2CPacket { public final String sender; public final BigInteger AB; - public CoinflipAcceptedC2CPacket(String sender, BigInteger publicKey) { + public DiceRollAcceptedC2CPacket(String sender, BigInteger publicKey) { this.sender = sender; this.AB = publicKey; } - public CoinflipAcceptedC2CPacket(PacketByteBuf stringBuf) { + public DiceRollAcceptedC2CPacket(PacketByteBuf stringBuf) { this.sender = stringBuf.readString(); this.AB = new BigInteger(stringBuf.readBitSet().toByteArray()); } @@ -64,16 +68,16 @@ public void apply(CCPacketListener listener) throws CommandSyntaxException { } } - public static class CoinflipResultC2CPacket implements C2CPacket { + public static class DiceRollResultC2CPacket implements C2CPacket { public final String sender; public final BigInteger s; - public CoinflipResultC2CPacket(String sender, BigInteger s) { + public DiceRollResultC2CPacket(String sender, BigInteger s) { this.sender = sender; this.s = s; } - public CoinflipResultC2CPacket(PacketByteBuf stringBuf) { + public DiceRollResultC2CPacket(PacketByteBuf stringBuf) { this.sender = stringBuf.readString(); this.s = new BigInteger(stringBuf.readBitSet().toByteArray()); } @@ -91,8 +95,8 @@ public void apply(CCPacketListener listener) { } public static void register() { - CCPacketHandler.register(CoinflipInitC2CPacket.class, CoinflipInitC2CPacket::new); - CCPacketHandler.register(CoinflipAcceptedC2CPacket.class, CoinflipAcceptedC2CPacket::new); - CCPacketHandler.register(CoinflipResultC2CPacket.class, CoinflipResultC2CPacket::new); + CCPacketHandler.register(DiceRollInitC2CPacket.class, DiceRollInitC2CPacket::new); + CCPacketHandler.register(DiceRollAcceptedC2CPacket.class, DiceRollAcceptedC2CPacket::new); + CCPacketHandler.register(DiceRollResultC2CPacket.class, DiceRollResultC2CPacket::new); } } diff --git a/src/main/java/net/earthcomputer/clientcommands/command/CoinflipCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/DiceRollCommand.java similarity index 59% rename from src/main/java/net/earthcomputer/clientcommands/command/CoinflipCommand.java rename to src/main/java/net/earthcomputer/clientcommands/command/DiceRollCommand.java index 5f41af06e..dc52abbe8 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/CoinflipCommand.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/DiceRollCommand.java @@ -7,7 +7,7 @@ import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.logging.LogUtils; import net.earthcomputer.clientcommands.c2c.CCNetworkHandler; -import net.earthcomputer.clientcommands.c2c.packets.CoinflipC2CPackets; +import net.earthcomputer.clientcommands.c2c.packets.DiceRollC2CPackets; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.PlayerListEntry; @@ -23,12 +23,11 @@ import java.security.SecureRandom; import java.util.*; -import static dev.xpple.clientarguments.arguments.CGameProfileArgumentType.gameProfile; -import static dev.xpple.clientarguments.arguments.CGameProfileArgumentType.getCProfileArgument; -import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument; -import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; +import static com.mojang.brigadier.arguments.IntegerArgumentType.*; +import static dev.xpple.clientarguments.arguments.CGameProfileArgumentType.*; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.*; -public class CoinflipCommand { +public class DiceRollCommand { private static final Logger LOGGER = LogUtils.getLogger(); private static final SimpleCommandExceptionType PLAYER_NOT_FOUND_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("commands.cwe.playerNotFound")); @@ -45,31 +44,41 @@ public class CoinflipCommand { // TODO: make these clear old failed ones to not leak memory - // hash's of opponent's ABs private static final Map opponentHash = new HashMap<>(); - - // oppoent name -> your AB + private static final Map rollSides = new HashMap<>(); private static final Map playerAB = new HashMap<>(); - - // result to compare with opponent's private static final Map result = new HashMap<>(); private static final SecureRandom random = new SecureRandom(); public static void register(CommandDispatcher dispatcher) { dispatcher.register(literal("ccoinflip") - .executes(ctx -> localCoinflip(ctx.getSource())) + .executes(ctx -> localDiceroll(ctx.getSource(), 2)) .then(argument("player", gameProfile()) - .executes(ctx -> coinflip(ctx.getSource(), getCProfileArgument(ctx, "player"))))); + .executes(ctx -> diceroll(ctx.getSource(), getCProfileArgument(ctx, "player"), 2)))); + + dispatcher.register(literal("cdiceroll") + .executes(ctx -> localDiceroll(ctx.getSource(), 6)) + .then(argument("sides", integer(2, 100)) + .executes(ctx -> localDiceroll(ctx.getSource(), getInteger(ctx, "sides"))) + .then(argument("player", gameProfile()) + .executes(ctx -> diceroll(ctx.getSource(), getCProfileArgument(ctx, "player"), getInteger(ctx, "sides"))))) + .then(argument("player", gameProfile()) + .executes(ctx -> diceroll(ctx.getSource(), getCProfileArgument(ctx, "player"), 6)))); + } - private static int localCoinflip(FabricClientCommandSource source) { - source.sendFeedback(random.nextBoolean() ? Text.translatable("commands.coinflip.heads") : Text.translatable("commands.coinflip.tails")); + private static int localDiceroll(FabricClientCommandSource source, int sides) { + if (sides == 2) { + source.sendFeedback(random.nextBoolean() ? Text.translatable("commands.diceroll.heads") : Text.translatable("commands.diceroll.tails")); + } else { + source.sendFeedback(Text.literal(Integer.toString(random.nextInt(sides) + 1))); + } return Command.SINGLE_SUCCESS; } public static byte[] toSHA1(byte[] convertme) { - MessageDigest md = null; + MessageDigest md; try { md = MessageDigest.getInstance("SHA-1"); } @@ -79,7 +88,7 @@ public static byte[] toSHA1(byte[] convertme) { return md.digest(convertme); } - private static int coinflip(FabricClientCommandSource source, Collection profiles) throws CommandSyntaxException { + private static int diceroll(FabricClientCommandSource source, Collection profiles, int sides) throws CommandSyntaxException { assert source.getClient().getNetworkHandler() != null; if (profiles.size() != 1) { throw PLAYER_NOT_FOUND_EXCEPTION.create(); @@ -90,17 +99,21 @@ private static int coinflip(FabricClientCommandSource source, Collection 0) { + headstails = Text.translatable("commands.diceroll.heads"); + } else { + headstails = Text.translatable("commands.diceroll.tails"); + } + message = Text.translatable("commands.diceroll.value", Text.translatable("commands.diceroll.coinflip"), packet.sender, headstails); + } else { + BigInteger sideVal = p.divide(BigInteger.valueOf(rollSides.get(packet.sender))); + int side = packet.s.divide(sideVal).intValue() + 1; + message = Text.translatable("commands.diceroll.value", Text.translatable("commands.diceroll.diceroll", rollSides.get(packet.sender)), packet.sender, side); + } MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(message); } else { LOGGER.info("expected: " + result.get(packet.sender).toString(16)); LOGGER.info("actual: " + packet.s.toString(16)); - MutableText message = Text.translatable("commands.coinflip.cheater", packet.sender).formatted(Formatting.RED); + MutableText message = Text.translatable("commands.diceroll.cheater", packet.sender).formatted(Formatting.RED); MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(message); } result.remove(packet.sender); + rollSides.remove(packet.sender); } else { throw new IllegalStateException("Coinflip result packet received before accepted/init packet"); } diff --git a/src/main/resources/assets/clientcommands/lang/en_us.json b/src/main/resources/assets/clientcommands/lang/en_us.json index 22967c8b8..ba8e53953 100644 --- a/src/main/resources/assets/clientcommands/lang/en_us.json +++ b/src/main/resources/assets/clientcommands/lang/en_us.json @@ -108,12 +108,14 @@ "commands.chotbar.notCreative": "Player must be in creative mode to restore item hotbars", "commands.chotbar.restoredHotbar": "Restored item hotbar %d", - "commands.coinflip.sent": "Sent coin flip request to %s", - "commands.coinflip.received": "Received coin flip request from %s", - "commands.coinflip.value": "Coin flip result from %s: %s", - "commands.coinflip.heads": "Heads", - "commands.coinflip.tails": "Tails", - "commands.coinflip.cheater": "I think %s is cheating, they got a different value.", + "commands.diceroll.diceroll": "dice roll (%s side)", + "commands.diceroll.coinflip": "coin flip", + "commands.diceroll.sent": "Sent %s request to %s", + "commands.diceroll.received": "Received %s request from %s", + "commands.diceroll.value": "%s result from %s: %s", + "commands.diceroll.heads": "Heads", + "commands.diceroll.tails": "Tails", + "commands.diceroll.cheater": "I think %s is cheating, they got a different value.", "commands.citemgroup.notFound": "Item group \"%s\" not found", "commands.citemgroup.outOfBounds": "Index %d is out of bounds", From 8cde39960e1900223429a18c7fd14e7e07da9eeb Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Tue, 20 Dec 2022 11:54:56 -0700 Subject: [PATCH 05/10] fix function names --- .../earthcomputer/clientcommands/c2c/CCNetworkHandler.java | 6 +++--- .../clientcommands/command/DiceRollCommand.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/CCNetworkHandler.java b/src/main/java/net/earthcomputer/clientcommands/c2c/CCNetworkHandler.java index c793a7c3b..1cb075583 100644 --- a/src/main/java/net/earthcomputer/clientcommands/c2c/CCNetworkHandler.java +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/CCNetworkHandler.java @@ -86,16 +86,16 @@ public void onMessageC2CPacket(MessageC2CPacket packet) { @Override public void onCoinflipInitC2CPacket(DiceRollC2CPackets.DiceRollInitC2CPacket packet) throws CommandSyntaxException { - DiceRollCommand.initCoinflip(packet); + DiceRollCommand.initDiceroll(packet); } @Override public void onCoinflipAcceptedC2CPacket(DiceRollC2CPackets.DiceRollAcceptedC2CPacket packet) throws CommandSyntaxException { - DiceRollCommand.acceptCoinflip(packet); + DiceRollCommand.acceptDiceroll(packet); } @Override public void onCoinflipResultC2CPacket(DiceRollC2CPackets.DiceRollResultC2CPacket packet) { - DiceRollCommand.completeCoinflip(packet); + DiceRollCommand.completeDiceroll(packet); } } diff --git a/src/main/java/net/earthcomputer/clientcommands/command/DiceRollCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/DiceRollCommand.java index dc52abbe8..4f1894abc 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/DiceRollCommand.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/DiceRollCommand.java @@ -126,7 +126,7 @@ public static String byteArrayToHexString(byte[] b) { } - public static void initCoinflip(DiceRollC2CPackets.DiceRollInitC2CPacket packet) throws CommandSyntaxException { + public static void initDiceroll(DiceRollC2CPackets.DiceRollInitC2CPacket packet) throws CommandSyntaxException { // get sender from name PlayerListEntry sender = MinecraftClient.getInstance().getNetworkHandler().getPlayerList().stream() @@ -160,7 +160,7 @@ public static void initCoinflip(DiceRollC2CPackets.DiceRollInitC2CPacket packet) CCNetworkHandler.getInstance().sendPacket(response, sender); } - public static void acceptCoinflip(DiceRollC2CPackets.DiceRollAcceptedC2CPacket packet) throws CommandSyntaxException { + public static void acceptDiceroll(DiceRollC2CPackets.DiceRollAcceptedC2CPacket packet) throws CommandSyntaxException { if (!opponentHash.containsKey(packet.sender)) { throw PLAYER_NOT_FOUND_EXCEPTION.create(); } @@ -194,7 +194,7 @@ public static void acceptCoinflip(DiceRollC2CPackets.DiceRollAcceptedC2CPacket p playerAB.remove(packet.sender); } - public static void completeCoinflip(DiceRollC2CPackets.DiceRollResultC2CPacket packet) { + public static void completeDiceroll(DiceRollC2CPackets.DiceRollResultC2CPacket packet) { if (result.containsKey(packet.sender)) { if (result.get(packet.sender).equals(packet.s)) { LOGGER.info("Coinflip val: " + packet.s.toString(16)); From 46d4223fcb1d292ae503f11f57d5e8f316495c3b Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Tue, 20 Dec 2022 12:40:45 -0700 Subject: [PATCH 06/10] bump to 1024 bytes, add timeout safety. make message too long say calculated length --- .../clientcommands/c2c/CCNetworkHandler.java | 32 ++++++++++++---- .../command/DiceRollCommand.java | 38 ++++++++++++++++--- .../assets/clientcommands/lang/en_us.json | 3 +- 3 files changed, 59 insertions(+), 14 deletions(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/CCNetworkHandler.java b/src/main/java/net/earthcomputer/clientcommands/c2c/CCNetworkHandler.java index 1cb075583..bb4ad9a33 100644 --- a/src/main/java/net/earthcomputer/clientcommands/c2c/CCNetworkHandler.java +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/CCNetworkHandler.java @@ -1,6 +1,7 @@ package net.earthcomputer.clientcommands.c2c; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.logging.LogUtils; import io.netty.buffer.Unpooled; @@ -21,7 +22,7 @@ public class CCNetworkHandler implements CCPacketListener { - private static final SimpleCommandExceptionType MESSAGE_TOO_LONG_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("ccpacket.messageTooLong")); + private static final DynamicCommandExceptionType MESSAGE_TOO_LONG_EXCEPTION = new DynamicCommandExceptionType(d -> Text.translatable("ccpacket.messageTooLong", d)); private static final SimpleCommandExceptionType PUBLIC_KEY_NOT_FOUND_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("ccpacket.publicKeyNotFound")); private static final SimpleCommandExceptionType ENCRYPTION_FAILED_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("ccpacket.encryptionFailed")); @@ -55,17 +56,32 @@ public void sendPacket(C2CPacket packet, PlayerListEntry recipient) throws Comma buf.writeInt(id); packet.write(buf); byte[] compressed = ConversionHelper.Gzip.compress(buf.getWrittenBytes()); - if (compressed.length > 245) { - throw MESSAGE_TOO_LONG_EXCEPTION.create(); + // split compressed into 245 byte chunks + int chunks = (compressed.length + 244) / 245; + byte[][] chunked = new byte[chunks][]; + for (int i = 0; i < chunks; i++) { + int start = i * 245; + int end = Math.min(start + 245, compressed.length); + chunked[i] = new byte[end - start]; + System.arraycopy(compressed, start, chunked[i], 0, end - start); } - byte[] encrypted = ConversionHelper.RsaEcb.encrypt(compressed, key); - if (encrypted == null || encrypted.length == 0) { - throw ENCRYPTION_FAILED_EXCEPTION.create(); + // encrypt each chunk + byte[][] encrypted = new byte[chunks][]; + for (int i = 0; i < chunks; i++) { + encrypted[i] = ConversionHelper.RsaEcb.encrypt(chunked[i], key); + if (encrypted[i] == null || encrypted[i].length == 0) { + throw ENCRYPTION_FAILED_EXCEPTION.create(); + } } - String packetString = ConversionHelper.BaseUTF8.toUnicode(encrypted); + // join encrypted chunks into one byte array + byte[] joined = new byte[encrypted.length * 256]; + for (int i = 0; i < encrypted.length; i++) { + System.arraycopy(encrypted[i], 0, joined, i * 256, 256); + } + String packetString = ConversionHelper.BaseUTF8.toUnicode(joined); String commandString = "w " + recipient.getProfile().getName() + " CCENC:" + packetString; if (commandString.length() >= 256) { - throw MESSAGE_TOO_LONG_EXCEPTION.create(); + throw MESSAGE_TOO_LONG_EXCEPTION.create(commandString.length()); } MinecraftClient.getInstance().getNetworkHandler().sendChatCommand(commandString); OutgoingPacketFilter.addPacket(packetString); diff --git a/src/main/java/net/earthcomputer/clientcommands/command/DiceRollCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/DiceRollCommand.java index 4f1894abc..74b089ec0 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/DiceRollCommand.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/DiceRollCommand.java @@ -36,13 +36,14 @@ public class DiceRollCommand { 0xFFFFFFFF, 0xFFFFFFFF, 0xC90FDAA2, 0x2168C234, 0xC4C6628B, 0x80DC1CD1, 0x29024E08, 0x8A67CC74, 0x020BBEA6, 0x3B139B22, 0x514A0879, 0x8E3404DD, 0xEF9519B3, 0xCD3A431B, 0x302B0A6D, 0xF25F1437, 0x4FE1356D, 0x6D51C245, - 0xE485B576, 0x625E7EC6, 0xF44C42E9, 0xA63A3620, 0xFFFFFFFF, 0xFFFFFFFF + 0xE485B576, 0x625E7EC6, 0xF44C42E9, 0xA637ED6B, 0x0BFF5CB6, 0xF406B7ED, + 0xEE386BFB, 0x5A899FA5, 0xAE9F2411, 0x7C4B1FE6, 0x49286651, 0xECE65381, + 0xFFFFFFFF, 0xFFFFFFFF }) ); private static final BigInteger g = BigInteger.TWO; - // TODO: make these clear old failed ones to not leak memory private static final Map opponentHash = new HashMap<>(); private static final Map rollSides = new HashMap<>(); @@ -102,7 +103,7 @@ private static int diceroll(FabricClientCommandSource source, Collection { + if (playerAB.remove(recipient) != null || + rollSides.remove(recipient) != null || + opponentHash.remove(recipient) != null || + result.remove(recipient) != null) { + if (sides == 2) { + MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.translatable("commands.diceroll.timeout", recipient, Text.translatable("commands.diceroll.coinflip"))); + } else { + MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.translatable("commands.diceroll.timeout", recipient, Text.translatable("commands.diceroll.diceroll", sides))); + } + } + }); + } + }, 5000L); + } + public static void initDiceroll(DiceRollC2CPackets.DiceRollInitC2CPacket packet) throws CommandSyntaxException { @@ -137,7 +163,7 @@ public static void initDiceroll(DiceRollC2CPackets.DiceRollInitC2CPacket packet) opponentHash.put(packet.sender, packet.ABHash); if (!playerAB.containsKey(packet.sender)) { - BigInteger b = new BigInteger(729, random).abs(); + BigInteger b = new BigInteger(1025, random).abs(); BigInteger B = g.modPow(b, p); playerAB.put(packet.sender, b); rollSides.put(packet.sender, packet.sides); @@ -148,9 +174,11 @@ public static void initDiceroll(DiceRollC2CPackets.DiceRollInitC2CPacket packet) if (packet.sides == 2) { message = Text.translatable("commands.diceroll.received", Text.translatable("commands.diceroll.coinflip"), packet.sender); } else { - message = Text.translatable("commands.diceroll.recieved", Text.translatable("commands.diceroll.diceroll", packet.sides), packet.sender); + message = Text.translatable("commands.diceroll.received", Text.translatable("commands.diceroll.diceroll", packet.sides), packet.sender); } MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(message); + + timeout(packet.sender, packet.sides); } BigInteger b = playerAB.get(packet.sender); diff --git a/src/main/resources/assets/clientcommands/lang/en_us.json b/src/main/resources/assets/clientcommands/lang/en_us.json index ba8e53953..02863b919 100644 --- a/src/main/resources/assets/clientcommands/lang/en_us.json +++ b/src/main/resources/assets/clientcommands/lang/en_us.json @@ -116,6 +116,7 @@ "commands.diceroll.heads": "Heads", "commands.diceroll.tails": "Tails", "commands.diceroll.cheater": "I think %s is cheating, they got a different value.", + "commands.diceroll.timeout": "Timeout waiting for %s's response to %s request", "commands.citemgroup.notFound": "Item group \"%s\" not found", "commands.citemgroup.outOfBounds": "Index %d is out of bounds", @@ -305,7 +306,7 @@ "snakeGame.title": "Snake", "snakeGame.score": "Score: %d", - "ccpacket.messageTooLong": "Message too long (max. 255 characters)", + "ccpacket.messageTooLong": "Message too long (max. 255 characters) got %s characters", "ccpacket.publicKeyNotFound": "Public key not found", "ccpacket.encryptionFailed": "Something failed while encrypting your message", "ccpacket.malformedPacket": "You have received a malformed C2C packet:", From 7ec2e3d865e7800cbe8be728a45fda5e67a57a1f Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Tue, 20 Dec 2022 12:51:57 -0700 Subject: [PATCH 07/10] forgot to make dynamic change on decrypt --- .../clientcommands/mixin/MixinChatHud.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/mixin/MixinChatHud.java b/src/main/java/net/earthcomputer/clientcommands/mixin/MixinChatHud.java index c65fffc07..9b19882a1 100644 --- a/src/main/java/net/earthcomputer/clientcommands/mixin/MixinChatHud.java +++ b/src/main/java/net/earthcomputer/clientcommands/mixin/MixinChatHud.java @@ -63,7 +63,13 @@ private void handleIfPacket(Text content, CallbackInfo ci) { private static boolean handleC2CPacket(String content) { byte[] encrypted = ConversionHelper.BaseUTF8.fromUnicode(content); - encrypted = Arrays.copyOf(encrypted, 256); + // round down to multiple of 256 bytes + int length = encrypted.length & ~0xFF; + // copy to new array of arrays + byte[][] encryptedArrays = new byte[length / 256][]; + for (int i = 0; i < length; i += 256) { + encryptedArrays[i / 256] = Arrays.copyOfRange(encrypted, i, i + 256); + } if (!(MinecraftClient.getInstance().getProfileKeys() instanceof IHasPrivateKey privateKeyHolder)) { return false; } @@ -71,10 +77,16 @@ private static boolean handleC2CPacket(String content) { if (key.isEmpty()) { return false; } - byte[] decrypted = ConversionHelper.RsaEcb.decrypt(encrypted, key.get()); - if (decrypted == null) { - return false; + // decrypt + byte[][] decryptedArrays = new byte[encryptedArrays.length][]; + for (int i = 0; i < encryptedArrays.length; i++) { + decryptedArrays[i] = ConversionHelper.RsaEcb.decrypt(encryptedArrays[i], key.get()); + if (decryptedArrays[i] == null) { + return false; + } } + // copy to new array + byte[] decrypted = new byte[decryptedArrays.length * 256]; byte[] uncompressed = ConversionHelper.Gzip.uncompress(decrypted); PacketByteBuf buf = new PacketByteBuf(Unpooled.wrappedBuffer(uncompressed)); int id = buf.readInt(); From b0e6c376e5f9aed7e7b8a3afe78d090249b79d2a Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Tue, 20 Dec 2022 12:53:40 -0700 Subject: [PATCH 08/10] missed a spot --- .../earthcomputer/clientcommands/mixin/MixinChatHud.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/mixin/MixinChatHud.java b/src/main/java/net/earthcomputer/clientcommands/mixin/MixinChatHud.java index 9b19882a1..84466ca4f 100644 --- a/src/main/java/net/earthcomputer/clientcommands/mixin/MixinChatHud.java +++ b/src/main/java/net/earthcomputer/clientcommands/mixin/MixinChatHud.java @@ -78,15 +78,22 @@ private static boolean handleC2CPacket(String content) { return false; } // decrypt + int len = 0; byte[][] decryptedArrays = new byte[encryptedArrays.length][]; for (int i = 0; i < encryptedArrays.length; i++) { decryptedArrays[i] = ConversionHelper.RsaEcb.decrypt(encryptedArrays[i], key.get()); if (decryptedArrays[i] == null) { return false; } + len += decryptedArrays[i].length; } // copy to new array - byte[] decrypted = new byte[decryptedArrays.length * 256]; + byte[] decrypted = new byte[len]; + int pos = 0; + for (byte[] decryptedArray : decryptedArrays) { + System.arraycopy(decryptedArray, 0, decrypted, pos, decryptedArray.length); + pos += decryptedArray.length; + } byte[] uncompressed = ConversionHelper.Gzip.uncompress(decrypted); PacketByteBuf buf = new PacketByteBuf(Unpooled.wrappedBuffer(uncompressed)); int id = buf.readInt(); From f00d2daf9d914ade26a768730840bbbb24bc8e58 Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Tue, 20 Dec 2022 13:04:59 -0700 Subject: [PATCH 09/10] fix timeout breaking things --- .../clientcommands/command/DiceRollCommand.java | 16 +++++++++++++++- .../assets/clientcommands/lang/en_us.json | 1 + 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/command/DiceRollCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/DiceRollCommand.java index 74b089ec0..d3b9d56f9 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/DiceRollCommand.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/DiceRollCommand.java @@ -4,6 +4,7 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.logging.LogUtils; import net.earthcomputer.clientcommands.c2c.CCNetworkHandler; @@ -22,6 +23,8 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import static com.mojang.brigadier.arguments.IntegerArgumentType.*; import static dev.xpple.clientarguments.arguments.CGameProfileArgumentType.*; @@ -30,6 +33,7 @@ public class DiceRollCommand { private static final Logger LOGGER = LogUtils.getLogger(); private static final SimpleCommandExceptionType PLAYER_NOT_FOUND_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("commands.cwe.playerNotFound")); + private static final DynamicCommandExceptionType WAITING_FOR_RESPONSE_EXCEPTION = new DynamicCommandExceptionType(d -> Text.translatable("commands.diceroll.waitingForResponse", d)); private static final BigInteger p = new BigInteger(1, intsToBytes(new int[] { @@ -49,6 +53,7 @@ public class DiceRollCommand { private static final Map rollSides = new HashMap<>(); private static final Map playerAB = new HashMap<>(); private static final Map result = new HashMap<>(); + private static final Map timeoutHolder = new HashMap<>(); private static final SecureRandom random = new SecureRandom(); @@ -108,6 +113,10 @@ private static int diceroll(FabricClientCommandSource source, Collection { + if (timeout.get()) { + return; + } if (playerAB.remove(recipient) != null || rollSides.remove(recipient) != null || opponentHash.remove(recipient) != null || @@ -248,6 +261,7 @@ public static void completeDiceroll(DiceRollC2CPackets.DiceRollResultC2CPacket p MutableText message = Text.translatable("commands.diceroll.cheater", packet.sender).formatted(Formatting.RED); MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(message); } + timeoutHolder.remove(packet.sender).set(true); result.remove(packet.sender); rollSides.remove(packet.sender); } else { diff --git a/src/main/resources/assets/clientcommands/lang/en_us.json b/src/main/resources/assets/clientcommands/lang/en_us.json index 02863b919..4cdc424eb 100644 --- a/src/main/resources/assets/clientcommands/lang/en_us.json +++ b/src/main/resources/assets/clientcommands/lang/en_us.json @@ -117,6 +117,7 @@ "commands.diceroll.tails": "Tails", "commands.diceroll.cheater": "I think %s is cheating, they got a different value.", "commands.diceroll.timeout": "Timeout waiting for %s's response to %s request", + "commands.diceroll.waitingForResponse": "Waiting for %s's response already", "commands.citemgroup.notFound": "Item group \"%s\" not found", "commands.citemgroup.outOfBounds": "Index %d is out of bounds", From b9fba3efca0788a7ea93121e3991b2329809fa2b Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Mon, 3 Jul 2023 05:52:12 -0600 Subject: [PATCH 10/10] update --- .../earthcomputer/clientcommands/c2c/C2CPacket.java | 3 ++- .../clientcommands/mixin/MixinChatHud.java | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacket.java b/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacket.java index b07112279..b64c1ed50 100644 --- a/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacket.java +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacket.java @@ -1,9 +1,10 @@ package net.earthcomputer.clientcommands.c2c; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.minecraft.network.PacketByteBuf; public interface C2CPacket { void write(PacketByteBuf buf); - void apply(CCPacketListener listener); + void apply(CCPacketListener listener) throws CommandSyntaxException; } diff --git a/src/main/java/net/earthcomputer/clientcommands/mixin/MixinChatHud.java b/src/main/java/net/earthcomputer/clientcommands/mixin/MixinChatHud.java index 086812c5e..3ff3e7060 100644 --- a/src/main/java/net/earthcomputer/clientcommands/mixin/MixinChatHud.java +++ b/src/main/java/net/earthcomputer/clientcommands/mixin/MixinChatHud.java @@ -1,5 +1,7 @@ package net.earthcomputer.clientcommands.mixin; +import com.mojang.brigadier.Message; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import io.netty.buffer.Unpooled; import net.earthcomputer.clientcommands.Configs; import net.earthcomputer.clientcommands.c2c.*; @@ -105,6 +107,14 @@ private static boolean handleC2CPacket(String content) { } try { c2CPacket.apply(CCNetworkHandler.getInstance()); + } catch (CommandSyntaxException e) { + Message m = e.getRawMessage(); + if (m instanceof Text t) { + MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(t); + } else { + MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.of(m.getString())); + } + e.printStackTrace(); } catch (Exception e) { MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.of(e.getMessage())); e.printStackTrace();