├── images ├── gui.png ├── enchant.png ├── slots.png ├── enchant_2.png ├── enchant_3.png ├── packet_option.png ├── click_slot_packet.png ├── button_click_packet.png ├── click_slot_actions.png ├── click_slot_example.png └── click_slot_example_2.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src └── main │ ├── resources │ ├── assets │ │ └── ui_utils │ │ │ ├── icon.png │ │ │ └── textures │ │ │ └── gui │ │ │ └── sprites │ │ │ ├── update.png │ │ │ └── update_selected.png │ ├── ui_utils.accesswidener │ ├── ui_utils.mixins.json │ └── fabric.mod.json │ └── java │ └── com │ └── ui_utils │ ├── mixin │ ├── accessor │ │ ├── ScreenAccessor.java │ │ └── ClientConnectionAccessor.java │ ├── SleepingChatScreenMixin.java │ ├── ChatScreenMixin.java │ ├── MultiplayerScreenMixin.java │ ├── SignEditScreenMixin.java │ ├── ClientConnectionMixin.java │ ├── ClientCommonNetworkHandlerMixin.java │ ├── TitleScreenMixin.java │ ├── BookScreenMixin.java │ ├── BookEditScreenMixin.java │ ├── ScreenMixin.java │ └── HandledScreenMixin.java │ ├── GithubRelease.java │ ├── SharedVariables.java │ ├── gui │ └── UpdateScreen.java │ ├── UpdateUtils.java │ └── MainClient.java ├── settings.gradle ├── gradle.properties ├── TERMS.md ├── LICENSE ├── gradlew.bat ├── .gitignore ├── README.md └── gradlew /images/gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coderx-Gamer/ui-utils/HEAD/images/gui.png -------------------------------------------------------------------------------- /images/enchant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coderx-Gamer/ui-utils/HEAD/images/enchant.png -------------------------------------------------------------------------------- /images/slots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coderx-Gamer/ui-utils/HEAD/images/slots.png -------------------------------------------------------------------------------- /images/enchant_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coderx-Gamer/ui-utils/HEAD/images/enchant_2.png -------------------------------------------------------------------------------- /images/enchant_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coderx-Gamer/ui-utils/HEAD/images/enchant_3.png -------------------------------------------------------------------------------- /images/packet_option.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coderx-Gamer/ui-utils/HEAD/images/packet_option.png -------------------------------------------------------------------------------- /images/click_slot_packet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coderx-Gamer/ui-utils/HEAD/images/click_slot_packet.png -------------------------------------------------------------------------------- /images/button_click_packet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coderx-Gamer/ui-utils/HEAD/images/button_click_packet.png -------------------------------------------------------------------------------- /images/click_slot_actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coderx-Gamer/ui-utils/HEAD/images/click_slot_actions.png -------------------------------------------------------------------------------- /images/click_slot_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coderx-Gamer/ui-utils/HEAD/images/click_slot_example.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coderx-Gamer/ui-utils/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /images/click_slot_example_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coderx-Gamer/ui-utils/HEAD/images/click_slot_example_2.png -------------------------------------------------------------------------------- /src/main/resources/assets/ui_utils/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coderx-Gamer/ui-utils/HEAD/src/main/resources/assets/ui_utils/icon.png -------------------------------------------------------------------------------- /src/main/resources/assets/ui_utils/textures/gui/sprites/update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coderx-Gamer/ui-utils/HEAD/src/main/resources/assets/ui_utils/textures/gui/sprites/update.png -------------------------------------------------------------------------------- /src/main/resources/assets/ui_utils/textures/gui/sprites/update_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coderx-Gamer/ui-utils/HEAD/src/main/resources/assets/ui_utils/textures/gui/sprites/update_selected.png -------------------------------------------------------------------------------- /src/main/resources/ui_utils.accesswidener: -------------------------------------------------------------------------------- 1 | accessWidener v1 named 2 | 3 | accessible method net/minecraft/client/gui/screen/Screen addDrawableChild (Lnet/minecraft/client/gui/Element;)Lnet/minecraft/client/gui/Element; -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { 4 | name = 'Fabric' 5 | url = 'https://maven.fabricmc.net/' 6 | } 7 | mavenCentral() 8 | gradlePluginPortal() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists -------------------------------------------------------------------------------- /src/main/java/com/ui_utils/mixin/accessor/ScreenAccessor.java: -------------------------------------------------------------------------------- 1 | package com.ui_utils.mixin.accessor; 2 | 3 | import net.minecraft.client.font.TextRenderer; 4 | import net.minecraft.client.gui.screen.Screen; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(Screen.class) 9 | public interface ScreenAccessor { 10 | @Accessor 11 | TextRenderer getTextRenderer(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/ui_utils/mixin/accessor/ClientConnectionAccessor.java: -------------------------------------------------------------------------------- 1 | package com.ui_utils.mixin.accessor; 2 | 3 | import io.netty.channel.Channel; 4 | import net.minecraft.network.ClientConnection; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(ClientConnection.class) 9 | public interface ClientConnectionAccessor { 10 | @Accessor 11 | Channel getChannel(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/ui_utils/GithubRelease.java: -------------------------------------------------------------------------------- 1 | package com.ui_utils; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | public class GithubRelease { 6 | @SerializedName("tag_name") 7 | private String tagName; 8 | 9 | public String getTagName() { 10 | return tagName; 11 | } 12 | 13 | @SerializedName("name") 14 | private String name; 15 | 16 | public String getMcVersion() { 17 | // in name, return the text within the () 18 | return name.substring(name.indexOf("(") + 1, name.indexOf(")")); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Done to increase the memory available to gradle. 2 | org.gradle.jvmargs=-Xmx4G 3 | org.gradle.parallel=true 4 | 5 | # Fabric Properties 6 | # check these on https://fabricmc.net/develop 7 | minecraft_version=1.21.7 8 | yarn_mappings=1.21.7+build.6 9 | loader_version=0.16.14 10 | 11 | # Fabric API 12 | fabric_version=0.128.2+1.21.7 13 | # Mod Properties 14 | mod_version = 2.4.0 15 | 16 | #x.y.z 17 | 18 | #x -- major mc release 19 | #y -- minor mc release 20 | #z -- ui utils release 21 | 22 | maven_group = com.ui_utils 23 | archives_base_name = ui_utils 24 | -------------------------------------------------------------------------------- /src/main/resources/ui_utils.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "com.ui_utils.mixin", 5 | "compatibilityLevel": "JAVA_17", 6 | "client": [ 7 | "BookEditScreenMixin", 8 | "BookScreenMixin", 9 | "ChatScreenMixin", 10 | "ClientCommonNetworkHandlerMixin", 11 | "ClientConnectionMixin", 12 | "HandledScreenMixin", 13 | "MultiplayerScreenMixin", 14 | "ScreenMixin", 15 | "SignEditScreenMixin", 16 | "SleepingChatScreenMixin", 17 | "TitleScreenMixin", 18 | "accessor.ClientConnectionAccessor", 19 | "accessor.ScreenAccessor" 20 | ], 21 | "injectors": { 22 | "defaultRequire": 1 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/ui_utils/SharedVariables.java: -------------------------------------------------------------------------------- 1 | package com.ui_utils; 2 | 3 | import net.minecraft.client.gui.screen.Screen; 4 | import net.minecraft.network.packet.Packet; 5 | import net.minecraft.screen.ScreenHandler; 6 | 7 | import java.util.ArrayList; 8 | 9 | public class SharedVariables { 10 | public static boolean sendUIPackets = true; 11 | public static boolean delayUIPackets = false; 12 | public static boolean shouldEditSign = true; 13 | 14 | public static ArrayList> delayedUIPackets = new ArrayList<>(); 15 | 16 | public static Screen storedScreen = null; 17 | public static ScreenHandler storedScreenHandler = null; 18 | 19 | public static boolean enabled = true; 20 | public static boolean bypassResourcePack = false; 21 | public static boolean resourcePackForceDeny = false; 22 | } 23 | -------------------------------------------------------------------------------- /TERMS.md: -------------------------------------------------------------------------------- 1 | # Terms of Use for UI-Utils 2 | 3 | ## 1. Acceptance of Terms 4 | By using UI-Utils, you agree to these Terms of Use. If you do not agree, please do not use the mod. 5 | 6 | ## 2. Usage Restrictions 7 | You are prohibited from using UI-Utils on any plugins that you have not created or do not have explicit permission to modify. 8 | 9 | ## 3. Misrepresentation 10 | UI-Utils is not advertised as a cheat or hack. Any claims to the contrary are misrepresentations. 11 | 12 | ## 4. Intended Use 13 | UI-Utils is intended solely for debugging purposes on plugins you created and have explicit permission to use it on. 14 | 15 | ## 5. Changes to Terms 16 | These Terms of Use may be updated from time to time. Continued use of the mod constitutes acceptance of any changes. 17 | 18 | ## 6. License 19 | You may not violate the license, by reposting on blacklisted sites, or using in unauthorized situations. 20 | 21 | ## 7. Contact Information 22 | For any inquiries regarding these Terms of Use, please contact mrbreaknfix@gmail.com. 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | UI Utils License Information 2 | 3 | This mod is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License (CC BY-NC-SA 4.0). 4 | 5 | You can find the full text of the CC BY-NC-SA 4.0 license at: 6 | https://creativecommons.org/licenses/by-nc-sa/4.0/ 7 | 8 | Additional Distribution Restrictions: 9 | 10 | 1. General Restriction: 11 | - UI Utils may not be uploaded, distributed, or made available on any mod hosting websites, online platforms, or similar services without the explicit prior written permission of the original authors, MrBreakNFix and Coderx_Gamer. 12 | - Specifically, this mod is not authorized for distribution on websites such as 9minecraft.net, or any websites listed on https://stopmodreposts.org/. 13 | 14 | 2. Redistribution: 15 | - To request permission for distribution or for any other inquiries, please contact: 16 | - MrBreakNFix at mrbreaknfix@gmail.com 17 | - Coderx_Gamer at coderx@mailfence.com 18 | 19 | Notice: 20 | - All rights are reserved by the authors. Unauthorized distribution of this mod outside the terms specified herein is prohibited and may result in legal action. 21 | 22 | For permissions and further information, please contact: 23 | - MrBreakNFix at mrbreaknfix@gmail.com 24 | - Coderx_Gamer at coderx@mailfence.com 25 | -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "ui-utils", 4 | "version": "${version}", 5 | 6 | "name": "UI Utils", 7 | "description": "Plugin debugging mod.", 8 | "authors": [ 9 | "Coderx_Gamer", 10 | "MrBreakNFix" 11 | ], 12 | "contact": { 13 | "homepage": "https://ui-utils.com", 14 | "sources": "https://github.com/Coderx-Gamer/ui-utils" 15 | }, 16 | 17 | "license": [ 18 | "CC BY-NC-SA 4.0", 19 | "The mod may not be uploaded to any mod hosting websites or other platforms, including but not limited to 9minecraft.net, without explicit written permission from MrBreakNFix and Coderx_Gamer. All rights reserved." 20 | ], 21 | "icon": "assets/ui_utils/icon.png", 22 | 23 | "environment": "client", 24 | "entrypoints": { 25 | "client": [ 26 | "com.ui_utils.MainClient" 27 | ] 28 | }, 29 | "mixins": [ 30 | "ui_utils.mixins.json" 31 | ], 32 | 33 | "depends": { 34 | "fabricloader": ">=0.16.9", 35 | "fabric": "*", 36 | "minecraft": ["~1.21.6"], 37 | "java": ">=17" 38 | }, 39 | 40 | "accessWidener": "ui_utils.accesswidener", 41 | 42 | "suggests": { 43 | "another-mod": "*" 44 | }, 45 | 46 | "breaks": { 47 | "modernfix": "*", 48 | "wurst": "*", 49 | "essential": "*" 50 | }, 51 | 52 | "custom": { 53 | "modmenu:clientsideOnly": true, 54 | "modmenu:api": false 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/ui_utils/mixin/SleepingChatScreenMixin.java: -------------------------------------------------------------------------------- 1 | package com.ui_utils.mixin; 2 | 3 | import net.minecraft.client.gui.screen.Screen; 4 | import net.minecraft.client.gui.screen.SleepingChatScreen; 5 | import net.minecraft.client.gui.widget.ButtonWidget; 6 | import net.minecraft.text.Text; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 11 | import com.ui_utils.SharedVariables; 12 | 13 | @Mixin(SleepingChatScreen.class) 14 | public class SleepingChatScreenMixin extends Screen { 15 | protected SleepingChatScreenMixin(Text title) { 16 | super(title); 17 | } 18 | 19 | // called when SleepingChatScreen is created 20 | @Inject(at = @At("TAIL"), method = "init") 21 | public void init(CallbackInfo ci) { 22 | // register "client wake up" button for SleepingChatScreen if ui utils is enabled 23 | if (SharedVariables.enabled) { 24 | addDrawableChild(ButtonWidget.builder(Text.of("Client wake up"), (button) -> { 25 | // wakes the player up client-side 26 | if (this.client != null && this.client.player != null) { 27 | this.client.player.wakeUp(); 28 | this.client.setScreen(null); 29 | } 30 | }).width(115).position(5, 5).build()); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/ui_utils/mixin/ChatScreenMixin.java: -------------------------------------------------------------------------------- 1 | package com.ui_utils.mixin; 2 | 3 | import net.minecraft.client.MinecraftClient; 4 | import net.minecraft.client.gui.screen.ChatScreen; 5 | import net.minecraft.text.Text; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | import com.ui_utils.MainClient; 11 | import com.ui_utils.SharedVariables; 12 | 13 | @Mixin(ChatScreen.class) 14 | public class ChatScreenMixin { 15 | @Inject(at = @At("HEAD"), method = "sendMessage", cancellable = true) 16 | public void sendMessage(String chatText, boolean addToHistory, CallbackInfo ci) { 17 | if (chatText.equals("^toggleuiutils")) { 18 | SharedVariables.enabled = !SharedVariables.enabled; 19 | if (MinecraftClient.getInstance().player != null) { 20 | MinecraftClient.getInstance().player.sendMessage(Text.of("UI-Utils is now " + (SharedVariables.enabled ? "enabled" : "disabled") + "."), false); 21 | } else { 22 | MainClient.LOGGER.warn("Minecraft player was nulling while enabling / disabling UI Utils."); 23 | } 24 | MinecraftClient.getInstance().inGameHud.getChatHud().addToMessageHistory(chatText); 25 | MinecraftClient.getInstance().setScreen(null); 26 | ci.cancel(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/ui_utils/gui/UpdateScreen.java: -------------------------------------------------------------------------------- 1 | package com.ui_utils.gui; 2 | 3 | import net.minecraft.client.gui.screen.Screen; 4 | import net.minecraft.client.gui.widget.ButtonWidget; 5 | import net.minecraft.client.gui.widget.TextWidget; 6 | import net.minecraft.text.Text; 7 | 8 | public class UpdateScreen extends Screen { 9 | 10 | public UpdateScreen(Text title) { 11 | super(title); 12 | } 13 | 14 | @Override 15 | protected void init() { 16 | super.init(); 17 | Text message1 = Text.of("In order to update UI-Utils, first quit the game then"); 18 | Text message2 = Text.of("delete the old UI-Utils jar file, and replace it with the new one you got on the website."); 19 | int centerX = this.width / 2; 20 | 21 | this.addDrawableChild(new TextWidget(centerX - textRenderer.getWidth(message1) / 2, 80, textRenderer.getWidth(message1), 20, message1, this.textRenderer)); 22 | this.addDrawableChild(new TextWidget(centerX - textRenderer.getWidth(message2) / 2, 95, textRenderer.getWidth(message2), 20, Text.of(message2), this.textRenderer)); 23 | 24 | int quitX = centerX - 85; 25 | int backX = centerX + 5; 26 | 27 | this.addDrawableChild(ButtonWidget.builder(Text.of("Quit"), (button) -> { 28 | this.client.stop(); 29 | }).width(80).position(quitX, 145).build()); 30 | 31 | this.addDrawableChild(ButtonWidget.builder(Text.of("Back"), (button) -> { 32 | this.client.setScreen(null); 33 | }).width(80).position(backX, 145).build()); 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/main/java/com/ui_utils/mixin/MultiplayerScreenMixin.java: -------------------------------------------------------------------------------- 1 | package com.ui_utils.mixin; 2 | 3 | import net.minecraft.client.gui.screen.Screen; 4 | import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; 5 | import net.minecraft.client.gui.widget.ButtonWidget; 6 | import net.minecraft.text.Text; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 11 | import com.ui_utils.SharedVariables; 12 | 13 | @Mixin(MultiplayerScreen.class) 14 | public class MultiplayerScreenMixin extends Screen { 15 | private MultiplayerScreenMixin() { 16 | super(null); 17 | } 18 | 19 | @Inject(at = @At("TAIL"), method = "init") 20 | public void init(CallbackInfo ci) { 21 | if (SharedVariables.enabled) { 22 | // Create "Bypass Resource Pack" option 23 | this.addDrawableChild(ButtonWidget.builder(Text.of("Bypass Resource Pack: " + (SharedVariables.bypassResourcePack ? "ON" : "OFF")), (button) -> { 24 | SharedVariables.bypassResourcePack = !SharedVariables.bypassResourcePack; 25 | button.setMessage(Text.of("Bypass Resource Pack: " + (SharedVariables.bypassResourcePack ? "ON" : "OFF"))); 26 | }).width(160).position(this.width - 170, this.height - 50).build()); 27 | 28 | // Create "Force Deny" option 29 | this.addDrawableChild(ButtonWidget.builder(Text.of("Force Deny: " + (SharedVariables.resourcePackForceDeny ? "ON" : "OFF")), (button) -> { 30 | SharedVariables.resourcePackForceDeny = !SharedVariables.resourcePackForceDeny; 31 | button.setMessage(Text.of("Force Deny: " + (SharedVariables.resourcePackForceDeny ? "ON" : "OFF"))); 32 | }).width(160).position(this.width - 170, this.height - 25).build()); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/ui_utils/mixin/SignEditScreenMixin.java: -------------------------------------------------------------------------------- 1 | package com.ui_utils.mixin; 2 | 3 | import net.minecraft.client.MinecraftClient; 4 | import net.minecraft.client.gui.screen.Screen; 5 | import net.minecraft.client.gui.screen.ingame.SignEditScreen; 6 | import net.minecraft.client.gui.widget.ButtonWidget; 7 | import net.minecraft.text.Text; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Unique; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | import com.ui_utils.SharedVariables; 14 | 15 | @Mixin(SignEditScreen.class) 16 | public class SignEditScreenMixin extends Screen { 17 | protected SignEditScreenMixin(Text title) { 18 | super(title); 19 | } 20 | 21 | @Unique 22 | private static final MinecraftClient mc = MinecraftClient.getInstance(); 23 | 24 | // called when any sign edit screen is created 25 | @Inject(at = @At("TAIL"), method = "init") 26 | public void init(CallbackInfo ci) { 27 | 28 | // register "close without packet" button for SignEditScreen if ui utils is enabled 29 | if (SharedVariables.enabled) { 30 | addDrawableChild(ButtonWidget.builder(Text.of("Close without packet"), (button) -> { 31 | // disables sign editing and closes the current gui without sending a packet 32 | SharedVariables.shouldEditSign = false; 33 | mc.setScreen(null); 34 | }).width(115).position(5, 5).build()); 35 | addDrawableChild(ButtonWidget.builder(Text.of("Disconnect"), (button) -> { 36 | if (mc.getNetworkHandler() != null) { 37 | mc.getNetworkHandler().getConnection().disconnect(Text.of("Disconnecting (UI-UTILS)")); 38 | } 39 | }).width(115).position(5, 35).build()); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/ui_utils/mixin/ClientConnectionMixin.java: -------------------------------------------------------------------------------- 1 | package com.ui_utils.mixin; 2 | 3 | import io.netty.channel.ChannelFutureListener; 4 | import net.minecraft.network.ClientConnection; 5 | import net.minecraft.network.packet.Packet; 6 | import net.minecraft.network.packet.c2s.play.ButtonClickC2SPacket; 7 | import net.minecraft.network.packet.c2s.play.ClickSlotC2SPacket; 8 | import net.minecraft.network.packet.c2s.play.UpdateSignC2SPacket; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | import com.ui_utils.SharedVariables; 14 | 15 | @Mixin(ClientConnection.class) 16 | public class ClientConnectionMixin { 17 | 18 | // called when sending any packet 19 | @Inject(at = @At("HEAD"), method = "sendImmediately", cancellable = true) 20 | public void sendImmediately(Packet packet, ChannelFutureListener channelFutureListener, boolean flush, CallbackInfo ci) { 21 | // checks for if packets should be sent and if the packet is a gui related packet 22 | if (!SharedVariables.sendUIPackets && (packet instanceof ClickSlotC2SPacket || packet instanceof ButtonClickC2SPacket)) { 23 | ci.cancel(); 24 | return; 25 | } 26 | 27 | // checks for if packets should be delayed and if the packet is a gui related packet and is added to a list 28 | if (SharedVariables.delayUIPackets && (packet instanceof ClickSlotC2SPacket || packet instanceof ButtonClickC2SPacket)) { 29 | SharedVariables.delayedUIPackets.add(packet); 30 | ci.cancel(); 31 | } 32 | 33 | // cancels sign update packets if sign editing is disabled and re-enables sign editing 34 | if (!SharedVariables.shouldEditSign && (packet instanceof UpdateSignC2SPacket)) { 35 | SharedVariables.shouldEditSign = true; 36 | ci.cancel(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/ui_utils/mixin/ClientCommonNetworkHandlerMixin.java: -------------------------------------------------------------------------------- 1 | package com.ui_utils.mixin; 2 | 3 | import net.minecraft.client.MinecraftClient; 4 | import net.minecraft.client.network.ClientCommonNetworkHandler; 5 | import net.minecraft.network.packet.Packet; 6 | import net.minecraft.network.packet.c2s.common.ResourcePackStatusC2SPacket; 7 | import net.minecraft.network.packet.s2c.common.ResourcePackSendS2CPacket; 8 | import org.spongepowered.asm.mixin.Final; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.Shadow; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 14 | import com.ui_utils.MainClient; 15 | import com.ui_utils.SharedVariables; 16 | 17 | @Mixin(ClientCommonNetworkHandler.class) 18 | public abstract class ClientCommonNetworkHandlerMixin { 19 | @Shadow 20 | @Final 21 | protected MinecraftClient client; 22 | 23 | @Shadow 24 | public abstract void sendPacket(Packet packet); 25 | 26 | @Inject(at = @At("HEAD"), method = "onResourcePackSend", cancellable = true) 27 | public void onResourcePackSend(ResourcePackSendS2CPacket packet, CallbackInfo ci) { 28 | if (SharedVariables.bypassResourcePack && (packet.required() || SharedVariables.resourcePackForceDeny)) { 29 | this.sendPacket(new ResourcePackStatusC2SPacket(MinecraftClient.getInstance().getSession().getUuidOrNull(), ResourcePackStatusC2SPacket.Status.ACCEPTED)); 30 | this.sendPacket(new ResourcePackStatusC2SPacket(MinecraftClient.getInstance().getSession().getUuidOrNull(), ResourcePackStatusC2SPacket.Status.SUCCESSFULLY_LOADED)); 31 | MainClient.LOGGER.info( 32 | "[UI Utils]: Required Resource Pack Bypassed, Message: " + 33 | (packet.prompt().isEmpty() ? "" : packet.prompt().toString()) + 34 | ", URL: " + (packet.url() == null ? "" : packet.url()) 35 | ); 36 | ci.cancel(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/ui_utils/mixin/TitleScreenMixin.java: -------------------------------------------------------------------------------- 1 | package com.ui_utils.mixin; 2 | 3 | import net.minecraft.client.MinecraftClient; 4 | import net.minecraft.client.gui.screen.ButtonTextures; 5 | import net.minecraft.client.gui.screen.Screen; 6 | import net.minecraft.client.gui.screen.TitleScreen; 7 | import net.minecraft.client.gui.widget.ButtonWidget; 8 | import net.minecraft.client.gui.widget.TextWidget; 9 | import net.minecraft.client.gui.widget.TexturedButtonWidget; 10 | import net.minecraft.client.toast.SystemToast; 11 | import net.minecraft.client.toast.ToastManager; 12 | import net.minecraft.text.Text; 13 | import net.minecraft.util.Identifier; 14 | import org.spongepowered.asm.mixin.Mixin; 15 | import org.spongepowered.asm.mixin.injection.At; 16 | import org.spongepowered.asm.mixin.injection.Inject; 17 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 18 | import com.ui_utils.UpdateUtils; 19 | 20 | @Mixin(TitleScreen.class) 21 | public class TitleScreenMixin extends Screen { 22 | protected TitleScreenMixin(Text title) { 23 | super(title); 24 | } 25 | 26 | @Inject(at = @At("RETURN"), method = "init") 27 | private void onInitWidgetsNormal(CallbackInfo ci) { 28 | if (UpdateUtils.isOutdated) { 29 | if (!UpdateUtils.messageShown) { 30 | MinecraftClient client = MinecraftClient.getInstance(); 31 | ToastManager toastManager = client.getToastManager(); 32 | Text title = Text.of("UI-Utils " + UpdateUtils.version + " is out for " + UpdateUtils.mcVersion + "!"); 33 | Text description = Text.of("Download it from the top left corner!"); 34 | SystemToast.add(toastManager, new SystemToast.Type(30000L), title, description); 35 | UpdateUtils.messageShown = true; 36 | } 37 | 38 | Text message = Text.of("Download UI-Utils " + UpdateUtils.version + "!"); 39 | 40 | this.addDrawableChild(new TextWidget(40 - 15, 5, textRenderer.getWidth(message), textRenderer.fontHeight, message, textRenderer)); 41 | 42 | ButtonWidget downloadUpdateButton = new TexturedButtonWidget(5, 5 - 3, 43 | 15, 15, 44 | new ButtonTextures( 45 | Identifier.of("ui_utils", "update"), 46 | Identifier.of("ui_utils", "update_selected") 47 | ), 48 | (button) -> UpdateUtils.downloadUpdate(), 49 | Text.of("Download Update")); 50 | this.addDrawableChild(downloadUpdateButton); 51 | 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/ui_utils/mixin/BookScreenMixin.java: -------------------------------------------------------------------------------- 1 | package com.ui_utils.mixin; 2 | 3 | import net.minecraft.client.MinecraftClient; 4 | import net.minecraft.client.gui.screen.Screen; 5 | import net.minecraft.client.gui.screen.ingame.BookScreen; 6 | import net.minecraft.client.gui.widget.TextFieldWidget; 7 | import net.minecraft.text.Text; 8 | import org.lwjgl.glfw.GLFW; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.Unique; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 14 | import com.ui_utils.MainClient; 15 | import com.ui_utils.SharedVariables; 16 | 17 | import java.util.regex.Pattern; 18 | 19 | @Mixin(BookScreen.class) 20 | public class BookScreenMixin extends Screen { 21 | protected BookScreenMixin(Text title) { 22 | super(title); 23 | } 24 | @Unique 25 | private static final MinecraftClient mc = MinecraftClient.getInstance(); 26 | 27 | @Inject(at = @At("TAIL"), method = "init") 28 | public void init(CallbackInfo ci) { 29 | if (SharedVariables.enabled) { 30 | MainClient.createWidgets(mc, this); 31 | 32 | // create chat box 33 | TextFieldWidget addressField = new TextFieldWidget(textRenderer, 5, 245, 160, 20, Text.of("Chat ...")) { 34 | @Override 35 | public boolean keyPressed(int keyCode, int scanCode, int modifiers) { 36 | if (keyCode == GLFW.GLFW_KEY_ENTER) { 37 | if (this.getText().equals("^toggleuiutils")) { 38 | SharedVariables.enabled = !SharedVariables.enabled; 39 | if (mc.player != null) { 40 | mc.player.sendMessage(Text.of("UI-Utils is now " + (SharedVariables.enabled ? "enabled" : "disabled") + "."), false); 41 | } 42 | return false; 43 | } 44 | 45 | if (mc.getNetworkHandler() != null) { 46 | if (this.getText().startsWith("/")) { 47 | mc.getNetworkHandler().sendChatCommand(this.getText().replaceFirst(Pattern.quote("/"), "")); 48 | } else { 49 | mc.getNetworkHandler().sendChatMessage(this.getText()); 50 | } 51 | } else { 52 | MainClient.LOGGER.warn("Minecraft network handler (mc.getNetworkHandler()) was null while trying to send chat message from UI Utils."); 53 | } 54 | 55 | this.setText(""); 56 | } 57 | return super.keyPressed(keyCode, scanCode, modifiers); 58 | } 59 | }; 60 | addressField.setText(""); 61 | addressField.setMaxLength(255); 62 | 63 | this.addDrawableChild(addressField); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/ui_utils/mixin/BookEditScreenMixin.java: -------------------------------------------------------------------------------- 1 | package com.ui_utils.mixin; 2 | 3 | import net.minecraft.client.MinecraftClient; 4 | import net.minecraft.client.gui.screen.Screen; 5 | import net.minecraft.client.gui.screen.ingame.BookEditScreen; 6 | import net.minecraft.client.gui.widget.TextFieldWidget; 7 | import net.minecraft.text.Text; 8 | import org.lwjgl.glfw.GLFW; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.Unique; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 14 | import com.ui_utils.MainClient; 15 | import com.ui_utils.SharedVariables; 16 | 17 | import java.util.regex.Pattern; 18 | 19 | @Mixin(BookEditScreen.class) 20 | public class BookEditScreenMixin extends Screen { 21 | protected BookEditScreenMixin(Text title) { 22 | super(title); 23 | } 24 | @Unique 25 | private static final MinecraftClient mc = MinecraftClient.getInstance(); 26 | 27 | @Inject(at = @At("TAIL"), method = "init") 28 | public void init(CallbackInfo ci) { 29 | if (SharedVariables.enabled) { 30 | MainClient.createWidgets(mc, this); 31 | 32 | // create chat box 33 | TextFieldWidget addressField = new TextFieldWidget(textRenderer, 5, 245, 160, 20, Text.of("Chat ...")) { 34 | @Override 35 | public boolean keyPressed(int keyCode, int scanCode, int modifiers) { 36 | if (keyCode == GLFW.GLFW_KEY_ENTER) { 37 | if (this.getText().equals("^toggleuiutils")) { 38 | SharedVariables.enabled = !SharedVariables.enabled; 39 | if (mc.player != null) { 40 | mc.player.sendMessage(Text.of("UI-Utils is now " + (SharedVariables.enabled ? "enabled" : "disabled") + "."), false); 41 | } 42 | return false; 43 | } 44 | 45 | if (mc.getNetworkHandler() != null) { 46 | if (this.getText().startsWith("/")) { 47 | mc.getNetworkHandler().sendChatCommand(this.getText().replaceFirst(Pattern.quote("/"), "")); 48 | } else { 49 | mc.getNetworkHandler().sendChatMessage(this.getText()); 50 | } 51 | } else { 52 | MainClient.LOGGER.warn("Minecraft network handler (mc.getNetworkHandler()) was null while trying to send chat message from UI Utils."); 53 | } 54 | 55 | this.setText(""); 56 | } 57 | return super.keyPressed(keyCode, scanCode, modifiers); 58 | } 59 | }; 60 | addressField.setText(""); 61 | addressField.setMaxLength(255); 62 | 63 | this.addDrawableChild(addressField); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /src/main/java/com/ui_utils/UpdateUtils.java: -------------------------------------------------------------------------------- 1 | package com.ui_utils; 2 | 3 | import com.google.gson.Gson; 4 | import net.minecraft.client.MinecraftClient; 5 | import net.minecraft.text.Text; 6 | import com.ui_utils.gui.UpdateScreen; 7 | 8 | import java.awt.*; 9 | import java.io.IOException; 10 | import java.net.URI; 11 | import java.net.URISyntaxException; 12 | import java.net.http.HttpClient; 13 | import java.net.http.HttpRequest; 14 | import java.net.http.HttpResponse; 15 | import java.util.concurrent.Callable; 16 | import java.util.concurrent.ExecutorService; 17 | import java.util.concurrent.Executors; 18 | import java.util.concurrent.Future; 19 | import java.util.logging.Level; 20 | 21 | import static com.ui_utils.MainClient.getModVersion; 22 | 23 | public class UpdateUtils { 24 | 25 | public static boolean isOutdated; 26 | public static String version; 27 | public static String mcVersion; 28 | public static boolean messageShown; 29 | public static final String currentVersion = getModVersion("ui-utils"); 30 | 31 | public static void checkForUpdates() { 32 | ExecutorService executorService = Executors.newSingleThreadExecutor(); 33 | Callable task = () -> { 34 | HttpClient client = HttpClient.newHttpClient(); 35 | HttpRequest request = HttpRequest.newBuilder() 36 | .uri(URI.create("https://api.github.com/repos/Coderx-Gamer/ui-utils/releases/latest")) 37 | .header("Accept", "application/vnd.github.v3+json") 38 | .build(); 39 | 40 | try { 41 | HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); 42 | if (response.statusCode() == 200) { 43 | Gson gson = new Gson(); 44 | GithubRelease release = gson.fromJson(response.body(), GithubRelease.class); 45 | mcVersion = release.getMcVersion(); 46 | return release.getTagName(); 47 | } else { 48 | MainClient.LOGGER.error("Failed to fetch the latest version. Status code: " + response.statusCode()); 49 | return null; 50 | } 51 | } catch (IOException | InterruptedException e) { 52 | MainClient.LOGGER.error("Failed to fetch the latest version: " + e); 53 | return null; 54 | } 55 | }; 56 | 57 | Future future = executorService.submit(task); 58 | try { 59 | String latestVersion = future.get(); 60 | MainClient.LOGGER.info("Latest version: " + latestVersion + " Current version: " + currentVersion); 61 | version = latestVersion; 62 | if (latestVersion != null && !latestVersion.equals(currentVersion)) { 63 | isOutdated = true; 64 | } 65 | 66 | } catch (Exception e) { 67 | MainClient.LOGGER.error("Failed to check for updates: " + e); 68 | } finally { 69 | executorService.shutdown(); 70 | } 71 | } 72 | 73 | public static void downloadUpdate() { 74 | MainClient.LOGGER.info("Opening download link..."); 75 | try { 76 | if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { 77 | Desktop.getDesktop().browse(new URI("https://ui-utils.com?ref=ingame&cv=" + currentVersion)); 78 | } else { 79 | Runtime runtime = Runtime.getRuntime(); 80 | runtime.exec(new String[]{"xdg-open", "https://ui-utils.com?ref=ingame&cv=" + currentVersion}); 81 | } 82 | } catch (IOException | URISyntaxException e) { 83 | MainClient.LOGGER.info(e.getLocalizedMessage(), Level.SEVERE); 84 | } 85 | MinecraftClient.getInstance().setScreen(new UpdateScreen(Text.empty())); 86 | } 87 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/java,gradle,intellij 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=java,gradle,intellij 3 | 4 | ### Intellij ### 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 7 | 8 | # User-specific stuff 9 | .idea/**/workspace.xml 10 | .idea/**/tasks.xml 11 | .idea/**/usage.statistics.xml 12 | .idea/**/dictionaries 13 | .idea/**/shelf 14 | 15 | # AWS User-specific 16 | .idea/**/aws.xml 17 | 18 | # Generated files 19 | .idea/**/contentModel.xml 20 | 21 | # Sensitive or high-churn files 22 | .idea/**/dataSources/ 23 | .idea/**/dataSources.ids 24 | .idea/**/dataSources.local.xml 25 | .idea/**/sqlDataSources.xml 26 | .idea/**/dynamic.xml 27 | .idea/**/uiDesigner.xml 28 | .idea/**/dbnavigator.xml 29 | 30 | # Gradle 31 | .idea/**/gradle.xml 32 | .idea/**/libraries 33 | 34 | # Gradle and Maven with auto-import 35 | # When using Gradle or Maven with auto-import, you should exclude module files, 36 | # since they will be recreated, and may cause churn. Uncomment if using 37 | # auto-import. 38 | # .idea/artifacts 39 | # .idea/compiler.xml 40 | # .idea/jarRepositories.xml 41 | # .idea/modules.xml 42 | # .idea/*.iml 43 | # .idea/modules 44 | # *.iml 45 | # *.ipr 46 | 47 | # CMake 48 | cmake-build-*/ 49 | 50 | # Mongo Explorer plugin 51 | .idea/**/mongoSettings.xml 52 | 53 | # File-based project format 54 | *.iws 55 | 56 | # IntelliJ 57 | out/ 58 | 59 | # mpeltonen/sbt-idea plugin 60 | .idea_modules/ 61 | 62 | # JIRA plugin 63 | atlassian-ide-plugin.xml 64 | 65 | # Cursive Clojure plugin 66 | .idea/replstate.xml 67 | 68 | # SonarLint plugin 69 | .idea/sonarlint/ 70 | 71 | # Crashlytics plugin (for Android Studio and IntelliJ) 72 | com_crashlytics_export_strings.xml 73 | crashlytics.properties 74 | crashlytics-build.properties 75 | fabric.properties 76 | 77 | # Editor-based Rest Client 78 | .idea/httpRequests 79 | 80 | # Android studio 3.1+ serialized cache file 81 | .idea/caches/build_file_checksums.ser 82 | 83 | ### Intellij Patch ### 84 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 85 | 86 | # *.iml 87 | # modules.xml 88 | # .idea/misc.xml 89 | # *.ipr 90 | 91 | # Sonarlint plugin 92 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 93 | .idea/**/sonarlint/ 94 | 95 | # SonarQube Plugin 96 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 97 | .idea/**/sonarIssues.xml 98 | 99 | # Markdown Navigator plugin 100 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 101 | .idea/**/markdown-navigator.xml 102 | .idea/**/markdown-navigator-enh.xml 103 | .idea/**/markdown-navigator/ 104 | 105 | # Cache file creation bug 106 | # See https://youtrack.jetbrains.com/issue/JBR-2257 107 | .idea/$CACHE_FILE$ 108 | 109 | # CodeStream plugin 110 | # https://plugins.jetbrains.com/plugin/12206-codestream 111 | .idea/codestream.xml 112 | 113 | # Azure Toolkit for IntelliJ plugin 114 | # https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij 115 | .idea/**/azureSettings.xml 116 | 117 | ### Java ### 118 | # Compiled class file 119 | *.class 120 | 121 | # Log file 122 | *.log 123 | 124 | # BlueJ files 125 | *.ctxt 126 | 127 | # Mobile Tools for Java (J2ME) 128 | .mtj.tmp/ 129 | 130 | # Package Files # 131 | *.jar 132 | *.war 133 | *.nar 134 | *.ear 135 | *.zip 136 | *.tar.gz 137 | *.rar 138 | 139 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 140 | hs_err_pid* 141 | replay_pid* 142 | 143 | ### Gradle ### 144 | .gradle 145 | **/build/ 146 | !src/**/build/ 147 | 148 | # Ignore Gradle GUI config 149 | gradle-app.setting 150 | 151 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 152 | !gradle-wrapper.jar 153 | 154 | # Avoid ignore Gradle wrappper properties 155 | !gradle-wrapper.properties 156 | 157 | # Cache of project 158 | .gradletasknamecache 159 | 160 | # Eclipse Gradle plugin generated files 161 | # Eclipse Core 162 | .project 163 | # JDT-specific (Eclipse Java Development Tools) 164 | .classpath 165 | 166 | ### Gradle Patch ### 167 | # Java heap dump 168 | *.hprof 169 | 170 | # End of https://www.toptal.com/developers/gitignore/api/java,gradle,intellij 171 | 172 | 173 | # added 174 | 175 | bin/ 176 | .vscode/ 177 | run/ 178 | .idea/ -------------------------------------------------------------------------------- /src/main/java/com/ui_utils/mixin/ScreenMixin.java: -------------------------------------------------------------------------------- 1 | package com.ui_utils.mixin; 2 | 3 | import net.minecraft.client.MinecraftClient; 4 | import net.minecraft.client.font.TextRenderer; 5 | import net.minecraft.client.gui.DrawContext; 6 | import net.minecraft.client.gui.Drawable; 7 | import net.minecraft.client.gui.Element; 8 | import net.minecraft.client.gui.Selectable; 9 | import net.minecraft.client.gui.screen.Screen; 10 | import net.minecraft.client.gui.screen.ingame.LecternScreen; 11 | import net.minecraft.client.gui.widget.TextFieldWidget; 12 | import net.minecraft.text.Text; 13 | import org.lwjgl.glfw.GLFW; 14 | import org.spongepowered.asm.mixin.Mixin; 15 | import org.spongepowered.asm.mixin.Shadow; 16 | import org.spongepowered.asm.mixin.injection.At; 17 | import org.spongepowered.asm.mixin.injection.Inject; 18 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 19 | import com.ui_utils.MainClient; 20 | import com.ui_utils.SharedVariables; 21 | import com.ui_utils.mixin.accessor.ScreenAccessor; 22 | 23 | import java.util.regex.Pattern; 24 | 25 | @SuppressWarnings("all") 26 | @Mixin(Screen.class) 27 | public abstract class ScreenMixin { 28 | @Shadow 29 | public abstract T addDrawableChild(T drawableElement); 30 | 31 | private static final MinecraftClient mc = MinecraftClient.getInstance(); 32 | 33 | private TextFieldWidget addressField; 34 | private boolean initialized = false; 35 | 36 | // inject at the end of the render method (if instanceof LecternScreen) 37 | @Inject(at = @At("TAIL"), method = "init(Lnet/minecraft/client/MinecraftClient;II)V") 38 | public void init(MinecraftClient client, int width, int height, CallbackInfo ci) { 39 | // check if the current gui is a lectern gui and if ui-utils is enabled 40 | if (mc.currentScreen instanceof LecternScreen screen && SharedVariables.enabled) { 41 | // setup widgets 42 | if (/*!this.initialized*/ true) { // bro why did you do this cxg :skull: 43 | // check if the current gui is a lectern gui and ui-utils is enabled 44 | // if you do not message me about this @coderx-gamer you are not reading my commits 45 | // why would you read them anyway tbh 46 | // ill clean this up later if you dont fix it 47 | 48 | TextRenderer textRenderer = ((ScreenAccessor) this).getTextRenderer(); 49 | MainClient.createWidgets(mc, screen); 50 | 51 | // create chat box 52 | this.addressField = new TextFieldWidget(textRenderer, 5, 245, 160, 20, Text.of("Chat ...")) { 53 | @Override 54 | public boolean keyPressed(int keyCode, int scanCode, int modifiers) { 55 | if (keyCode == GLFW.GLFW_KEY_ENTER) { 56 | if (this.getText().equals("^toggleuiutils")) { 57 | SharedVariables.enabled = !SharedVariables.enabled; 58 | if (mc.player != null) { 59 | mc.player.sendMessage(Text.of("UI-Utils is now " + (SharedVariables.enabled ? "enabled" : "disabled") + "."), false); 60 | } 61 | return false; 62 | } 63 | 64 | if (mc.getNetworkHandler() != null) { 65 | if (this.getText().startsWith("/")) { 66 | mc.getNetworkHandler().sendChatCommand(this.getText().replaceFirst(Pattern.quote("/"), "")); 67 | } else { 68 | mc.getNetworkHandler().sendChatMessage(this.getText()); 69 | } 70 | } else { 71 | MainClient.LOGGER.warn("Minecraft network handler (mc.getNetworkHandler()) was null while trying to send chat message from UI Utils."); 72 | } 73 | 74 | this.setText(""); 75 | } 76 | return super.keyPressed(keyCode, scanCode, modifiers); 77 | } 78 | }; 79 | this.addressField.setText(""); 80 | this.addressField.setMaxLength(255); 81 | 82 | this.addDrawableChild(this.addressField); 83 | this.initialized = true; 84 | } 85 | } 86 | } 87 | 88 | @Inject(at = @At("TAIL"), method = "render") 89 | public void render(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { 90 | // display sync id, revision, if ui utils is enabled 91 | if (SharedVariables.enabled && mc.player != null && mc.currentScreen instanceof LecternScreen) { 92 | MainClient.createText(mc, context, ((ScreenAccessor) this).getTextRenderer()); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/ui_utils/mixin/HandledScreenMixin.java: -------------------------------------------------------------------------------- 1 | package com.ui_utils.mixin; 2 | 3 | import net.minecraft.client.MinecraftClient; 4 | import net.minecraft.client.gui.DrawContext; 5 | import net.minecraft.client.gui.screen.Screen; 6 | import net.minecraft.client.gui.screen.ingame.HandledScreen; 7 | import net.minecraft.client.gui.widget.TextFieldWidget; 8 | import net.minecraft.screen.slot.Slot; 9 | import net.minecraft.screen.slot.SlotActionType; 10 | import net.minecraft.text.Text; 11 | import org.jetbrains.annotations.Nullable; 12 | import org.lwjgl.glfw.GLFW; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.Shadow; 15 | import org.spongepowered.asm.mixin.Unique; 16 | import org.spongepowered.asm.mixin.injection.At; 17 | import org.spongepowered.asm.mixin.injection.Inject; 18 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 19 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 20 | import com.ui_utils.MainClient; 21 | import com.ui_utils.SharedVariables; 22 | 23 | import java.util.regex.Pattern; 24 | 25 | @Mixin(HandledScreen.class) 26 | public abstract class HandledScreenMixin extends Screen { 27 | private HandledScreenMixin() { 28 | super(null); 29 | } 30 | 31 | @Shadow 32 | protected abstract boolean handleHotbarKeyPressed(int keyCode, int scanCode); 33 | @Shadow 34 | protected abstract void onMouseClick(Slot slot, int slotId, int button, SlotActionType actionType); 35 | @Shadow 36 | @Nullable 37 | protected Slot focusedSlot; 38 | 39 | @Unique 40 | private static final MinecraftClient mc = MinecraftClient.getInstance(); 41 | 42 | @Unique 43 | private TextFieldWidget addressField; 44 | 45 | // called when creating a HandledScreen 46 | @Inject(at = @At("TAIL"), method = "init") 47 | public void init(CallbackInfo ci) { 48 | if (SharedVariables.enabled) { 49 | MainClient.createWidgets(mc, this); 50 | 51 | // create chat box 52 | this.addressField = new TextFieldWidget(this.textRenderer, 5, 245, 160, 20, Text.of("Chat ...")) { 53 | @Override 54 | public boolean keyPressed(int keyCode, int scanCode, int modifiers) { 55 | if (keyCode == GLFW.GLFW_KEY_ENTER) { 56 | if (this.getText().equals("^toggleuiutils")) { 57 | SharedVariables.enabled = !SharedVariables.enabled; 58 | if (mc.player != null) { 59 | mc.player.sendMessage(Text.of("UI-Utils is now " + (SharedVariables.enabled ? "enabled" : "disabled") + "."), false); 60 | 61 | } 62 | return false; 63 | } 64 | 65 | if (mc.getNetworkHandler() != null) { 66 | if (this.getText().startsWith("/")) { 67 | mc.getNetworkHandler().sendChatCommand(this.getText().replaceFirst(Pattern.quote("/"), "")); 68 | } else { 69 | mc.getNetworkHandler().sendChatMessage(this.getText()); 70 | } 71 | } else { 72 | MainClient.LOGGER.warn("Minecraft network handler (mc.getNetworkHandler()) was null while trying to send chat message from UI Utils."); 73 | } 74 | 75 | this.setText(""); 76 | } 77 | return super.keyPressed(keyCode, scanCode, modifiers); 78 | } 79 | }; 80 | this.addressField.setText(""); 81 | this.addressField.setMaxLength(256); 82 | 83 | this.addDrawableChild(this.addressField); 84 | } 85 | } 86 | 87 | @Inject(at = @At("HEAD"), method = "keyPressed", cancellable = true) 88 | public void keyPressed(int keyCode, int scanCode, int modifiers, CallbackInfoReturnable cir) { 89 | cir.cancel(); 90 | if (super.keyPressed(keyCode, scanCode, modifiers)) { 91 | cir.setReturnValue(true); 92 | } else if (MainClient.mc.options.inventoryKey.matchesKey(keyCode, scanCode) && (this.addressField == null || !this.addressField.isSelected())) { 93 | // Crashes if address field does not exist (because of ui utils disabled, this is a temporary fix.) 94 | this.close(); 95 | cir.setReturnValue(true); 96 | } else { 97 | this.handleHotbarKeyPressed(keyCode, scanCode); 98 | if (this.focusedSlot != null && this.focusedSlot.hasStack()) { 99 | if (mc.options.pickItemKey.matchesKey(keyCode, scanCode)) { 100 | this.onMouseClick(this.focusedSlot, this.focusedSlot.id, 0, SlotActionType.CLONE); 101 | } else if (mc.options.dropKey.matchesKey(keyCode, scanCode)) { 102 | this.onMouseClick(this.focusedSlot, this.focusedSlot.id, hasControlDown() ? 1 : 0, SlotActionType.THROW); 103 | } 104 | } 105 | 106 | cir.setReturnValue(true); 107 | } 108 | } 109 | 110 | // inject at the end of the render method 111 | @Inject(at = @At("TAIL"), method = "render") 112 | public void render(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { 113 | // display sync id, revision, if ui utils is enabled 114 | // this hurts me physically to look at this in a render method :( 115 | // im too lazy to fix it tho :D 116 | if (SharedVariables.enabled) { 117 | MainClient.createText(mc, context, this.textRenderer); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UI-Utils 2 | Plugin debugging mod. May be incompatible with mac, needs Fabric API. 3 | --- 4 | 5 | # How to use: 6 | 7 | --- 8 | 9 | - Open any inventory / container with the mod, and you should see some buttons and a text field. 10 | 11 | ![image](images/gui.png) 12 | 13 | - "Close without packet" closes your current GUI (ScreenHandler) without sending a `CloseHandledScreenC2SPacket` to the server. 14 | 15 | - "De-sync" closes your current GUI server-side and keeps it open client-side. 16 | 17 | - "Send packets: true/false" tells the client whether it should send any `ClickSlotC2SPacket`(s) and `ButtonClickC2SPacket`(s). 18 | 19 | - "Delay packets: true/false" when turned on it will store all `ClickSlotC2SPacket`(s) and `ButtonClickC2SPacket`(s) into a list and will not send them immediately until turned off which sends them all at once. 20 | 21 | - "Save GUI" saves your current GUI to a variable and can be accessed by pressing a keybinding in the keybinding options (default key is 'V'). 22 | 23 | - "Disconnect and send packets" will if "Delay packets" is turned on send the list of stored packets immediately and then disconnect right afterward (can create potential race conditions on non-vanilla servers). 24 | 25 | - "Sync Id: ??" is a number used internally to sync various GUI related packets. 26 | 27 | - "Revision: ??" is a number used internally to sync various GUI related packets sent from the server to the client. 28 | 29 | - "Fabricate packet" allows you to create a custom `ClickSlotC2SPacket` and `ButtonClickC2SPacket` within a window it creates. 30 | 31 | - "Copy GUI Title JSON" copies the name of your current GUI in JSON format. 32 | 33 | - The text box is a chat field for chatting or running commands while in a GUI. 34 | 35 | --- 36 | 37 | ### Fabricate packet tutorial: 38 | 39 | --- 40 | 41 | - `ClickSlotC2SPacket`(s) are what the client sends to the server when clicking any slot in a GUI (e.g. shift clicking an item). 42 | 43 | - When clicking the "Fabricate packet" button you should see this window appear (may not work on OSX): 44 | 45 | ![image](images/packet_option.png) 46 | 47 | - Clicking "Click Slot" will open up this window: 48 | 49 | ![image](images/click_slot_packet.png) 50 | 51 | - Enter the "Sync Id" and "Revision" value you see in the in-game GUI to the "Click Slot Packet" GUI. 52 | 53 | - The "Slot" value should be set to what slot you would like to click (starting from 0) you can generally find the location of GUI slots on Google for generic GUI(s), e.g. double chest: 54 | 55 | ![image](images/slots.png) 56 | 57 | - The "Button" field should be set to either: (0 is a left-click, 1 is a right-click, 0-8 and 40 will be explained below). 58 | 59 | - The "Action" field should be set to one of these options, 60 | 61 | ![image](images/click_slot_actions.png) 62 | 63 | - "PICKUP" puts the item on the slot field on your cursor or vice versa, "QUICK_MOVE" is a shift click, "SWAP" acts as a hotbar or offhand swap (e.g. if your "Action" is set to "SWAP" and the "Button" set to 0-8, it will swap the item in the "Slot" field to one of those hotbar slots (starting from 0) or vice versa, "Button" being set to 40 will swap the item in the "Slot" field to your offhand or vice versa), "CLONE" acts as a middle click to clone items (only works in creative mode), "THROW" drops the item in the "Slot" field, "QUICK_CRAFT" is a bit complicated, so you will have to experiment yourself or look into some code for it, "PICKUP_ALL" will pick up all the items matching to the item on your cursor, as long as "Slot" is within bounds of that GUI. 64 | 65 | - The "Send" button will send the packet for with all the info you inputted and will give a response if it was successful or failed to send the packet you provided. 66 | 67 | - The "Times to send" field tells the client how many times to send that packet when pressing "Send". 68 | 69 | - The delay checkbox tells the client if the packet should wait while "Delay Packets" is on. 70 | 71 | --- 72 | 73 | - Example of this feature: 74 | 75 | ![image](images/click_slot_example.png) 76 | 77 | - When clicking "Send" in the above image, it should drop the bedrock item on the ground. 78 | 79 | ![image](images/click_slot_example_2.png) 80 | 81 | 82 | - Fabricate packet tutorial (Button Click): 83 | --- 84 | 85 | - `ButtonClickC2SPacket`(s) are what the client sends to the server when clicking a button in a server-side GUI (e.g. clicking an enchantment in an enchantment table). 86 | 87 | - When clicking the "Fabricate packet" button you should see this window appear: 88 | 89 | ![image](images/packet_option.png) 90 | 91 | - Clicking "Button Click" will open up this window: 92 | 93 | ![image](images/button_click_packet.png) 94 | 95 | - Enter the "Sync Id" field in the "Button Click Packet" GUI as the "Sync Id" value you will see in the in-game GUI. 96 | 97 | - Enter the "Button Id" field as what button you would like to click in a GUI (starting from 0.) 98 | 99 | - Enter into the field "Times to send" (by default it's one) the amount of packets you would like to send. 100 | 101 | - Check the "Delay" checkbox if you would like to delay your packet while "Delay Packets" is on. 102 | 103 | - Example of this feature: 104 | --- 105 | 106 | ![image](images/enchant.png) 107 | 108 | ![image](images/enchant_2.png) 109 | 110 | ![image](images/enchant_3.png) 111 | 112 | --- 113 | 114 | ## "^toggleuiutils" command: 115 | 116 | --- 117 | - Enables/Disables UI-Utils in-game GUI rendering. 118 | --- 119 | 120 | ## Resource Pack Bypassing 121 | 122 | --- 123 | 124 | You can bypass a required resource pack on a server by turning on "Bypass Resource Pack" in the multiplayer screen, if you still get kicked, enable "Force Deny", which will block all resource packs. 125 | 126 | --- 127 | 128 | ## Building: 129 | 130 | --- 131 | 132 | You can find building instructions on the UI-Utils website (https://ui-utils.com). 133 | 134 | --- 135 | 136 | This mod concept is not an original idea, there are similar mods out there that have similar features, the goal of this mod is to be on the latest version and have some additional features. 137 | 138 | Some images here may be outdated. 139 | 140 | Note that mac may work with most of the mod but not the fabricate packet feature potentially. 141 | 142 | That's all the features for this mod currently, there may be more updates in the future but I cannot guarantee. 143 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /src/main/java/com/ui_utils/MainClient.java: -------------------------------------------------------------------------------- 1 | package com.ui_utils; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.gson.Gson; 5 | import com.mojang.serialization.JsonOps; 6 | import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; 7 | import net.fabricmc.api.ClientModInitializer; 8 | import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; 9 | import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; 10 | import net.fabricmc.loader.api.FabricLoader; 11 | import net.fabricmc.loader.api.metadata.ModMetadata; 12 | import net.minecraft.client.MinecraftClient; 13 | import net.minecraft.client.font.TextRenderer; 14 | import net.minecraft.client.gui.DrawContext; 15 | import net.minecraft.client.gui.screen.Screen; 16 | import net.minecraft.client.gui.widget.ButtonWidget; 17 | import net.minecraft.client.option.KeyBinding; 18 | import net.minecraft.client.util.InputUtil; 19 | import net.minecraft.network.packet.Packet; 20 | import net.minecraft.network.packet.c2s.play.ButtonClickC2SPacket; 21 | import net.minecraft.network.packet.c2s.play.ClickSlotC2SPacket; 22 | import net.minecraft.network.packet.c2s.play.CloseHandledScreenC2SPacket; 23 | import net.minecraft.screen.slot.SlotActionType; 24 | import net.minecraft.screen.sync.ItemStackHash; 25 | import net.minecraft.text.Text; 26 | import net.minecraft.text.TextCodecs; 27 | import org.jetbrains.annotations.NotNull; 28 | import org.lwjgl.glfw.GLFW; 29 | import org.slf4j.Logger; 30 | import org.slf4j.LoggerFactory; 31 | import com.ui_utils.mixin.accessor.ClientConnectionAccessor; 32 | 33 | import javax.swing.*; 34 | import java.awt.*; 35 | import java.util.Timer; 36 | import java.util.TimerTask; 37 | import java.util.Vector; 38 | 39 | public class MainClient implements ClientModInitializer { 40 | public static Font monospace; 41 | public static Color darkWhite; 42 | 43 | public static KeyBinding restoreScreenKey; 44 | 45 | public static Logger LOGGER = LoggerFactory.getLogger("ui-utils"); 46 | public static MinecraftClient mc = MinecraftClient.getInstance(); 47 | @Override 48 | public void onInitializeClient() { 49 | UpdateUtils.checkForUpdates(); 50 | 51 | // register "restore screen" key 52 | restoreScreenKey = KeyBindingHelper.registerKeyBinding(new KeyBinding("Restore Screen", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_V, "UI Utils")); 53 | 54 | // register event for END_CLIENT_TICK 55 | ClientTickEvents.END_CLIENT_TICK.register((client) -> { 56 | // detect if the "restore screen" keybinding is pressed 57 | while (restoreScreenKey.wasPressed()) { 58 | if (SharedVariables.storedScreen != null && SharedVariables.storedScreenHandler != null && client.player != null) { 59 | client.setScreen(SharedVariables.storedScreen); 60 | client.player.currentScreenHandler = SharedVariables.storedScreenHandler; 61 | } 62 | } 63 | }); 64 | 65 | // set java.awt.headless to false if os is not mac (allows for JFrame guis to be used) 66 | if (!MinecraftClient.IS_SYSTEM_MAC) { 67 | System.setProperty("java.awt.headless", "false"); 68 | monospace = new Font(Font.MONOSPACED, Font.PLAIN, 10); 69 | darkWhite = new Color(220, 220, 220); 70 | } 71 | } 72 | 73 | @SuppressWarnings("all") 74 | public static void createText(MinecraftClient mc, DrawContext context, TextRenderer textRenderer) { 75 | // display the current gui's sync id, revision 76 | context.drawText(textRenderer, "Sync Id: " + mc.player.currentScreenHandler.syncId, 200, 5, Color.WHITE.getRGB(), false); 77 | context.drawText(textRenderer, "Revision: " + mc.player.currentScreenHandler.getRevision(), 200, 35, Color.WHITE.getRGB(), false); 78 | } 79 | 80 | // bro are you ever going to clean this up? 81 | // this code is very messy, ill clean it up if you dont 82 | // -- MrBreakNFix 83 | public static void createWidgets(MinecraftClient mc, Screen screen) { 84 | // register "close without packet" button in all HandledScreens 85 | screen.addDrawableChild(ButtonWidget.builder(Text.of("Close without packet"), (button) -> { 86 | // closes the current gui without sending a packet to the current server 87 | mc.setScreen(null); 88 | }).width(115).position(5, 5).build()); 89 | 90 | // register "de-sync" button in all HandledScreens 91 | screen.addDrawableChild(ButtonWidget.builder(Text.of("De-sync"), (button) -> { 92 | // keeps the current gui open client-side and closed server-side 93 | if (mc.getNetworkHandler() != null && mc.player != null) { 94 | mc.getNetworkHandler().sendPacket(new CloseHandledScreenC2SPacket(mc.player.currentScreenHandler.syncId)); 95 | } else { 96 | LOGGER.warn("Minecraft network handler or player was null while using 'De-sync' in UI Utils."); 97 | } 98 | }).width(115).position(5, 35).build()); 99 | 100 | // register "send packets" button in all HandledScreens 101 | screen.addDrawableChild(ButtonWidget.builder(Text.of("Send packets: " + SharedVariables.sendUIPackets), (button) -> { 102 | // tells the client if it should send any gui related packets 103 | SharedVariables.sendUIPackets = !SharedVariables.sendUIPackets; 104 | button.setMessage(Text.of("Send packets: " + SharedVariables.sendUIPackets)); 105 | }).width(115).position(5, 65).build()); 106 | 107 | // register "delay packets" button in all HandledScreens 108 | screen.addDrawableChild(ButtonWidget.builder(Text.of("Delay packets: " + SharedVariables.delayUIPackets), (button) -> { 109 | // toggles a setting to delay all gui related packets to be used later when turning this setting off 110 | SharedVariables.delayUIPackets = !SharedVariables.delayUIPackets; 111 | button.setMessage(Text.of("Delay packets: " + SharedVariables.delayUIPackets)); 112 | if (!SharedVariables.delayUIPackets && !SharedVariables.delayedUIPackets.isEmpty() && mc.getNetworkHandler() != null) { 113 | for (Packet packet : SharedVariables.delayedUIPackets) { 114 | mc.getNetworkHandler().sendPacket(packet); 115 | } 116 | if (mc.player != null) { 117 | mc.player.sendMessage(Text.of("Sent " + SharedVariables.delayedUIPackets.size() + " packets."), false); 118 | } 119 | SharedVariables.delayedUIPackets.clear(); 120 | } 121 | }).width(115).position(5, 95).build()); 122 | 123 | // register "save gui" button in all HandledScreens 124 | screen.addDrawableChild(ButtonWidget.builder(Text.of("Save GUI"), (button) -> { 125 | // saves the current gui to a variable to be accessed later 126 | if (mc.player != null) { 127 | SharedVariables.storedScreen = mc.currentScreen; 128 | SharedVariables.storedScreenHandler = mc.player.currentScreenHandler; 129 | } 130 | }).width(115).position(5, 125).build()); 131 | 132 | // register "disconnect and send packets" button in all HandledScreens 133 | screen.addDrawableChild(ButtonWidget.builder(Text.of("Disconnect and send packets"), (button) -> { 134 | // sends all "delayed" gui related packets before disconnecting, use: potential race conditions on non-vanilla servers 135 | SharedVariables.delayUIPackets = false; 136 | if (mc.getNetworkHandler() != null) { 137 | for (Packet packet : SharedVariables.delayedUIPackets) { 138 | mc.getNetworkHandler().sendPacket(packet); 139 | } 140 | mc.getNetworkHandler().getConnection().disconnect(Text.of("Disconnecting (UI-UTILS)")); 141 | } else { 142 | LOGGER.warn("Minecraft network handler (mc.getNetworkHandler()) is null while client is disconnecting."); 143 | } 144 | SharedVariables.delayedUIPackets.clear(); 145 | }).width(160).position(5, 155).build()); 146 | 147 | // register "fabricate packet" button in all HandledScreens 148 | ButtonWidget fabricatePacketButton = ButtonWidget.builder(Text.of("Fabricate packet"), (button) -> { 149 | // creates a gui allowing you to fabricate packets 150 | 151 | JFrame frame = new JFrame("Choose Packet"); 152 | frame.setBounds(0, 0, 450, 100); 153 | frame.setResizable(false); 154 | frame.setLocationRelativeTo(null); 155 | frame.setLayout(null); 156 | 157 | JButton clickSlotButton = getPacketOptionButton("Click Slot"); 158 | clickSlotButton.setBounds(100, 25, 110, 20); 159 | clickSlotButton.addActionListener((event) -> { 160 | // im too lazy to comment everything here just read the code yourself 161 | frame.setVisible(false); 162 | 163 | JFrame clickSlotFrame = new JFrame("Click Slot Packet"); 164 | clickSlotFrame.setBounds(0, 0, 450, 300); 165 | clickSlotFrame.setResizable(false); 166 | clickSlotFrame.setLocationRelativeTo(null); 167 | clickSlotFrame.setLayout(null); 168 | 169 | JLabel syncIdLabel = new JLabel("Sync Id:"); 170 | syncIdLabel.setFocusable(false); 171 | syncIdLabel.setFont(monospace); 172 | syncIdLabel.setBounds(25, 25, 100, 20); 173 | 174 | JLabel revisionLabel = new JLabel("Revision:"); 175 | revisionLabel.setFocusable(false); 176 | revisionLabel.setFont(monospace); 177 | revisionLabel.setBounds(25, 50, 100, 20); 178 | 179 | JLabel slotLabel = new JLabel("Slot:"); 180 | slotLabel.setFocusable(false); 181 | slotLabel.setFont(monospace); 182 | slotLabel.setBounds(25, 75, 100, 20); 183 | 184 | JLabel buttonLabel = new JLabel("Button:"); 185 | buttonLabel.setFocusable(false); 186 | buttonLabel.setFont(monospace); 187 | buttonLabel.setBounds(25, 100, 100, 20); 188 | 189 | JLabel actionLabel = new JLabel("Action:"); 190 | actionLabel.setFocusable(false); 191 | actionLabel.setFont(monospace); 192 | actionLabel.setBounds(25, 125, 100, 20); 193 | 194 | JLabel timesToSendLabel = new JLabel("Times to send:"); 195 | timesToSendLabel.setFocusable(false); 196 | timesToSendLabel.setFont(monospace); 197 | timesToSendLabel.setBounds(25, 190, 100, 20); 198 | 199 | JTextField syncIdField = new JTextField(1); 200 | syncIdField.setFont(monospace); 201 | syncIdField.setBounds(125, 25, 100, 20); 202 | 203 | JTextField revisionField = new JTextField(1); 204 | revisionField.setFont(monospace); 205 | revisionField.setBounds(125, 50, 100, 20); 206 | 207 | JTextField slotField = new JTextField(1); 208 | slotField.setFont(monospace); 209 | slotField.setBounds(125, 75, 100, 20); 210 | 211 | JTextField buttonField = new JTextField(1); 212 | buttonField.setFont(monospace); 213 | buttonField.setBounds(125, 100, 100, 20); 214 | 215 | JComboBox actionField = new JComboBox<>(new Vector<>(ImmutableList.of( 216 | "PICKUP", 217 | "QUICK_MOVE", 218 | "SWAP", 219 | "CLONE", 220 | "THROW", 221 | "QUICK_CRAFT", 222 | "PICKUP_ALL" 223 | ))); 224 | actionField.setFocusable(false); 225 | actionField.setEditable(false); 226 | actionField.setBorder(BorderFactory.createEmptyBorder()); 227 | actionField.setBackground(darkWhite); 228 | actionField.setFont(monospace); 229 | actionField.setBounds(125, 125, 100, 20); 230 | 231 | JLabel statusLabel = new JLabel(); 232 | statusLabel.setVisible(false); 233 | statusLabel.setFocusable(false); 234 | statusLabel.setFont(monospace); 235 | statusLabel.setBounds(210, 150, 190, 20); 236 | 237 | JCheckBox delayBox = new JCheckBox("Delay"); 238 | delayBox.setBounds(115, 150, 85, 20); 239 | delayBox.setSelected(false); 240 | delayBox.setFont(monospace); 241 | delayBox.setFocusable(false); 242 | 243 | JTextField timesToSendField = new JTextField("1"); 244 | timesToSendField.setFont(monospace); 245 | timesToSendField.setBounds(125, 190, 100, 20); 246 | 247 | JButton sendButton = new JButton("Send"); 248 | sendButton.setFocusable(false); 249 | sendButton.setBounds(25, 150, 75, 20); 250 | sendButton.setBorder(BorderFactory.createEtchedBorder()); 251 | sendButton.setBackground(darkWhite); 252 | sendButton.setFont(monospace); 253 | sendButton.addActionListener((event0) -> { 254 | if ( 255 | MainClient.isInteger(syncIdField.getText()) && 256 | MainClient.isInteger(revisionField.getText()) && 257 | MainClient.isInteger(slotField.getText()) && 258 | MainClient.isInteger(buttonField.getText()) && 259 | MainClient.isInteger(timesToSendField.getText()) && 260 | actionField.getSelectedItem() != null) { 261 | int syncId = Integer.parseInt(syncIdField.getText()); 262 | int revision = Integer.parseInt(revisionField.getText()); 263 | short slot = Short.parseShort(slotField.getText()); 264 | byte button0 = Byte.parseByte(buttonField.getText()); 265 | SlotActionType action = MainClient.stringToSlotActionType(actionField.getSelectedItem().toString()); 266 | int timesToSend = Integer.parseInt(timesToSendField.getText()); 267 | 268 | if (action != null) { 269 | ClickSlotC2SPacket packet = new ClickSlotC2SPacket(syncId, revision, slot, button0, action, new Int2ObjectArrayMap<>(), ItemStackHash.EMPTY); 270 | try { 271 | Runnable toRun = getFabricatePacketRunnable(mc, delayBox.isSelected(), packet); 272 | for (int i = 0; i < timesToSend; i++) { 273 | toRun.run(); 274 | } 275 | } catch (Exception e) { 276 | statusLabel.setForeground(Color.RED.darker()); 277 | statusLabel.setText("You must be connected to a server!"); 278 | MainClient.queueTask(() -> { 279 | statusLabel.setVisible(false); 280 | statusLabel.setText(""); 281 | }, 1500L); 282 | return; 283 | } 284 | statusLabel.setVisible(true); 285 | statusLabel.setForeground(Color.GREEN.darker()); 286 | statusLabel.setText("Sent successfully!"); 287 | MainClient.queueTask(() -> { 288 | statusLabel.setVisible(false); 289 | statusLabel.setText(""); 290 | }, 1500L); 291 | } else { 292 | statusLabel.setVisible(true); 293 | statusLabel.setForeground(Color.RED.darker()); 294 | statusLabel.setText("Invalid arguments!"); 295 | MainClient.queueTask(() -> { 296 | statusLabel.setVisible(false); 297 | statusLabel.setText(""); 298 | }, 1500L); 299 | } 300 | } else { 301 | statusLabel.setVisible(true); 302 | statusLabel.setForeground(Color.RED.darker()); 303 | statusLabel.setText("Invalid arguments!"); 304 | MainClient.queueTask(() -> { 305 | statusLabel.setVisible(false); 306 | statusLabel.setText(""); 307 | }, 1500L); 308 | } 309 | }); 310 | 311 | clickSlotFrame.add(syncIdLabel); 312 | clickSlotFrame.add(revisionLabel); 313 | clickSlotFrame.add(slotLabel); 314 | clickSlotFrame.add(buttonLabel); 315 | clickSlotFrame.add(actionLabel); 316 | clickSlotFrame.add(timesToSendLabel); 317 | clickSlotFrame.add(syncIdField); 318 | clickSlotFrame.add(revisionField); 319 | clickSlotFrame.add(slotField); 320 | clickSlotFrame.add(buttonField); 321 | clickSlotFrame.add(actionField); 322 | clickSlotFrame.add(sendButton); 323 | clickSlotFrame.add(statusLabel); 324 | clickSlotFrame.add(delayBox); 325 | clickSlotFrame.add(timesToSendField); 326 | clickSlotFrame.setVisible(true); 327 | }); 328 | 329 | JButton buttonClickButton = getPacketOptionButton("Button Click"); 330 | buttonClickButton.setBounds(250, 25, 110, 20); 331 | buttonClickButton.addActionListener((event) -> { 332 | frame.setVisible(false); 333 | 334 | JFrame buttonClickFrame = new JFrame("Button Click Packet"); 335 | buttonClickFrame.setBounds(0, 0, 450, 250); 336 | buttonClickFrame.setResizable(false); 337 | buttonClickFrame.setLocationRelativeTo(null); 338 | buttonClickFrame.setLayout(null); 339 | 340 | JLabel syncIdLabel = new JLabel("Sync Id:"); 341 | syncIdLabel.setFocusable(false); 342 | syncIdLabel.setFont(monospace); 343 | syncIdLabel.setBounds(25, 25, 100, 20); 344 | 345 | JLabel buttonIdLabel = new JLabel("Button Id:"); 346 | buttonIdLabel.setFocusable(false); 347 | buttonIdLabel.setFont(monospace); 348 | buttonIdLabel.setBounds(25, 50, 100, 20); 349 | 350 | JTextField syncIdField = new JTextField(1); 351 | syncIdField.setFont(monospace); 352 | syncIdField.setBounds(125, 25, 100, 20); 353 | 354 | JTextField buttonIdField = new JTextField(1); 355 | buttonIdField.setFont(monospace); 356 | buttonIdField.setBounds(125, 50, 100, 20); 357 | 358 | JLabel statusLabel = new JLabel(); 359 | statusLabel.setVisible(false); 360 | statusLabel.setFocusable(false); 361 | statusLabel.setFont(monospace); 362 | statusLabel.setBounds(210, 95, 190, 20); 363 | 364 | JCheckBox delayBox = new JCheckBox("Delay"); 365 | delayBox.setBounds(115, 95, 85, 20); 366 | delayBox.setSelected(false); 367 | delayBox.setFont(monospace); 368 | delayBox.setFocusable(false); 369 | 370 | JLabel timesToSendLabel = new JLabel("Times to send:"); 371 | timesToSendLabel.setFocusable(false); 372 | timesToSendLabel.setFont(monospace); 373 | timesToSendLabel.setBounds(25, 130, 100, 20); 374 | 375 | JTextField timesToSendField = new JTextField("1"); 376 | timesToSendField.setFont(monospace); 377 | timesToSendField.setBounds(125, 130, 100, 20); 378 | 379 | JButton sendButton = new JButton("Send"); 380 | sendButton.setFocusable(false); 381 | sendButton.setBounds(25, 95, 75, 20); 382 | sendButton.setBorder(BorderFactory.createEtchedBorder()); 383 | sendButton.setBackground(darkWhite); 384 | sendButton.setFont(monospace); 385 | sendButton.addActionListener((event0) -> { 386 | if ( 387 | MainClient.isInteger(syncIdField.getText()) && 388 | MainClient.isInteger(buttonIdField.getText()) && 389 | MainClient.isInteger(timesToSendField.getText())) { 390 | int syncId = Integer.parseInt(syncIdField.getText()); 391 | int buttonId = Integer.parseInt(buttonIdField.getText()); 392 | int timesToSend = Integer.parseInt(timesToSendField.getText()); 393 | 394 | ButtonClickC2SPacket packet = new ButtonClickC2SPacket(syncId, buttonId); 395 | try { 396 | Runnable toRun = getFabricatePacketRunnable(mc, delayBox.isSelected(), packet); 397 | for (int i = 0; i < timesToSend; i++) { 398 | toRun.run(); 399 | } 400 | } catch (Exception e) { 401 | statusLabel.setVisible(true); 402 | statusLabel.setForeground(Color.RED.darker()); 403 | statusLabel.setText("You must be connected to a server!"); 404 | MainClient.queueTask(() -> { 405 | statusLabel.setVisible(false); 406 | statusLabel.setText(""); 407 | }, 1500L); 408 | return; 409 | } 410 | statusLabel.setVisible(true); 411 | statusLabel.setForeground(Color.GREEN.darker()); 412 | statusLabel.setText("Sent successfully!"); 413 | MainClient.queueTask(() -> { 414 | statusLabel.setVisible(false); 415 | statusLabel.setText(""); 416 | }, 1500L); 417 | } else { 418 | statusLabel.setVisible(true); 419 | statusLabel.setForeground(Color.RED.darker()); 420 | statusLabel.setText("Invalid arguments!"); 421 | MainClient.queueTask(() -> { 422 | statusLabel.setVisible(false); 423 | statusLabel.setText(""); 424 | }, 1500L); 425 | } 426 | }); 427 | 428 | buttonClickFrame.add(syncIdLabel); 429 | buttonClickFrame.add(buttonIdLabel); 430 | buttonClickFrame.add(syncIdField); 431 | buttonClickFrame.add(timesToSendLabel); 432 | buttonClickFrame.add(buttonIdField); 433 | buttonClickFrame.add(sendButton); 434 | buttonClickFrame.add(statusLabel); 435 | buttonClickFrame.add(delayBox); 436 | buttonClickFrame.add(timesToSendField); 437 | buttonClickFrame.setVisible(true); 438 | }); 439 | 440 | frame.add(clickSlotButton); 441 | frame.add(buttonClickButton); 442 | frame.setVisible(true); 443 | }).width(115).position(5, 185).build(); 444 | fabricatePacketButton.active = !MinecraftClient.IS_SYSTEM_MAC; 445 | screen.addDrawableChild(fabricatePacketButton); 446 | 447 | screen.addDrawableChild(ButtonWidget.builder(Text.of("Copy GUI Title JSON"), (button) -> { 448 | try { 449 | if (mc.currentScreen == null) { 450 | throw new IllegalStateException("The current minecraft screen (mc.currentScreen) is null"); 451 | } 452 | // fixes #137 453 | // From fabric wiki https://docs.fabricmc.net/develop/text-and-translations#serializing-text 454 | mc.keyboard.setClipboard(new Gson().toJson(TextCodecs.CODEC.encodeStart(JsonOps.INSTANCE, mc.currentScreen.getTitle()).getOrThrow())); 455 | } catch (IllegalStateException e) { 456 | LOGGER.error("Error while copying title JSON to clipboard", e); 457 | } 458 | }).width(115).position(5, 215).build()); 459 | } 460 | 461 | @NotNull 462 | private static JButton getPacketOptionButton(String label) { 463 | JButton button = new JButton(label); 464 | button.setFocusable(false); 465 | button.setBorder(BorderFactory.createEtchedBorder()); 466 | button.setBackground(darkWhite); 467 | button.setFont(monospace); 468 | return button; 469 | } 470 | 471 | @NotNull 472 | private static Runnable getFabricatePacketRunnable(MinecraftClient mc, boolean delay, Packet packet) { 473 | Runnable toRun; 474 | if (delay) { 475 | toRun = () -> { 476 | if (mc.getNetworkHandler() != null) { 477 | mc.getNetworkHandler().sendPacket(packet); 478 | } else { 479 | LOGGER.warn("Minecraft network handler (mc.getNetworkHandler()) is null while sending fabricated packets."); 480 | } 481 | }; 482 | } else { 483 | toRun = () -> { 484 | if (mc.getNetworkHandler() != null) { 485 | mc.getNetworkHandler().sendPacket(packet); 486 | } else { 487 | LOGGER.warn("Minecraft network handler (mc.getNetworkHandler()) is null while sending fabricated packets."); 488 | } 489 | ((ClientConnectionAccessor) mc.getNetworkHandler().getConnection()).getChannel().writeAndFlush(packet); 490 | }; 491 | } 492 | return toRun; 493 | } 494 | 495 | public static boolean isInteger(String string) { 496 | try { 497 | Integer.parseInt(string); 498 | return true; 499 | } catch (Exception e) { 500 | return false; 501 | } 502 | } 503 | 504 | public static SlotActionType stringToSlotActionType(String string) { 505 | // converts a string to SlotActionType 506 | return switch (string) { 507 | case "PICKUP" -> SlotActionType.PICKUP; 508 | case "QUICK_MOVE" -> SlotActionType.QUICK_MOVE; 509 | case "SWAP" -> SlotActionType.SWAP; 510 | case "CLONE" -> SlotActionType.CLONE; 511 | case "THROW" -> SlotActionType.THROW; 512 | case "QUICK_CRAFT" -> SlotActionType.QUICK_CRAFT; 513 | case "PICKUP_ALL" -> SlotActionType.PICKUP_ALL; 514 | default -> null; 515 | }; 516 | } 517 | 518 | public static void queueTask(Runnable runnable, long delayMs) { 519 | // queues a task for minecraft to run 520 | Timer timer = new Timer(); 521 | TimerTask task = new TimerTask() { 522 | @Override 523 | public void run() { 524 | MinecraftClient.getInstance().send(runnable); 525 | } 526 | }; 527 | timer.schedule(task, delayMs); 528 | } 529 | 530 | public static String getModVersion(String modId) { 531 | ModMetadata modMetadata = FabricLoader.getInstance().getModContainer(modId).isPresent() ? FabricLoader.getInstance().getModContainer(modId).get().getMetadata() : null; 532 | 533 | return modMetadata != null ? modMetadata.getVersion().getFriendlyString() : "null"; 534 | } 535 | } 536 | --------------------------------------------------------------------------------