feat: chomens mod integration (completely unused)
i will probably make it a server instead idk, right now i just wanted to make the chipmunkmod core refill silent
This commit is contained in:
parent
043efe9547
commit
265a35080b
9 changed files with 486 additions and 1 deletions
build-number.txt
src/main/java/me/chayapak1/chomens_bot
|
@ -1 +1 @@
|
|||
2131
|
||||
2160
|
|
@ -107,6 +107,7 @@ public class Bot extends SessionAdapter {
|
|||
public PacketSnifferPlugin packetSniffer;
|
||||
public VoiceChatPlugin voiceChat;
|
||||
public TeamJoinerPlugin teamJoiner;
|
||||
public ChomeNSModIntegrationPlugin chomeNSMod;
|
||||
public AuthPlugin auth;
|
||||
public ScreensharePlugin screenshare;
|
||||
public FormatCheckerPlugin formatChecker;
|
||||
|
@ -159,6 +160,7 @@ public class Bot extends SessionAdapter {
|
|||
this.packetSniffer = new PacketSnifferPlugin(this);
|
||||
this.voiceChat = new VoiceChatPlugin(this);
|
||||
this.teamJoiner = new TeamJoinerPlugin(this);
|
||||
this.chomeNSMod = new ChomeNSModIntegrationPlugin(this);
|
||||
this.auth = new AuthPlugin(this);
|
||||
// this.screenshare = new ScreensharePlugin(this);
|
||||
this.formatChecker = new FormatCheckerPlugin(this);
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package me.chayapak1.chomens_bot.chomeNSMod;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public interface Packet {
|
||||
int getId ();
|
||||
void serialize (ByteBuf buf);
|
||||
}
|
52
src/main/java/me/chayapak1/chomens_bot/chomeNSMod/Types.java
Normal file
52
src/main/java/me/chayapak1/chomens_bot/chomeNSMod/Types.java
Normal file
|
@ -0,0 +1,52 @@
|
|||
package me.chayapak1.chomens_bot.chomeNSMod;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Types {
|
||||
public static UUID readUUID (ByteBuf buf) {
|
||||
final long mostSignificantBits = buf.readLong();
|
||||
final long leastSignificantBits = buf.readLong();
|
||||
|
||||
return new UUID(mostSignificantBits, leastSignificantBits);
|
||||
}
|
||||
|
||||
public static void writeUUID (ByteBuf buf, UUID uuid) {
|
||||
buf.writeLong(uuid.getMostSignificantBits());
|
||||
buf.writeLong(uuid.getLeastSignificantBits());
|
||||
}
|
||||
|
||||
public static void writeString (ByteBuf buf, String string) {
|
||||
buf.writeInt(string.length());
|
||||
buf.writeBytes(string.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public static String readString (ByteBuf buf) {
|
||||
final int length = buf.readInt();
|
||||
|
||||
final byte[] bytes = new byte[length];
|
||||
buf.readBytes(bytes);
|
||||
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public static Component readComponent (ByteBuf buf) {
|
||||
final String stringJSON = readString(buf);
|
||||
|
||||
try {
|
||||
return GsonComponentSerializer.gson().deserialize(stringJSON);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeComponent (ByteBuf buf, Component component) {
|
||||
final String stringJSON = GsonComponentSerializer.gson().serialize(component);
|
||||
|
||||
writeString(buf, stringJSON);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package me.chayapak1.chomens_bot.chomeNSMod.clientboundPackets;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import me.chayapak1.chomens_bot.chomeNSMod.Packet;
|
||||
import me.chayapak1.chomens_bot.chomeNSMod.Types;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class ClientboundCoreOutputPacket implements Packet {
|
||||
public final UUID runID;
|
||||
public final Component output;
|
||||
|
||||
public ClientboundCoreOutputPacket (UUID runID, Component output) {
|
||||
this.runID = runID;
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
public ClientboundCoreOutputPacket (ByteBuf buf) {
|
||||
this.runID = Types.readUUID(buf);
|
||||
this.output = Types.readComponent(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId () {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize (ByteBuf buf) {
|
||||
Types.writeUUID(buf, this.runID);
|
||||
Types.writeComponent(buf, this.output);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package me.chayapak1.chomens_bot.chomeNSMod.clientboundPackets;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import me.chayapak1.chomens_bot.chomeNSMod.Packet;
|
||||
|
||||
public class ClientboundSuccessfulHandshakePacket implements Packet {
|
||||
public ClientboundSuccessfulHandshakePacket () {
|
||||
}
|
||||
|
||||
public ClientboundSuccessfulHandshakePacket (ByteBuf buf) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize (ByteBuf buf) {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package me.chayapak1.chomens_bot.chomeNSMod.serverboundPackets;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import me.chayapak1.chomens_bot.chomeNSMod.Packet;
|
||||
|
||||
public class ServerboundHandshakePacket implements Packet {
|
||||
public ServerboundHandshakePacket () {
|
||||
}
|
||||
|
||||
public ServerboundHandshakePacket (ByteBuf buf) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId () {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize (ByteBuf buf) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package me.chayapak1.chomens_bot.chomeNSMod.serverboundPackets;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import me.chayapak1.chomens_bot.chomeNSMod.Packet;
|
||||
import me.chayapak1.chomens_bot.chomeNSMod.Types;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class ServerboundRunCoreCommandPacket implements Packet {
|
||||
public final UUID runID;
|
||||
public final String command;
|
||||
|
||||
public ServerboundRunCoreCommandPacket (UUID runID, String command) {
|
||||
this.runID = runID;
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public ServerboundRunCoreCommandPacket (ByteBuf buf) {
|
||||
this.runID = Types.readUUID(buf);
|
||||
this.command = Types.readString(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId () {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize (ByteBuf buf) {
|
||||
Types.writeUUID(buf, this.runID);
|
||||
Types.writeString(buf, this.command);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,313 @@
|
|||
package me.chayapak1.chomens_bot.plugins;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import me.chayapak1.chomens_bot.Bot;
|
||||
import me.chayapak1.chomens_bot.Configuration;
|
||||
import me.chayapak1.chomens_bot.chomeNSMod.Packet;
|
||||
import me.chayapak1.chomens_bot.chomeNSMod.Types;
|
||||
import me.chayapak1.chomens_bot.chomeNSMod.clientboundPackets.ClientboundCoreOutputPacket;
|
||||
import me.chayapak1.chomens_bot.chomeNSMod.clientboundPackets.ClientboundSuccessfulHandshakePacket;
|
||||
import me.chayapak1.chomens_bot.chomeNSMod.serverboundPackets.ServerboundHandshakePacket;
|
||||
import me.chayapak1.chomens_bot.chomeNSMod.serverboundPackets.ServerboundRunCoreCommandPacket;
|
||||
import me.chayapak1.chomens_bot.data.player.PlayerEntry;
|
||||
import me.chayapak1.chomens_bot.util.LoggerUtilities;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.*;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
// This is inspired from the ChomeNS Bot Proxy which is in the JavaScript version of ChomeNS Bot.
|
||||
// Right now it is not really used anywhere, so you'll see duplicate codes from AuthPlugin
|
||||
public class ChomeNSModIntegrationPlugin implements ChatPlugin.Listener, PlayersPlugin.Listener {
|
||||
private static final String ID = "chomens_mod";
|
||||
|
||||
public static final List<Class<? extends Packet>> SERVERBOUND_PACKETS = new ArrayList<>();
|
||||
|
||||
static {
|
||||
SERVERBOUND_PACKETS.add(ServerboundHandshakePacket.class);
|
||||
SERVERBOUND_PACKETS.add(ServerboundRunCoreCommandPacket.class);
|
||||
}
|
||||
|
||||
private static PrivateKey PRIVATE_KEY;
|
||||
|
||||
private static final Map<String, PublicKey> CLIENT_PUBLIC_KEYS = new HashMap<>();
|
||||
private static final Path CLIENT_PUBLIC_KEYS_PATH = Path.of("client_public_keys");
|
||||
|
||||
private static final Path PRIVATE_KEY_PATH = Path.of("private.key");
|
||||
private static final Path PUBLIC_KEY_PATH = Path.of("public.key");
|
||||
|
||||
private static final String BEGIN_PRIVATE_KEY = "-----BEGIN CHOMENS BOT PRIVATE KEY-----";
|
||||
private static final String END_PRIVATE_KEY = "-----END CHOMENS BOT PRIVATE KEY-----";
|
||||
|
||||
private static final String BEGIN_PUBLIC_KEY = "-----BEGIN CHOMENS BOT PUBLIC KEY-----";
|
||||
private static final String END_PUBLIC_KEY = "-----END CHOMENS BOT PUBLIC KEY-----";
|
||||
|
||||
public static void init (Configuration config) {
|
||||
if (!config.ownerAuthentication.enabled) return;
|
||||
|
||||
try {
|
||||
// let's only check for the private key here
|
||||
if (!Files.exists(PRIVATE_KEY_PATH)) {
|
||||
final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
|
||||
keyGen.initialize(2048);
|
||||
|
||||
final KeyPair pair = keyGen.generateKeyPair();
|
||||
|
||||
// write the keys
|
||||
// (note: no newline split is intentional)
|
||||
final String encodedPrivateKey =
|
||||
BEGIN_PRIVATE_KEY + "\n" +
|
||||
Base64.getEncoder().encodeToString(pair.getPrivate().getEncoded()) +
|
||||
"\n" + END_PRIVATE_KEY;
|
||||
final String encodedPublicKey =
|
||||
BEGIN_PUBLIC_KEY + "\n" +
|
||||
Base64.getEncoder().encodeToString(pair.getPublic().getEncoded()) +
|
||||
"\n" + END_PUBLIC_KEY;
|
||||
|
||||
final BufferedWriter privateKeyWriter = Files.newBufferedWriter(PRIVATE_KEY_PATH);
|
||||
privateKeyWriter.write(encodedPrivateKey);
|
||||
privateKeyWriter.close();
|
||||
|
||||
final BufferedWriter publicKeyWriter = Files.newBufferedWriter(PUBLIC_KEY_PATH);
|
||||
publicKeyWriter.write(encodedPublicKey);
|
||||
publicKeyWriter.close();
|
||||
}
|
||||
|
||||
// is this a good way to remove the things?
|
||||
final String privateKeyString = new String(Files.readAllBytes(PRIVATE_KEY_PATH))
|
||||
.replace(BEGIN_PRIVATE_KEY + "\n", "")
|
||||
.replace("\n" + END_PRIVATE_KEY, "")
|
||||
.replace("\n", "")
|
||||
.trim();
|
||||
|
||||
final byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyString);
|
||||
final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
|
||||
PRIVATE_KEY = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
|
||||
|
||||
// lol this is so messy
|
||||
if (Files.isDirectory(CLIENT_PUBLIC_KEYS_PATH)) {
|
||||
try (final Stream<Path> files = Files.list(CLIENT_PUBLIC_KEYS_PATH)) {
|
||||
for (Path path : files.toList()) {
|
||||
try {
|
||||
final String publicKeyString = new String(Files.readAllBytes(path))
|
||||
.replace(BEGIN_PUBLIC_KEY + "\n", "")
|
||||
.replace("\n" + END_PUBLIC_KEY, "")
|
||||
.replace("\n", "")
|
||||
.trim();
|
||||
|
||||
final byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyString);
|
||||
final KeyFactory clientKeyFactory = KeyFactory.getInstance("RSA");
|
||||
|
||||
String username = path.getFileName().toString();
|
||||
|
||||
if (username.contains(".")) username = username.substring(0, username.lastIndexOf("."));
|
||||
|
||||
CLIENT_PUBLIC_KEYS.put(
|
||||
username,
|
||||
clientKeyFactory.generatePublic(new X509EncodedKeySpec(publicKeyBytes))
|
||||
);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException | IllegalArgumentException e) {
|
||||
LoggerUtilities.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private final Bot bot;
|
||||
|
||||
private final List<Listener> listeners = new ArrayList<>();
|
||||
|
||||
public final List<PlayerEntry> connectedPlayers = new ArrayList<>();
|
||||
|
||||
public ChomeNSModIntegrationPlugin (Bot bot) {
|
||||
this.bot = bot;
|
||||
|
||||
bot.chat.addListener(this);
|
||||
bot.players.addListener(this);
|
||||
}
|
||||
|
||||
public byte[] decrypt (byte[] data) throws Exception {
|
||||
final Cipher cipher = Cipher.getInstance("RSA");
|
||||
cipher.init(Cipher.DECRYPT_MODE, PRIVATE_KEY);
|
||||
|
||||
return cipher.doFinal(data);
|
||||
}
|
||||
|
||||
public String encrypt (String player, byte[] data) throws Exception {
|
||||
final PublicKey publicKey = CLIENT_PUBLIC_KEYS.get(player);
|
||||
|
||||
if (publicKey == null) return null;
|
||||
|
||||
final Cipher cipher = Cipher.getInstance("RSA");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||
|
||||
final byte[] encryptedBytes = cipher.doFinal(data);
|
||||
|
||||
return Base64.getEncoder().encodeToString(encryptedBytes);
|
||||
}
|
||||
|
||||
public void send (PlayerEntry target, Packet packet) {
|
||||
if (!connectedPlayers.contains(target)) return;
|
||||
|
||||
final ByteBuf buf = Unpooled.buffer();
|
||||
|
||||
buf.writeInt(packet.getId());
|
||||
packet.serialize(buf);
|
||||
|
||||
final byte[] bytes = new byte[buf.readableBytes()];
|
||||
buf.readBytes(bytes);
|
||||
|
||||
try {
|
||||
final String encrypted = encrypt(target.profile.getName(), bytes);
|
||||
|
||||
final Component component = Component
|
||||
.text(ID)
|
||||
.append(Component.text(encrypted));
|
||||
|
||||
bot.chat.tellraw(component, target.profile.getId());
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
private Pair<PlayerEntry, Packet> deserialize (byte[] data) {
|
||||
final ByteBuf buf = Unpooled.wrappedBuffer(data);
|
||||
|
||||
final UUID uuid = Types.readUUID(buf);
|
||||
|
||||
final PlayerEntry player = bot.players.getEntry(uuid);
|
||||
|
||||
if (player == null) return null;
|
||||
|
||||
final int id = buf.readInt();
|
||||
|
||||
final Class<? extends Packet> packetClass = SERVERBOUND_PACKETS.get(id);
|
||||
|
||||
if (packetClass == null) return null;
|
||||
|
||||
try {
|
||||
return Pair.of(player, packetClass.getDeclaredConstructor(ByteBuf.class).newInstance(buf));
|
||||
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean systemMessageReceived (Component component, String string, String ansi) {
|
||||
if (!(component instanceof TextComponent textComponent)) return true;
|
||||
|
||||
final String id = textComponent.content();
|
||||
|
||||
if (!id.equals(ID)) return true;
|
||||
|
||||
if (component.children().size() != 1) return true;
|
||||
|
||||
if (!(component.children().getFirst() instanceof TextComponent dataComponent)) return true;
|
||||
|
||||
final String data = dataComponent.content();
|
||||
|
||||
try {
|
||||
final byte[] decrypted = decrypt(Base64.getDecoder().decode(data));
|
||||
|
||||
final Pair<PlayerEntry, Packet> deserialized = deserialize(decrypted);
|
||||
|
||||
if (deserialized == null) return false;
|
||||
|
||||
final PlayerEntry player = deserialized.getKey();
|
||||
final Packet packet = deserialized.getValue();
|
||||
|
||||
handlePacket(player, packet);
|
||||
|
||||
// isAuthenticating = false;
|
||||
//
|
||||
// bot.logger.log(
|
||||
// LogType.AUTH,
|
||||
// Component
|
||||
// .text("Player has been verified")
|
||||
// .color(NamedTextColor.GREEN)
|
||||
// );
|
||||
//
|
||||
// final PlayerEntry target = bot.players.getEntry(bot.config.ownerName);
|
||||
//
|
||||
// if (target == null) return false; // sad :(
|
||||
//
|
||||
// bot.chat.tellraw(
|
||||
// Component
|
||||
// .text("You have been verified")
|
||||
// .color(NamedTextColor.GREEN),
|
||||
// target.profile.getId()
|
||||
// );
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handlePacket (PlayerEntry player, Packet packet) {
|
||||
if (packet instanceof ServerboundHandshakePacket t_packet) handlePacket(player, t_packet);
|
||||
else if (packet instanceof ServerboundRunCoreCommandPacket t_packet) handlePacket(player, t_packet);
|
||||
|
||||
for (Listener listener : listeners) listener.packetReceived(player, packet);
|
||||
}
|
||||
|
||||
private void handlePacket (PlayerEntry player, ServerboundHandshakePacket ignoredPacket) {
|
||||
connectedPlayers.remove(player);
|
||||
|
||||
connectedPlayers.add(player);
|
||||
|
||||
send(player, new ClientboundSuccessfulHandshakePacket());
|
||||
}
|
||||
|
||||
private void handlePacket (PlayerEntry player, ServerboundRunCoreCommandPacket packet) {
|
||||
final CompletableFuture<Component> future = bot.core.runTracked(packet.command);
|
||||
|
||||
if (future == null) {
|
||||
send(
|
||||
player,
|
||||
new ClientboundCoreOutputPacket(
|
||||
packet.runID,
|
||||
Component.empty()
|
||||
)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
future.thenApply(output -> {
|
||||
send(
|
||||
player,
|
||||
new ClientboundCoreOutputPacket(
|
||||
packet.runID,
|
||||
output
|
||||
)
|
||||
);
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playerLeft (PlayerEntry target) {
|
||||
if (!connectedPlayers.contains(target)) return;
|
||||
|
||||
connectedPlayers.remove(target);
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
default void packetReceived (PlayerEntry player, Packet packet) {}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue