Merge pull request #24 from eaglerforge/main

massive update
This commit is contained in:
ZXMushroom63 2024-10-04 22:06:41 +08:00 committed by GitHub
commit e94c747a6c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 458 additions and 161 deletions

View File

@ -94,6 +94,9 @@ The ModAPI object has the following methods:
- Triggers a right click ingame.
- `getFPS() : int`
- Gets the frames per second of the game
- `promisify(asyncJavaMethod: Method | Constructor) : PromisifiedJavaRunner`
- Allows running java methods that are @Async/@Async dependent.
- More [PromisifyDocumentation](promisify.md)
## Handling strings, numbers and booleans to and from java.
@ -142,3 +145,25 @@ For example, take the method `setRenderViewEntity()` on `ModAPI.mcinstance`. Ins
var entityIndex = 1; //Index of the entity to look for. 0 means first, which is usually the player, so 1 is usually a natural entity.
ModAPI.mc.setRenderViewEntity(ModAPI.world.loadedEntityList.get(entityIndex).getRef());
```
## Corrective Proxies
By default, accessing a global like `ModAPI.player` will return a proxy to the original player that removes $ prefixes, as well as making instance methods callable. TeaVM has a quirk where it adds numerical suffixes to some properties. For example `ModAPI.player.inGround0` instead of `ModAPI.player.inGround`. As this is a large issue due to these suffixes changing for every eaglercraft update, you can now bypass this by obtaining a corrective version of `ModAPI.player`, using `ModAPI.player.getCorrective()`.
For example:
```javascript
ModAPI.player.inGround //returns undefined
ModAPI.player.inGround0 //returns 1 or 0, the correct value
ModAPI.player.isCorrective() //returns false
var correctedPlayer = ModAPI.player.getCorrective();
correctedPlayer.inGround //returns 1 or 0, the correct value
correctedPlayer.inGround0 //returns 1 or 0, the correct value
correctedPlayer.isCorrective() //returns true
```
You can check if an object is corrective using `<object>.isCorrective()`;
Accessing children of a corrective object will also make them corrective. `correctedPlayer.fishEntity.isCorrective(); //true`

26
docs/apidoc/promisify.md Normal file
View File

@ -0,0 +1,26 @@
## ModAPI.promisify()
Some methods in java are asynchronous, meaning that they don't return a value/modify state immediately. Calling them in an event or in a patch to a normal function will cause a stack implosion, characterised by the client/dedicated server hanging without any error messages.
In order to call them properly from javascript, you need to use the `ModAPI.promisify()` function.
For example, here we have a simple client-side command that will try to use the `PlatformRuntime` class to download data from a URI:
```javascript
ModAPI.addEventListener("sendchatmessage", function downloadSomething(e) {
if (e.message.toLowerCase().startsWith("/downloadtest")) {
var arraybuffer = ModAPI.hooks.methods.nlevi_PlatformRuntime_downloadRemoteURI(ModAPI.util.str("data:text/plain,hi"));
console.log(arraybuffer);
}
});
```
This will cause the client to hang. The correct way of calling this asynchronous method is like this:
```javascript
ModAPI.addEventListener("sendchatmessage", function downloadSomething(e) {
if (e.message.toLowerCase().startsWith("/downloadtest")) {
ModAPI.promisify(ModAPI.hooks.methods.nlevi_PlatformRuntime_downloadRemoteURI)(ModAPI.util.str("data:text/plain,hi")).then(arraybuffer => {
console.log(arraybuffer);
});
}
});
```
You can replace the argument with any other method or constructor, including non asynchronous ones.

View File

