From f9e78d96e9264384bb7d88176a86aa032047e669 Mon Sep 17 00:00:00 2001 From: Verox001 Date: Wed, 14 May 2025 17:36:23 +0200 Subject: [PATCH] Finished Config and most of the features --- .../java/com/cimeyclust/ezcheat/Config.java | 39 +---- .../java/com/cimeyclust/ezcheat/Ezcheat.java | 155 +++++++++++------- .../cimeyclust/ezcheat/ModHashesPacket.java | 44 +++++ 3 files changed, 146 insertions(+), 92 deletions(-) create mode 100644 src/main/java/com/cimeyclust/ezcheat/ModHashesPacket.java diff --git a/src/main/java/com/cimeyclust/ezcheat/Config.java b/src/main/java/com/cimeyclust/ezcheat/Config.java index 01cce51..bc6b3dc 100644 --- a/src/main/java/com/cimeyclust/ezcheat/Config.java +++ b/src/main/java/com/cimeyclust/ezcheat/Config.java @@ -1,51 +1,24 @@ package com.cimeyclust.ezcheat; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; import net.minecraftforge.common.ForgeConfigSpec; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.event.config.ModConfigEvent; -import net.minecraftforge.registries.ForgeRegistries; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -// An example config class. This is not required, but it's a good idea to have one to keep your config organized. -// Demonstrates how to use Forge's config APIs @Mod.EventBusSubscriber(modid = Ezcheat.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) public class Config { private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); - private static final ForgeConfigSpec.BooleanValue LOG_DIRT_BLOCK = BUILDER.comment("Whether to log the dirt block on common setup").define("logDirtBlock", true); + public static final ForgeConfigSpec.ConfigValue MOD_ENDPOINT_URL = BUILDER + .comment("Endpoint URL used by the Ezcheat mod") + .define("modEndpointUrl", "https://ezcheat.cimeyclust.com/api/mods"); - private static final ForgeConfigSpec.IntValue MAGIC_NUMBER = BUILDER.comment("A magic number").defineInRange("magicNumber", 42, 0, Integer.MAX_VALUE); + public static final ForgeConfigSpec SPEC = BUILDER.build(); - public static final ForgeConfigSpec.ConfigValue MAGIC_NUMBER_INTRODUCTION = BUILDER.comment("What you want the introduction message to be for the magic number").define("magicNumberIntroduction", "The magic number is... "); - - // a list of strings that are treated as resource locations for items - private static final ForgeConfigSpec.ConfigValue> ITEM_STRINGS = BUILDER.comment("A list of items to log on common setup.").defineListAllowEmpty("items", List.of("minecraft:iron_ingot"), Config::validateItemName); - - static final ForgeConfigSpec SPEC = BUILDER.build(); - - public static boolean logDirtBlock; - public static int magicNumber; - public static String magicNumberIntroduction; - public static Set items; - - private static boolean validateItemName(final Object obj) { - return obj instanceof final String itemName && ForgeRegistries.ITEMS.containsKey(new ResourceLocation(itemName)); - } + public static String modEndpointUrl; @SubscribeEvent static void onLoad(final ModConfigEvent event) { - logDirtBlock = LOG_DIRT_BLOCK.get(); - magicNumber = MAGIC_NUMBER.get(); - magicNumberIntroduction = MAGIC_NUMBER_INTRODUCTION.get(); - - // convert the list of strings into a set of items - items = ITEM_STRINGS.get().stream().map(itemName -> ForgeRegistries.ITEMS.getValue(new ResourceLocation(itemName))).collect(Collectors.toSet()); + modEndpointUrl = MOD_ENDPOINT_URL.get(); } } diff --git a/src/main/java/com/cimeyclust/ezcheat/Ezcheat.java b/src/main/java/com/cimeyclust/ezcheat/Ezcheat.java index a8f405a..29fd054 100644 --- a/src/main/java/com/cimeyclust/ezcheat/Ezcheat.java +++ b/src/main/java/com/cimeyclust/ezcheat/Ezcheat.java @@ -1,8 +1,14 @@ package com.cimeyclust.ezcheat; +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; import com.mojang.logging.LogUtils; import net.minecraft.client.Minecraft; import net.minecraft.core.registries.Registries; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.food.FoodProperties; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.CreativeModeTab; @@ -18,90 +24,118 @@ import net.minecraftforge.event.BuildCreativeModeTabContentsEvent; import net.minecraftforge.event.server.ServerStartingEvent; import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.ModLoadingContext; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo; +import net.minecraftforge.forgespi.language.IModInfo; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.simple.SimpleChannel; import net.minecraftforge.registries.DeferredRegister; import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.RegistryObject; +import org.apache.commons.codec.digest.DigestUtils; import org.slf4j.Logger; -// The value here should match an entry in the META-INF/mods.toml file +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Type; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + @Mod(Ezcheat.MODID) public class Ezcheat { - - // Define mod id in a common place for everything to reference public static final String MODID = "ezcheat"; + // Directly reference a slf4j logger private static final Logger LOGGER = LogUtils.getLogger(); - // Create a Deferred Register to hold Blocks which will all be registered under the "ezcheat" namespace - public static final DeferredRegister BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MODID); - // Create a Deferred Register to hold Items which will all be registered under the "ezcheat" namespace - public static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MODID); - // Create a Deferred Register to hold CreativeModeTabs which will all be registered under the "ezcheat" namespace - public static final DeferredRegister CREATIVE_MODE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, MODID); - // Creates a new Block with the id "ezcheat:example_block", combining the namespace and path - public static final RegistryObject EXAMPLE_BLOCK = BLOCKS.register("example_block", () -> new Block(BlockBehaviour.Properties.of().mapColor(MapColor.STONE))); - // Creates a new BlockItem with the id "ezcheat:example_block", combining the namespace and path - public static final RegistryObject EXAMPLE_BLOCK_ITEM = ITEMS.register("example_block", () -> new BlockItem(EXAMPLE_BLOCK.get(), new Item.Properties())); + public static final String PROTOCOL_VERSION = "1"; + public static final SimpleChannel NETWORK = NetworkRegistry.newSimpleChannel( + new ResourceLocation(MODID, "main"), + () -> PROTOCOL_VERSION, PROTOCOL_VERSION::equals, PROTOCOL_VERSION::equals + ); - // Creates a new food item with the id "ezcheat:example_id", nutrition 1 and saturation 2 - public static final RegistryObject EXAMPLE_ITEM = ITEMS.register("example_item", () -> new Item(new Item.Properties().food(new FoodProperties.Builder().alwaysEat().nutrition(1).saturationMod(2f).build()))); + public static Map getInstalledModHashes() { + Map modHashes = new HashMap<>(); - // Creates a creative tab with the id "ezcheat:example_tab" for the example item, that is placed after the combat tab - public static final RegistryObject EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab", () -> CreativeModeTab.builder().withTabsBefore(CreativeModeTabs.COMBAT).icon(() -> EXAMPLE_ITEM.get().getDefaultInstance()).displayItems((parameters, output) -> { - output.accept(EXAMPLE_ITEM.get()); // Add the example item to the tab. For your own tabs, this method is preferred over the event - }).build()); + for (IModInfo mod : ModList.get().getMods()) { + Optional modFile = Optional.ofNullable(mod.getOwningFile().getFile().getFilePath()); + modFile.ifPresent(path -> { + try (InputStream in = Files.newInputStream(path)) { + String hash = DigestUtils.sha256Hex(in); // Apache Commons Codec + modHashes.put(mod.getModId(), hash); + } catch (IOException e) { + LOGGER.error("Failed to read mod file for {}: {}", mod.getModId(), e.getMessage()); + } + }); + } + + return modHashes; + } + + public static void handlePlayerModHashes(ServerPlayer player, Map modHashes) { + // Request an external whitelist + MinecraftServer server = player.getServer(); + if (server == null) return; + + server.execute(() -> { + try { + URL url = new URL(Config.modEndpointUrl); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + + InputStream in = conn.getInputStream(); + String response = new String(in.readAllBytes()); + in.close(); + + Gson gson = new Gson(); + Type type = new TypeToken>() {}.getType(); + Map allowedMods = gson.fromJson(response, type); + + StringBuilder unallowedMods = new StringBuilder("Unallowed mods: "); + for (Map.Entry entry : modHashes.entrySet()) { + String modid = entry.getKey(); + String hash = entry.getValue(); + + if (!allowedMods.containsKey(modid) || !allowedMods.get(modid).equalsIgnoreCase(hash)) { + unallowedMods.append(modid).append(", "); + } + } + + if (!unallowedMods.isEmpty()) { + unallowedMods.setLength(unallowedMods.length() - 2); // Remove last comma and space + player.connection.disconnect(Component.literal(unallowedMods.toString())); + } + } catch (IOException e) { + Ezcheat.LOGGER.error("Modprüfung fehlgeschlagen", e); + } + }); + } public Ezcheat() { - IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); - - // Register the commonSetup method for modloading - modEventBus.addListener(this::commonSetup); - - // Register the Deferred Register to the mod event bus so blocks get registered - BLOCKS.register(modEventBus); - // Register the Deferred Register to the mod event bus so items get registered - ITEMS.register(modEventBus); - // Register the Deferred Register to the mod event bus so tabs get registered - CREATIVE_MODE_TABS.register(modEventBus); - - // Register ourselves for server and other game events we are interested in - MinecraftForge.EVENT_BUS.register(this); - - // Register the item to a creative tab - modEventBus.addListener(this::addCreative); - - // Register our mod's ForgeConfigSpec so that Forge can create and load the config file for us ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, Config.SPEC); - } - private void commonSetup(final FMLCommonSetupEvent event) { - // Some common setup code - LOGGER.info("HELLO FROM COMMON SETUP"); - LOGGER.info("DIRT BLOCK >> {}", ForgeRegistries.BLOCKS.getKey(Blocks.DIRT)); - - if (Config.logDirtBlock) LOGGER.info("DIRT BLOCK >> {}", ForgeRegistries.BLOCKS.getKey(Blocks.DIRT)); - - LOGGER.info(Config.magicNumberIntroduction + Config.magicNumber); - - Config.items.forEach((item) -> LOGGER.info("ITEM >> {}", item.toString())); - } - - // Add the example block item to the building blocks tab - private void addCreative(BuildCreativeModeTabContentsEvent event) { - if (event.getTabKey() == CreativeModeTabs.BUILDING_BLOCKS) event.accept(EXAMPLE_BLOCK_ITEM); + NETWORK.registerMessage(0, ModHashesPacket.class, + ModHashesPacket::encode, + ModHashesPacket::decode, + ModHashesPacket::handle + ); } // You can use SubscribeEvent and let the Event Bus discover methods to call @SubscribeEvent public void onServerStarting(ServerStartingEvent event) { - // Do something when the server starts - LOGGER.info("HELLO from server starting"); + } // You can use EventBusSubscriber to automatically register all static methods in the class annotated with @SubscribeEvent @@ -110,9 +144,12 @@ public class Ezcheat { @SubscribeEvent public static void onClientSetup(FMLClientSetupEvent event) { - // Some client setup code - LOGGER.info("HELLO FROM CLIENT SETUP"); - LOGGER.info("MINECRAFT NAME >> {}", Minecraft.getInstance().getUser().getName()); + event.enqueueWork(() -> { + if (Minecraft.getInstance().getConnection() != null) { + Map modHashes = Ezcheat.getInstalledModHashes(); + Ezcheat.NETWORK.sendToServer(new ModHashesPacket(modHashes)); + } + }); } } } diff --git a/src/main/java/com/cimeyclust/ezcheat/ModHashesPacket.java b/src/main/java/com/cimeyclust/ezcheat/ModHashesPacket.java new file mode 100644 index 0000000..92e7a0a --- /dev/null +++ b/src/main/java/com/cimeyclust/ezcheat/ModHashesPacket.java @@ -0,0 +1,44 @@ +package com.cimeyclust.ezcheat; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +public class ModHashesPacket { + private final Map hashes; + + public ModHashesPacket(Map hashes) { + this.hashes = hashes; + } + + public static void encode(ModHashesPacket msg, FriendlyByteBuf buf) { + buf.writeInt(msg.hashes.size()); + msg.hashes.forEach((modid, hash) -> { + buf.writeUtf(modid); + buf.writeUtf(hash); + }); + } + + public static ModHashesPacket decode(FriendlyByteBuf buf) { + int size = buf.readInt(); + Map hashes = new HashMap<>(); + for (int i = 0; i < size; i++) { + hashes.put(buf.readUtf(), buf.readUtf()); + } + return new ModHashesPacket(hashes); + } + + public static void handle(ModHashesPacket msg, Supplier ctx) { + ctx.get().enqueueWork(() -> { + ServerPlayer player = ctx.get().getSender(); + if (player != null) { + Ezcheat.handlePlayerModHashes(player, msg.hashes); + } + }); + ctx.get().setPacketHandled(true); + } +}