diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c0804ef --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Eagler Forge + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/index.md b/docs/index.md index dc9812f..7ef3dcc 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,4 +8,6 @@ EaglerForge Injector is a tool that uses regular expressions to attach itself to [Modding tutorials](tutorials/index.md) -[Modding API documentation](apidoc/index.md) \ No newline at end of file +[Modding API documentation](apidoc/index.md) + +[Compiling the client for EaglerForgeInjector](compiling_client.md) \ No newline at end of file diff --git a/docs/tutorials/disable_all_particles.md b/docs/tutorials/disable_all_particles.md new file mode 100644 index 0000000..99526ff --- /dev/null +++ b/docs/tutorials/disable_all_particles.md @@ -0,0 +1,90 @@ +## Disable All Particles +Particles in minecraft are really laggy, and there's a large chance you may want to disable them to boost your FPS when breaking blocks. + +Let's look through the Eaglercraft 1.8 source code to find where particles are rendered. We can do this with a global search for `particle`. You'll find a lot of hits in the `EffectRenderer` class in the `net.minecraft.client.particle` package. We methods we'll want to patch are: +- `renderParticles` +- `addEffect` +- `addBlockDestroyEffects` +- `hasParticlesInAlphaLayer` + +For the first 3 methods, you can see that they are defined something like: +```java + public void renderParticles() { + // render particles code. + } +``` +The `void` in `public void` means that it does not expect a return value. +Using [`ModAPI.util.getMethodFromPackage`](../apidoc/utils.md) we can find the compiled method name, and look for it in [`ModAPI.hooks.methods`](../apidoc/hooks.md#property-modapihooksmethods). + +```javascript +(function NoParticles() { + //Basic, boilerplate code + ModAPI.meta.title("No Particles"); + ModAPI.meta.description("Disables all particles in game"); + ModAPI.meta.credits("By "); + + ModAPI.hooks.methods[ + ModAPI.util.getMethodFromPackage("net.minecraft.client.particle.EffectRenderer", "renderParticles") + ] = function () {}; //Override renderParticles in EffectRenderer to an empty function that does nothing. +})(); +``` + +We can also do this for `addEffect` and `addBlockDestroyEffects`. + +```javascript +(function NoParticles() { + //Basic, boilerplate code + ModAPI.meta.title("No Particles"); + ModAPI.meta.description("Disables all particles in game"); + ModAPI.meta.credits("By "); + + ModAPI.hooks.methods[ + ModAPI.util.getMethodFromPackage("net.minecraft.client.particle.EffectRenderer", "renderParticles") + ] = function () {}; //Override renderParticles in EffectRenderer with an empty function that does nothing. + + ModAPI.hooks.methods[ + ModAPI.util.getMethodFromPackage("net.minecraft.client.particle.EffectRenderer", "addEffect") + ] = function () {}; //Override addEffect in EffectRenderer with an empty function that does nothing. + + ModAPI.hooks.methods[ + ModAPI.util.getMethodFromPackage("net.minecraft.client.particle.EffectRenderer", "addBlockDestroyEffects") + ] = function () {}; //Override addBlockDestroyEffects in EffectRenderer with an empty function that does nothing. +})(); +``` + +For `hasParticlesInAlphaLayer`, it doesn't use `void`, but instead a `boolean`. +```java + public boolean hasParticlesInAlphaLayer() { + // hasParticlesInAlphaLayer code. + } +``` +When TeaVM translates booleans, it converts booleans to integers: +- `false` turns into `0` +- `true` turns into `1` + +So when we override `hasParticlesInAlphaLayer`, we'll need to return a `0` or a `1`. Since we want the game to thing that there aren't any particles in the alpha layer, we'll return `0` (false). + +```javascript +(function NoParticles() { + //Basic, boilerplate code + ModAPI.meta.title("No Particles"); + ModAPI.meta.description("Disables all particles in game"); + ModAPI.meta.credits("By "); + + ModAPI.hooks.methods[ + ModAPI.util.getMethodFromPackage("net.minecraft.client.particle.EffectRenderer", "renderParticles") + ] = function () {}; //Override renderParticles in EffectRenderer with an empty function that does nothing. + + ModAPI.hooks.methods[ + ModAPI.util.getMethodFromPackage("net.minecraft.client.particle.EffectRenderer", "addEffect") + ] = function () {}; //Override addEffect in EffectRenderer with an empty function that does nothing. + + ModAPI.hooks.methods[ + ModAPI.util.getMethodFromPackage("net.minecraft.client.particle.EffectRenderer", "addBlockDestroyEffects") + ] = function () {}; //Override addBlockDestroyEffects in EffectRenderer with an empty function that does nothing. + + ModAPI.hooks.methods[ + ModAPI.util.getMethodFromPackage("net.minecraft.client.particle.EffectRenderer", "hasParticlesInAlphaLayer") + ] = function () {return 0}; //Override hasParticlesInAlphaLayer in EffectRenderer with a function that returns 0. +})(); +``` \ No newline at end of file diff --git a/docs/tutorials/hat.md b/docs/tutorials/hat.md new file mode 100644 index 0000000..89c48d2 --- /dev/null +++ b/docs/tutorials/hat.md @@ -0,0 +1,71 @@ +## /hat with ModAPI +/hat is a common server-side plugin that lets you put any block/item on your head. This tutorial will explain how to register a server-side command, construct a packet, and send it to a player. +[`S09PacketHeldItemChange` constructors]() + + +As always, start with the basic boilerplate IIFE with credits: +```javascript +(function HatMod() { + ModAPI.meta.title("Hat Mod"); + ModAPI.meta.description("Use /hat to put anything on your head."); + ModAPI.meta.credits("By "); +})(); +``` +In order to add a server side command, we need to: +- Write a function to run on the server. +- In this function, we'll add a listener to the [`processcommand` event](../apidoc/events.md#server-side-events) +- We'll check if the command is `/hat` +- If it is, we'll swap their head and current hand inventory slots, and send a network packet. +- Finally, the function from step 1 is thrown into [`ModAPI.dedicatedServer.appendCode`](../apidoc/dedicatedserver.md) + +Here's the completed code: +```javascript +(function HatMod() { + ModAPI.meta.title("Hat Mod"); + ModAPI.meta.description("Use /hat to put anything on your head."); + ModAPI.meta.credits("By "); + + ModAPI.dedicatedServer.appendCode(function serverSideCode() { + // Find the constructor for the held item change packet that has only one argument. + // This will be used to notify the client that their hotbar has been updated. + // Reference: https://nurmarvin.github.io/Minecraft-1.8-JavaDocs/net/minecraft/network/play/server/S09PacketHeldItemChange.html + var S09PacketHeldItemChange_Constructor = ModAPI.reflect.getClassByName("S09PacketHeldItemChange").constructors.find(x => x.length === 1); + + //Add an event listener for when a command is processed on the server. + ModAPI.addEventListener("processcommand", (event) => { + // If the command starts with /hat + if (event.command.toLowerCase().startsWith("/hat")) { + // Exit if the sender isn't a player + if ( + !ModAPI.reflect.getClassById("net.minecraft.entity.player.EntityPlayerMP").instanceOf( + event.sender.getRef() + ) + ) { + return + }; + + // Get the current held item + var heldItem = event.sender.inventory.getCurrentItem(); + + // Get the contents of the helmet slot + var armorItem = event.sender.inventory.armorInventory[3]; + + // Get the inventory index of the current held item + var hotbarIdx = event.sender.inventory.currentItem; + + // Set the helmet slot to heldItem.getRef() (raw java object) if heldItem exists, otherwise set it to null + event.sender.inventory.armorInventory[3] = heldItem ? heldItem.getRef() : null; + + // Set the hotbar slot to the original value of the helmet slot if it has a value, otherwise set it to null + event.sender.inventory.mainInventory[hotbarIdx] = armorItem ? armorItem.getRef() : null; + + // Make a packet to notify the client that the selected hotbar slot has been updated. + event.sender.playerNetServerHandler.sendPacket(makePacketItemChange(hotbarIdx)); + + // Prevent the 'unknown command' error + event.preventDefault = true; + } + } + }); +})(); +``` \ No newline at end of file diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md index 1398ebc..5fecc0b 100644 --- a/docs/tutorials/index.md +++ b/docs/tutorials/index.md @@ -11,23 +11,23 @@ Prerequisites: Tutorials: - [Step Hack](step.md) - [Spider Hack](spider.md) -- [VClip Exploit](comingsoon) +- [VClip Exploit](vclip.md) ### Intermediate Prerequisites: - - Basic knowledge of JavaScript + - Intermediate knowledge of JavaScript (functions, arrays, promises, callbacks, keys and values in objects) - A good code editor (recommended: https://vscode.dev) - A copy of the eaglercraft workspace (optional, get it at: https://git.eaglercraft.rip/eaglercraft/eaglercraft-1.8-workspace) Tutorials: -- [Disable All Particles](comingsoon) -- [/hat mod](comingsoon) +- [Disable All Particles](disable_all_particles.md) +- [Slippery Mod](slippery.md) +- [/hat mod](hat.md) - [/spawnxp command](comingsoon) -- [Slippery Mod](comingsoon) ### Advanced Prerequisites: - - Basic knowledge of JavaScript + - Advanced knowledge of JavaScript (prototypes) - A good code editor (recommended: https://vscode.dev) - A copy of the eaglercraft workspace (get it at: https://git.eaglercraft.rip/eaglercraft/eaglercraft-1.8-workspace) - Your EaglerForgeInjector processed.html opened in an editor (optional) diff --git a/docs/tutorials/slippery.md b/docs/tutorials/slippery.md new file mode 100644 index 0000000..7a13370 --- /dev/null +++ b/docs/tutorials/slippery.md @@ -0,0 +1,108 @@ +## Slippery Mod with ModAPI +In this tutorial you will learn how to modify properties of blocks, and run the code that does this on both the dedicated server and the client. + +We'll begin with the basic boilerplate mod code: +```javascript +(function SlipperyMod() { + ModAPI.meta.title("Slippery Mod"); + ModAPI.meta.description("Makes everything turn into ice."); + ModAPI.meta.credits("By "); + + //New code will go here! +})(); +``` + +Let's write the client side part of the code first. +- We'll get the keys for the ModAPI.blocks object (ids of each block) using [`Object.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) +- Then, we'll loop over those keys, and modify their respective block to be as slippery as ice. +```javascript +var blockKeys = Object.keys(ModAPI.blocks); + +blockKeys.forEach(key => { //for each key (block id) + //make sure the block has a slipperiness property + //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining + if(ModAPI.blocks[key]?.slipperiness) { + ModAPI.blocks[key].slipperiness = 0.98; //Set the slipperiness value of the block at that key to 0.98 + } +}); +``` + +Your code should now look like this: +```javascript +(function SlipperyMod() { + ModAPI.meta.title("Slippery Mod"); + ModAPI.meta.description("Makes everything turn into ice."); + ModAPI.meta.credits("By "); + + var blockKeys = Object.keys(ModAPI.blocks); + blockKeys.forEach(key => { + if(ModAPI.blocks[key]?.slipperiness) { + ModAPI.blocks[key].slipperiness = 0.98; + } + }); +})(); +``` + +Your code should now look like this: +```javascript +(function SlipperyMod() { + ModAPI.meta.title("Slippery Mod"); + ModAPI.meta.description("Makes everything turn into ice."); + ModAPI.meta.credits("By "); + + var blockKeys = Object.keys(ModAPI.blocks); + blockKeys.forEach(key => { + if(ModAPI.blocks[key]?.slipperiness) { + ModAPI.blocks[key].slipperiness = 0.98; + } + }); + + //dedicated server code will be added here +})(); +``` + +Currently this only runs on the client meaning in singleplayer, when you throw an item or punch a sheep, it still won't slide. Only you will. + +We'll have to duplicate the code to run on the server using [`ModAPI.dedicatedServer`](../apidoc/dedicatedserver.md). +We also have to run the code after the `serverstart` event, as on the server, `ModAPI.blocks` is only created when it is needed. +```javascript +ModAPI.dedicatedServer.appendCode(function () { + //Code in here cannot reference outside variables. + ModAPI.addEventListener("serverstart", function () { + var blockKeys = Object.keys(ModAPI.blocks); + blockKeys.forEach(key => { + if(ModAPI.blocks[key]?.slipperiness) { + ModAPI.blocks[key].slipperiness = 0.98; + } + }); + }); +}); +``` + +let's add this to the final mod, and we'll be finished! +```javascript +(function SlipperyMod() { + ModAPI.meta.title("Slippery Mod"); + ModAPI.meta.description("Makes everything turn into ice."); + ModAPI.meta.credits("By "); + + var blockKeys = Object.keys(ModAPI.blocks); + blockKeys.forEach(key => { + if(ModAPI.blocks[key]?.slipperiness) { + ModAPI.blocks[key].slipperiness = 0.98; + } + }); + + ModAPI.dedicatedServer.appendCode(function () { + //Code in here cannot reference outside variables. + ModAPI.addEventListener("serverstart", function () { + var blockKeys = Object.keys(ModAPI.blocks); + blockKeys.forEach(key => { + if(ModAPI.blocks[key]?.slipperiness) { + ModAPI.blocks[key].slipperiness = 0.98; + } + }); + }); + }); +})(); +``` \ No newline at end of file diff --git a/docs/tutorials/vclip.md b/docs/tutorials/vclip.md new file mode 100644 index 0000000..7afb570 --- /dev/null +++ b/docs/tutorials/vclip.md @@ -0,0 +1,82 @@ +## VClip Exploit with ModAPI +Most minecraft servers have an exploit where you can trick the server into clipping you up to 10 blocks up or down through ceilings or floors. + +### Part 1: Setup + +Make a new file in your editor, and call it something like `vclip.js` +First, let's setup a basic [IIFE (immediately invoked function expression)](https://developer.mozilla.org/en-US/docs/Glossary/IIFE) around our mod code. This ensures that we don't leak variables that can interfere wiht other mods.\ +Example of the issue: if mod A and mod B both use `var myVariable = 0`, the value for myVariable will get overwritten. + +```javascript +(function VClipExploit() { + //Future code here +})(); //We define a function, called VClipExploit, which we wrap in parantheses () and immediately execute. +``` +This allows us to use variables without worrying about mod compatibility, as variables are scoped to the function. + +\ +Then, we'll add some basic [metadata](../apidoc/meta.md) for the mod loader (note that this is optional, but makes the mod look a lot better in the GUI once the game is loaded.)\ +We'll also require the player, so the `ModAPI.player` global is generated. + +```javascript +(function VClipExploit() { + ModAPI.meta.title("VClip Exploit"); + ModAPI.meta.description("todo: add description."); + ModAPI.meta.credits("By author_name"); + + ModAPI.require("player"); + + +})(); +``` + +### Part 2: VClip Exploit +Our VClip exploit will be triggered by a client-side command, `.vclip `. +Let's start off with the vclip command by adding an event listener to when the player sends a chat message: +```javascript +(function VClipExploit() { + ModAPI.meta.title("VClip Exploit"); + ModAPI.meta.description("todo: add description."); + ModAPI.meta.credits("By author_name"); + + ModAPI.require("player"); + + ModAPI.addEventListener("sendchatmessage", (ev) => { + // handler code here + }); +})(); +``` +In this event handler, we can get the content of the message by using `event.message`, process it, and check whether it is using the `.vclip` command. We can do this by casting the string to lowercase, using [`string.toLowerCase()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase), checking if the result of that is a call to the `.vclip` command using [`string.startsWith()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith). + +If it is a call to the VClip command, we can use [`string.split(" ")`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split) to get all the arguments, find the vclip amount, and change the player's position. + +```javascript +(function VClipExploit() { + ModAPI.meta.title("VClip Exploit"); + ModAPI.meta.description("todo: add description."); + ModAPI.meta.credits("By author_name"); + + ModAPI.require("player"); + + ModAPI.addEventListener("sendchatmessage", (ev) => { + var string = ev.message.toLowerCase(); //Get the lower case version of the command + if (string.startsWith(".vclip")) { //does the chat message start with .vclip? + ev.preventDefault = true; //we don't want this being sent into chat as a message + var yOffset = 1; //The offset on the y axis + var args = string.split(" "); + if (args[1]) { //If the second argument to .vclip exists (the vclip ) + yOffset = parseFloat(args[1]) || 0; //Convert the second argument into a number. We use || to replace NaN (invalid numbers) with 0. Then, store it into the y offset. + } //This allows you to just type .vclip to clip upwards 1 block. + + ModAPI.player.setPosition( //This function sets the players position to an XYZ coordinate + ModAPI.player.posX, + ModAPI.player.posY + yOffset, //All XYZ elements are the same, except we add the yOffset variable to the y axis. + ModAPI.player.posZ + ); + + //Finally, log the amount we've VClipped into the chat. + ModAPI.displayToChat("[VClip] VClipped " + yOffset + " blocks."); + } + }); +})(); +``` \ No newline at end of file diff --git a/examplemods/hats.js b/examplemods/hats.js index 2c663f9..43744de 100644 --- a/examplemods/hats.js +++ b/examplemods/hats.js @@ -9,9 +9,6 @@ ModAPI.dedicatedServer.appendCode(function () { // This will be used to notify the client that their hotbar has been updated. var makePacketItemChange = ModAPI.reflect.getClassByName("S09PacketHeldItemChange").constructors.find(x => x.length === 1); - // Find the method for sending packets. - var sendPacket = ModAPI.reflect.getClassByName("NetHandlerPlayServer").methods.sendPacket.method; - // When the server is processing a command ModAPI.addEventListener("processcommand", (event) => { // If the command starts with /hat @@ -35,7 +32,7 @@ ModAPI.dedicatedServer.appendCode(function () { event.sender.inventory.mainInventory[hotbarIdx] = armorItem ? armorItem.getRef() : null; // Use the sendPacket method to send a item change packet to the client. - sendPacket(event.sender.playerNetServerHandler.getRef(), makePacketItemChange(hotbarIdx)); + event.sender.playerNetServerHandler.sendPacket(makePacketItemChange(hotbarIdx)); event.preventDefault = true; } diff --git a/examplemods/no_particles.js b/examplemods/no_particles.js index cd202de..997d619 100644 --- a/examplemods/no_particles.js +++ b/examplemods/no_particles.js @@ -1,4 +1,4 @@ ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.client.particle.EffectRenderer", "renderParticles")] = ()=>{}; -ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.client.particle.EffectRenderer", "hasParticlesInAlphaLayer")] = ()=>{return 0}; ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.client.particle.EffectRenderer", "addEffect")] = ()=>{}; -ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.client.particle.EffectRenderer", "addBlockDestroyEffects")] = ()=>{}; \ No newline at end of file +ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.client.particle.EffectRenderer", "addBlockDestroyEffects")] = ()=>{}; +ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.client.particle.EffectRenderer", "hasParticlesInAlphaLayer")] = ()=>{return 0}; \ No newline at end of file diff --git a/examplemods/slippery.js b/examplemods/slippery.js index ac1a902..7cb81a0 100644 --- a/examplemods/slippery.js +++ b/examplemods/slippery.js @@ -9,7 +9,6 @@ ModAPI.dedicatedServer.appendCode(()=>{ //Add version of the code to the dedicat blockKeys.forEach(key=>{ //Loop through all the identifiers if(ModAPI?.blocks?.[key]?.slipperiness) {// TeaVM likes to add metadata properties which are `null` or `undefined` ModAPI.blocks[key].slipperiness = 0.98; //Ice slipperiness value. - ModAPI.blocks[key].reload();// load the value } }); }); @@ -18,6 +17,5 @@ var blockKeys = Object.keys(ModAPI.blocks); //Get keys (identifiers) of all the blockKeys.forEach(key=>{ //Loop through all the identifiers if(ModAPI?.blocks?.[key]?.slipperiness) {// TeaVM likes to add metadata properties which are `null` or `undefined` ModAPI.blocks[key].slipperiness = 0.98; //Ice slipperiness value. - ModAPI.blocks[key].reload();// load the value } });