@ -2,7 +2,7 @@
When TeaVM compiles code, it sometimes does strange things.
#### Property Suffixes
TeaVM will add suffixes to some variables, seemingly randomly. An example is the property `inGround` of any entity. When accessing this on the `ModAPI.player.fishEntity` object, TeaVM has renamed it to `inGround2`. Can be mitigated with `ModAPI.util.getNearestProperty`.
TeaVM will add suffixes to some variables, seemingly randomly. An example is the property `inGround` of any entity. When accessing this on the `ModAPI.player.fishEntity` object, TeaVM has renamed it to `inGround2`. Can be mitigated with `ModAPI.util.getNearestProperty`, or, even better, using a corrective version of ModAPI.player.
#### Collapsing Methods
When I was trying to hook into the server-side processing of chat messages, I found that chat packets were handled by the method `processChatMessage` in `NetHandlerPlayServer`. However, in the compiled JavaScript, this method no longer exists. This is because it is only used once, in the `processPacket` method of `C01PacketChatMessage`. TeaVM automatically saw this, and collapsed one method into the other.
@ -19,6 +19,9 @@ Calling methods while the TeaVM thread is in a critical transition state (see `M
Update 22/09/2024:
See Asynchronous Code
Update 4/10/2024:
@Async issue solved, see [PromisifyDocumentation](apidoc/promisify.md)
#### TeaVM thread suspension/resumption
TeaVM allows for writing asynchronous callbacks, which eaglercraft uses for file operations and downloading from URIs. However, when a method that makes use of an async callback gets run from ModAPI, it triggers a stack implosion due to mismatches in value types upon return (as well as a whole other myriad of symptoms). Currently this is not supported by ModAPI, and it will take some time until it will be. In the meanwhile, avoid using constructors or methods that access a file or use other asynchronous apis. Examples:
- Constructing an EntityPlayerMP

View File

@ -0,0 +1,10 @@
## Mod tutorials with ModAPI
Prerequisites:
- Basic knowledge of JavaScript
- A good code editor (recommended: https://vscode.dev)
### Beginner
- [Step Hack](step.md)
- [Spider Hack](spider.md)
- [VClip Exploit](comingsoon)

41
docs/tutorials/spider.md Normal file
View File

@ -0,0 +1,41 @@
## Spider Hack with ModAPI
A spider hack allows players to climb up any wall like a spider.
Start by creating a new file in your code editor. Save it to a folder on your device, ensuring that the file extension is `.js`.
The spider hack has a lot of similarities to the step hack, so lets copy the step hack and rename the `stepHackUpdateCode` function to `spiderHackUpdateCode`:
```javascript
ModAPI.require("player");
function spiderHackUpdateCode() {
//We will add code here.
}
ModAPI.addEventListener("update", spiderHackUpdateCode);
```
Most spider hacks work by checking if the player is walking into a wall, and then setting their vertical velocity to a constant amount (usually `0.2`, for parity with ladders).
Let's start by checking if the player is walking into a wall, by adding an if statement inside the `spiderHackUpdateCode` function:
```javascript
if (ModAPI.player.isCollidedHorizontally) {
}
```
Now, let's set the player's vertical velocity:
```javascript
if (ModAPI.player.isCollidedHorizontally) {
ModAPI.player.motionY = 0.2; //Feel free to change this value to something bigger, smaller or even negative.
}
```
Time to see the final code:
```javascript
ModAPI.require("player");
function spiderHackUpdateCode() {
if (ModAPI.player.isCollidedHorizontally) {
ModAPI.player.motionY = 0.2; //Feel free to change this value to something bigger, smaller or even negative.
}
}
ModAPI.addEventListener("update", spiderHackUpdateCode);
```

33
docs/tutorials/step.md Normal file
View File

@ -0,0 +1,33 @@
## Step Hack with ModAPI
A step hack allows a player to walk up full blocks (or more) as if they were slabs.
Start by creating a new file in your code editor. Save it to a folder on your device, ensuring that the file extension is `.js`.
Let's start by requiring the player. This will allow us to change attributes on the player:
```javascript
ModAPI.require("player");
```
Now, we have to detect when the player in a world. This will be done with the `update` event, which runs every tick while the player is in a world. In EaglerForge's ModAPI, to run code on an event, you have to create a function. Then, you register the function to an event.
```javascript
ModAPI.require("player");
function stepHackUpdateCode() {
//We will add code here.
}
ModAPI.addEventListener("update", stepHackUpdateCode);
```
Inside this method, lets change the player's `stepHeight`, which controls how high they can step. By default this is `0.5`, to alow players to walk up slabs or stairs. I'll change it to `2` for demonstration purposes. You can also try any other number, like `0`, `6`, etc.
```javascript
ModAPI.require("player");
function stepHackUpdateCode() {
ModAPI.player.stepHeight = 2;
}
ModAPI.addEventListener("update", stepHackUpdateCode);
```
Now, to load this mod, open your EaglerForge build, and in the start screen select `Upload Mod (.js)`. Upload the mod you created, and you should now be able to walk up two block tall pillars, or more depending on what `stepHeight` you selected.
Disclaimer: using this on servers may get you banned/kicked for cheating!

View File

@ -6,19 +6,56 @@
ModAPI.require("player");
ModAPI.require("world");
var tessellator = ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.client.renderer.Tessellator", "getInstance")]();
var worldRenderer = tessellator.$getWorldRenderer();
var glStateManagerSetColor = ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager", "color")];
var glStateManagerEnableBlend = ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager", "enableBlend")];
var glStateManagerDisableBlend = ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager", "disableBlend")];
var glStateManagerdisableTex2d = ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager", "disableTexture2D")];
var glStateManagerenableTex2d = ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager", "enableTexture2D")];
var glStateManagerdisableDepth = ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager", "disableDepth")];
var glStateManagerenableDepth = ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager", "enableDepth")];
var glStateManagerSetBlend = ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager", "blendFunc")];
var glStateManagerDepthMask = ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager", "depthMask")];
var eaglercraftGPUSetLineWidth = ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.opengl.EaglercraftGPU", "glLineWidth")];
var positionVertexFormat = ModAPI.reflect.getClassByName("DefaultVertexFormats").staticVariables.POSITION;
globalThis.drawLine = function drawLine(positions, color) {
glStateManagerSetBlend(770, 771);
glStateManagerEnableBlend();
eaglercraftGPUSetLineWidth(2);
glStateManagerdisableTex2d();
glStateManagerdisableDepth();
glStateManagerDepthMask(0);
var renderManager = ModAPI.mc.getRenderManager();
glStateManagerSetColor(color.r, color.b, color.g, color.a);
worldRenderer.$begin(3, positionVertexFormat);
positions.forEach(pos => {
worldRenderer.$pos(pos.x - renderManager.renderPosX, pos.y - renderManager.renderPosY, pos.z - renderManager.renderPosZ).$endVertex();
});
tessellator.$draw();
glStateManagerenableTex2d();
glStateManagerDepthMask(1);
glStateManagerenableDepth();
glStateManagerDisableBlend();
}
var blockBreakCostMultiplier = 2;
const costMap = Object.fromEntries(Object.keys(ModAPI.blocks).flatMap(x => {
ModAPI.blocks[x].readableId = x;
return [x, (Math.floor(ModAPI.blocks[x].blockHardness * 10 * blockBreakCostMultiplier) + 1) || 99999]; //Block hardness is in decimals, unbreakable blocks are negative one, and base movement cost is 1. -1 + 1 = 0, and 0 || 99999 is 99999
return [[x, (Math.floor(ModAPI.blocks[x].blockHardness * 10 * blockBreakCostMultiplier) + 1) || 99999]]; //Block hardness is in decimals, unbreakable blocks are negative one, and base movement cost is 1. -1 + 1 = 0, and 0 || 99999 is 99999
}));
var makeBlockPos = ModAPI.reflect.getClassById("net.minecraft.util.BlockPos").constructors.find(x=>x.length === 3);
var makeBlockPos = ModAPI.reflect.getClassById("net.minecraft.util.BlockPos").constructors.find(x => x.length === 3);
function shouldPause(x, y, z) {
return !ModAPI.world.isAreaLoaded0(makeBlockPos(x, y, z), 2);
}
class APNode {
globalThis.APNode = class APNode {
constructor(x, y, z, g, h, parent = null) {
this.x = x;
this.y = y;
@ -59,7 +96,7 @@
return costMap[block.readableId];
}
function* aStar(start, goal) {
globalThis.aStar = function* aStar(start, goal) {
const openSet = [];
const closedSet = new Set();
openSet.push(start);
@ -108,9 +145,30 @@
return [];
}
const start = new APNode(0, 0, 0, 0, 0);
const goal = new APNode(2, 2, 2, 0, 0);
const pathGenerator = aStar(start, goal);
})();
var start = new APNode(-24, 73, 1, 0, 0);
var goal = new APNode(-30, 73, 1, 0, 0);
var pathGenerator = aStar(start, goal);
var positions = [];
var rendererPositions = [];
var timer = 0;
ModAPI.addEventListener("update", ()=>{
timer++;
if (timer > 20) {
timer = 0;
} else {
return;
}
if (positions.length > 0 && shouldPause(...positions[positions.length - 1])) {
return;
}
var nextPos = pathGenerator.next();
if (nextPos.value && nextPos.value.length === 3) {
positions.push(nextPos.value);
rendererPositions.push({x: nextPos.value[0] + 0.5, y: nextPos.value[1] + 0.5, z: nextPos.value[2] + 0.5});
}
});
ModAPI.addEventListener("render", () => {
drawLine(rendererPositions, { r: 1, g: 0, b: 0, a: 0.5 })
});

View File

@ -1,25 +1,27 @@
PluginAPI.require("player"); //Require the player
var GrappleHookPlugin = {
(function grapplehook() {
PluginAPI.require("player"); //Require the player
var GrappleHookPlugin = {
oldXYZ: [0, 0, 0], //The previous hook position.
prev: "NONE", //The previous state
scaleH: 0.25, //Used for X and Z velocity
scaleV: 0.15, //((Grapple Y) minus (Player Y)) times scaleV
lift: 0.4, //Base vertical motion
crouchToCancel: true //Whether or not crouching should disable the grappling hook.
};
PluginAPI.addEventListener("update", () => { //Every client tick
if (!PluginAPI.player.fishEntity) { //If the fish hook does not exist.
if (GrappleHookPlugin.prev === "GROUND" && (!GrappleHookPlugin.crouchToCancel || !PluginAPI.player.isSneaking())) { //If the old state was ground
};
var player = ModAPI.player.getCorrective(); //Gets the corrective version of the player object. This removes broken proerty suffixes. You usually don't need this, but in my case, I do.
PluginAPI.addEventListener("update", () => { //Every client tick
if (!player.fishEntity) { //If the fish hook does not exist.
if (GrappleHookPlugin.prev === "GROUND" && (!GrappleHookPlugin.crouchToCancel || !player.isSneaking())) { //If the old state was ground
GrappleHookPlugin.prev = "NONE"; //Update the state
var mx = GrappleHookPlugin.oldXYZ[0] - PluginAPI.player.posX; //Get delta X
var my = GrappleHookPlugin.oldXYZ[1] - PluginAPI.player.posY; //Get delta Y
var mz = GrappleHookPlugin.oldXYZ[2] - PluginAPI.player.posZ; //Get delta Z
var mx = GrappleHookPlugin.oldXYZ[0] - player.posX; //Get delta X
var my = GrappleHookPlugin.oldXYZ[1] - player.posY; //Get delta Y
var mz = GrappleHookPlugin.oldXYZ[2] - player.posZ; //Get delta Z
mx *= GrappleHookPlugin.scaleH; //Multiply by horizontal scale
my *= GrappleHookPlugin.scaleV; //Multiply by vertical scale
mz *= GrappleHookPlugin.scaleH; //Multiply by horizontal scale
PluginAPI.player.motionX += mx; //Add x motion
PluginAPI.player.motionY += my + GrappleHookPlugin.lift; //Add y motion, plus base lift.
PluginAPI.player.motionZ += mz; //Add z motion
player.motionX += mx; //Add x motion
player.motionY += my + GrappleHookPlugin.lift; //Add y motion, plus base lift.
player.motionZ += mz; //Add z motion
} else {
GrappleHookPlugin.prev = "NONE";
}
@ -27,15 +29,16 @@ PluginAPI.addEventListener("update", () => { //Every client tick
GrappleHookPlugin.prev = "AIR";
}
if (
PluginAPI.player.fishEntity !== undefined && //If the fish hook exists
player.fishEntity !== undefined && //If the fish hook exists
GrappleHookPlugin.prev === "AIR" && //And the hook was previously in the air
PluginAPI.player.fishEntity[PluginAPI.util.getNearestProperty(ModAPI.player.fishEntity, "inGround")] //And the hook is in the ground (the inGround property is botched with random suffixes sometimes)
player.fishEntity.inGround //And the hook is in the ground
) {
GrappleHookPlugin.oldXYZ = [ //Set old grapple hook position
PluginAPI.player.fishEntity.posX,
PluginAPI.player.fishEntity.posY,
PluginAPI.player.fishEntity.posZ,
player.fishEntity.posX,
player.fishEntity.posY,
player.fishEntity.posZ,
];
GrappleHookPlugin.prev = "GROUND";//Update state
}
});
});

View File

@ -4,8 +4,8 @@
// Check if the sender is a player
if (!ModAPI.reflect.getClassById("net.minecraft.entity.player.EntityPlayerMP").instanceOf(event.sender.getRef())) { return; }
// Check if the command is "/spawnnpc"
if (event.command.toLowerCase().startsWith("/spawnnpc")) {
// Check if the command is "/spawnsheep"
if (event.command.toLowerCase().startsWith("/spawnsheep")) {
const world = event.sender.getServerForPlayer();
const senderPos = event.sender.getPosition();
@ -17,21 +17,28 @@
sheep.$setLocationAndAngles(senderPos.getX(), senderPos.getY(), senderPos.getZ(), senderPos.rotationYaw, senderPos.rotationPitch);
// Disable AI (no AI behavior)
sheep.$getDataWatcher().$updateObject(15, 1); // AI flag, 15 is the byte for AI, 1 means no AI
//sheep.$setNoAI(1)
// Disable gravity
sheep.$noGravity = 1;
//sheep.$noGravity = 1;
// Make sheep invincible
sheep.$setEntityInvulnerable(1);
//sheep.$invulnerable = 1
if (globalThis.AsyncSink) { //If we can, start the AsyncSink debugger to see filesystem requests
AsyncSink.startDebuggingFS();
}
// Add the sheep to the world
world.spawnEntityInWorld(sheep);
ModAPI.promisify(ModAPI.hooks.methods.nmw_World_spawnEntityInWorld)(world.getRef(), sheep).then(result => {
// Notify the player that the sheep has been spawned
const ChatComponentTextClass = ModAPI.reflect.getClassById("net.minecraft.util.ChatComponentText");
event.sender.addChatMessage(ChatComponentTextClass.constructors[0](ModAPI.util.str("A special sheep has been spawned!")));
if (globalThis.AsyncSink) { //Stop debugging when we are done, otherwise the console will get filled up.
AsyncSink.stopDebuggingFS();
}
});
// Prevent the command from executing further
event.preventDefault = true;

View File

@ -1,28 +1,10 @@
(() => {
PluginAPI.dedicatedServer.appendCode(function () {
var ready = false;
var killFS = false;
function setup_filesystem_middleware() {
if (!ready) {
AsyncSink.MIDDLEWARE.push((ev)=>{
if (killFS) {
ev.shim = true;
if (typeof ev.shimOutput === "boolean") {
ev.shimOutput = true;
}
}
});
ready = true;
}
}
PluginAPI.addEventListener("processcommand", (event) => {
if (!ModAPI.reflect.getClassById("net.minecraft.entity.player.EntityPlayerMP").instanceOf(event.sender.getRef())) { return; }
if (event.command.toLowerCase().startsWith("/spawnnpc")) {
if (!globalThis.AsyncSink) {
return console.error("NPC Spawner relies on the AsyncSink library.");
}
setup_filesystem_middleware();
if (event.command.toLowerCase().startsWith("/spawnnpc2")) {
AsyncSink.startDebuggingFS();
const world = event.sender.getServerForPlayer();
const senderPos = event.sender.getPosition();
@ -39,12 +21,10 @@
const playerInteractionManager = PlayerInteractionManagerClass.constructors[0](world.getRef());
// Get the EntityPlayerMP class to spawn the fake player
killFS = true;
const EntityPlayerMPClass = ModAPI.reflect.getClassById("net.minecraft.entity.player.EntityPlayerMP");
const fakePlayer = ModAPI.util.wrap(EntityPlayerMPClass.constructors[0](
ModAPI.server.getRef(), world.getRef(), fakeProfile, playerInteractionManager
));
killFS = false;
ModAPI.promisify(EntityPlayerMPClass.constructors[0])(ModAPI.server.getRef(), world.getRef(), fakeProfile, playerInteractionManager).then(result => {
console.log(result);
var fakePlayer = ModAPI.util.wrap(result);
// Set the fake player position to be near the command sender
console.log(senderPos);
@ -56,6 +36,8 @@
// Notify the player that the fake player has been spawned
const ChatComponentTextClass = ModAPI.reflect.getClassById("net.minecraft.util.ChatComponentText");
event.sender.addChatMessage(ChatComponentTextClass.constructors[0](ModAPI.util.str("Fake Steve has been spawned!")));
});
// Prevent the command from executing further
event.preventDefault = true;

View File

@ -3,6 +3,11 @@ ModAPI.hooks._teavm.$rt_startThread(() => {
return ModAPI.hooks.methods.nlevi_PlatformRuntime_downloadRemoteURI(ModAPI.util.str("data:text/plain,hi"))
}, function (...args) { console.log(this, args) })
// SUCCESS - Runs anywhere, anytime. Might work with async/await, but for now stick to .then()
ModAPI.promisify(ModAPI.hooks.methods.nlevi_PlatformRuntime_downloadRemoteURI)(ModAPI.util.str("data:text/plain,hi")).then(result => {
console.log(result); //Log arraybuffer
});
//WIP - Pausing and resuming client thread
globalThis.suspendTest = function (...args) {

36
examplemods/xpspawner.js Normal file
View File

@ -0,0 +1,36 @@
(() => {
PluginAPI.dedicatedServer.appendCode(function () {
PluginAPI.addEventListener("processcommand", (event) => {
// Check if the sender is a player
if (!ModAPI.reflect.getClassById("net.minecraft.entity.player.EntityPlayerMP").instanceOf(event.sender.getRef())) { return; }
// Check if the command is "/spawnsheep"
if (event.command.toLowerCase().startsWith("/spawnxp")) {
const world = event.sender.getServerForPlayer();
const senderPos = event.sender.getPosition();
// Create a sheep entity
const EntityXP = ModAPI.reflect.getClassByName("EntityXPOrb");
const xporb = EntityXP.constructors[0](world.getRef(), senderPos.getX(), senderPos.getY(), senderPos.getZ(), 10);
if (globalThis.AsyncSink) { //If we can, start the AsyncSink debugger to see filesystem requests
AsyncSink.startDebuggingFS();
}
// Add the sheep to the world
ModAPI.promisify(ModAPI.hooks.methods.nmw_World_spawnEntityInWorld)(world.getRef(), xporb).then(result => {
// Notify the player that the sheep has been spawned
const ChatComponentTextClass = ModAPI.reflect.getClassById("net.minecraft.util.ChatComponentText");
event.sender.addChatMessage(ChatComponentTextClass.constructors[0](ModAPI.util.str("An xp orb has been spawned!")));
if (globalThis.AsyncSink) { //Stop debugging when we are done, otherwise the console will get filled up.
AsyncSink.stopDebuggingFS();
}
});
// Prevent the command from executing further
event.preventDefault = true;
}
});
});
})();

View File

@ -175,6 +175,7 @@
`;
var freezeCallstack = `if(ModAPI.hooks.freezeCallstack){return false};`;
</script>
<script src="patches.js"></script>
<script src="injector.minify.js"></script>
<script src="injector.js"></script>

View File

@ -263,6 +263,10 @@ var main;(function(){`
}
);
_status("Applying bonus patches from patch registry...");
await wait(50);
patchedFile = PatchesRegistry.patchFile(patchedFile);
if (globalThis.doShronk) {
_status("Shrinking file...");
await wait(50);
@ -276,6 +280,7 @@ var main;(function(){`
patchedFile = patchedFile.replace(
` id="game_frame">`,
` id="game_frame">
\<script id="modapi_patchesreg_events"\>${PatchesRegistry.getEventInjectorCode()};\<\/script\>
\<script id="modapi_postinit"\>${globalThis.modapi_postinit}\<\/script\>
\<script id="modapi_postinitasync"\>${globalThis.modapi_postinitasync}\<\/script\>
\<script id="modapi_modloader"\>${globalThis.modapi_modloader}\<\/script\>
@ -286,6 +291,7 @@ var main;(function(){`
patchedFile = patchedFile.replaceAll(/main\(\);\s*?}/gm, (match) => {
return match.replace("main();", "main();ModAPI.hooks._postInit();");
});
_status("Done, awaiting input...");
await wait(50);
return patchedFile;

38
patches.js Normal file
View File

@ -0,0 +1,38 @@
class PatchesRegistry {
static patchFns = []
static patchedEventNames = []
static getEventInjectorCode() {
return "globalThis.modapi_specialevents = [" + PatchesRegistry.patchedEventNames.flatMap(x=>`\`${x}\``).join(",") + "];";
}
static patchFile(x) {
var current = x;
PatchesRegistry.patchFns.forEach(fn => {
current = fn(current);
});
return current;
}
static addPatch(fn) {
PatchesRegistry.patchFns.push(fn);
}
static regSpecialEvent(x) {
PatchesRegistry.patchedEventNames.push(x);
}
}
// PatchesRegistry.regSpecialEvent("test");
// PatchesRegistry.addPatch(function (input) {
// var output = input;
// return output;
// })
PatchesRegistry.regSpecialEvent("render");
PatchesRegistry.addPatch(function (input) {
var output = input.replaceAll(
/continue main;\s+?}\s+?if\s?\(!\$this.\$renderHand\)/gm
,
`continue main;
}
ModAPI.events.callEvent("render",{partialTicks:$partialTicks})
if (!$this.$renderHand)`
);
return output;
})

View File

@ -114,20 +114,34 @@ globalThis.modapi_postinit = "(" + (() => {
});
return name;
}
ModAPI.util.wrap = function (outputValue, target) {
ModAPI.util.wrap = function (outputValue, target, corrective, disableFunctions) {
const CorrectiveArray = patchProxyConfToCorrective(ModAPI.util.TeaVMArray_To_Recursive_BaseData_ProxyConf);
const CorrectiveRecursive = patchProxyConfToCorrective(ModAPI.util.TeaVM_to_Recursive_BaseData_ProxyConf);
if (outputValue && typeof outputValue === "object" && Array.isArray(outputValue.data) && typeof outputValue.type === "function") {
if (corrective) {
return new Proxy(outputValue.data, CorrectiveArray);
}
return new Proxy(outputValue.data, ModAPI.util.TeaVMArray_To_Recursive_BaseData_ProxyConf);
}
if (outputValue && typeof outputValue === "object" && !Array.isArray(outputValue)) {
if (corrective) {
return new Proxy(outputValue.data, CorrectiveRecursive);
}
return new Proxy(outputValue, ModAPI.util.TeaVM_to_Recursive_BaseData_ProxyConf);
}
if (outputValue && typeof outputValue === "function" && target) {
if (!disableFunctions && outputValue && typeof outputValue === "function" && target) {
return function (...args) {
var xOut = outputValue.apply(target, args);
if (xOut && typeof xOut === "object" && Array.isArray(xOut.data) && typeof outputValue.type === "function") {
if (corrective) {
return new Proxy(outputValue.data, CorrectiveArray);
}
return new Proxy(xOut.data, ModAPI.util.TeaVMArray_To_Recursive_BaseData_ProxyConf);
}
if (xOut && typeof xOut === "object" && !Array.isArray(xOut)) {
if (corrective) {
return new Proxy(outputValue.data, CorrectiveRecursive);
}
return new Proxy(xOut, ModAPI.util.TeaVM_to_Recursive_BaseData_ProxyConf);
}
return xOut;
@ -186,7 +200,7 @@ globalThis.modapi_postinit = "(" + (() => {
ModAPI.version = "v2.0";
ModAPI.flavour = "injector";
ModAPI.GNU = "terry pratchett";
ModAPI.credits = ["ZXMushroom63", "radmanplays", "OtterCodes101", "TheIdiotPlays"];
ModAPI.credits = ["ZXMushroom63", "radmanplays", "Murturtle", "OtterCodes101", "TheIdiotPlays", "OeildeLynx31", "Stpv22"];
ModAPI.hooks.regenerateClassMap = function () {
ModAPI.hooks._rippedConstructorKeys = Object.keys(ModAPI.hooks._rippedConstructors);
ModAPI.hooks._rippedMethodKeys = Object.keys(ModAPI.hooks._rippedMethodTypeMap);
@ -295,60 +309,18 @@ globalThis.modapi_postinit = "(" + (() => {
return key ? ModAPI.hooks._classMap[key] : null;
}
var reloadDeprecationWarnings = 0;
const TeaVM_to_BaseData_ProxyConf = {
ownKeys(target) {
return Reflect.ownKeys(target).flatMap(x => x.substring(1));
},
getOwnPropertyDescriptor(target, prop) {
return Object.getOwnPropertyDescriptor(target, "$" + prop);
},
has(target, prop) {
return ("$" + prop) in target;
},
get(target, prop, receiver) {
if (prop === "getRef") {
return function () {
return target;
}
}
if (prop === "reload") {
return function () {
if (reloadDeprecationWarnings < 10) {
console.warn("ModAPI/PluginAPI reload() is obsolete, please stop using it in code.")
reloadDeprecationWarnings++;
}
}
}
var outProp = "$" + prop;
var outputValue = Reflect.get(target, outProp, receiver);
if (outputValue && typeof outputValue === "object" && Array.isArray(outputValue.data) && typeof outputValue.type === "function") {
return outputValue.data;
}
if (outputValue && typeof outputValue === "function") {
return function (...args) {
return outputValue.apply(target, args);
}
}
return outputValue;
},
set(object, prop, value) {
var outProp = "$" + prop;
object[outProp] = value;
return true;
},
};
const TeaVMArray_To_Recursive_BaseData_ProxyConf = {
get(target, prop, receiver) {
var outputValue = Reflect.get(target, prop, receiver);
if (outputValue && typeof outputValue === "object" && !Array.isArray(outputValue)) {
return new Proxy(outputValue, TeaVM_to_Recursive_BaseData_ProxyConf);
}
if (prop === "getRef") {
return function () {
return target;
}
}
var outputValue = Reflect.get(target, prop, receiver);
var wrapped = ModAPI.util.wrap(outputValue, target, this._corrective, true);
if (wrapped) {
return wrapped;
}
return outputValue;
},
set(object, prop, value) {
@ -367,6 +339,16 @@ globalThis.modapi_postinit = "(" + (() => {
return ("$" + prop) in target;
},
get(target, prop, receiver) {
if (prop === "getCorrective") {
return function () {
return new Proxy(target, patchProxyConfToCorrective(TeaVM_to_Recursive_BaseData_ProxyConf));
}
}
if (prop === "isCorrective") {
return function () {
return !!this._corrective;
}
}
if (prop === "getRef") {
return function () {
return target;
@ -382,8 +364,11 @@ globalThis.modapi_postinit = "(" + (() => {
}
var outProp = "$" + prop;
if (this._corrective) {
outProp = ModAPI.util.getNearestProperty(target, outProp);
}
var outputValue = Reflect.get(target, outProp, receiver);
var wrapped = ModAPI.util.wrap(outputValue, target);
var wrapped = ModAPI.util.wrap(outputValue, target, this._corrective, false);
if (wrapped) {
return wrapped;
}
@ -395,6 +380,11 @@ globalThis.modapi_postinit = "(" + (() => {
return true;
},
};
function patchProxyConfToCorrective(conf) {
var pconf = Object.assign({}, conf);
pconf._corrective = true;
return pconf;
}
const StaticProps_ProxyConf = {
get(target, prop, receiver) {
var outProp = prop;
@ -413,7 +403,6 @@ globalThis.modapi_postinit = "(" + (() => {
return true;
},
};
ModAPI.util.TeaVM_to_BaseData_ProxyConf = TeaVM_to_BaseData_ProxyConf;
ModAPI.util.TeaVMArray_To_Recursive_BaseData_ProxyConf = TeaVMArray_To_Recursive_BaseData_ProxyConf;
ModAPI.util.TeaVM_to_Recursive_BaseData_ProxyConf = TeaVM_to_Recursive_BaseData_ProxyConf;
ModAPI.util.StaticProps_ProxyConf = StaticProps_ProxyConf;
@ -554,6 +543,23 @@ globalThis.modapi_postinit = "(" + (() => {
ModAPI.hooks.freezeCallstack = false;
}
//Function used for running @Async / @Async-dependent TeaVM methods.
ModAPI.promisify = function promisify(fn) {
return function promisifiedJavaMethpd(...inArguments) {
return new Promise((res, rej) => {
Promise.resolve().then( //queue microtask
() => {
ModAPI.hooks._teavm.$rt_startThread(() => {
return fn(...inArguments);
}, function (out) {
res(out);
});
}
);
});
}
}
ModAPI.util.string = ModAPI.util.str = ModAPI.hooks._teavm.$rt_str;
ModAPI.util.setStringContent = function (jclString, string) {
@ -678,6 +684,17 @@ globalThis.modapi_postinit = "(" + (() => {
return x;
};
var desktopServerStartup = ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.sp.internal.ClientPlatformSingleplayer", "startIntegratedServer");
const desktopServerStartupMethod = ModAPI.hooks.methods[desktopServerStartup];
ModAPI.hooks.methods[desktopServerStartup] = function (...args) {
var x = desktopServerStartupMethod.apply(this, args);
ModAPI.dedicatedServer._data.forEach((code) => {
(new Function(code))();
});
console.log("[ModAPI] Hooked into external integrated server.");
return x;
};
ModAPI.events.newEvent("load", "client");
ModAPI.events.newEvent("sendchatmessage", "client");
@ -871,4 +888,10 @@ globalThis.modapi_postinit = "(" + (() => {
return originalMainMethod.apply(this, args);
}
}
if (globalThis.modapi_specialevents && Array.isArray(globalThis.modapi_specialevents)) {
globalThis.modapi_specialevents.forEach(eventName => {
ModAPI.events.newEvent(eventName, "patcher");
});
}
}).toString() + ")();";