From 195a50315c39531a248f75bee360dba7ffcad91f Mon Sep 17 00:00:00 2001 From: ZXMushroom63 Date: Fri, 13 Sep 2024 18:50:13 +0800 Subject: [PATCH] Fix more stack implosion occurences --- docs/apidoc/index.md | 2 +- docs/apidoc/utils.md | 11 ++++++++--- docs/quirks.md | 5 ++++- examplemods/blocklook.js | 31 ++++++++++++++++++++++--------- postinit.injector.js | 9 +++++++++ postinit.js | 9 +++++++++ 6 files changed, 53 insertions(+), 14 deletions(-) diff --git a/docs/apidoc/index.md b/docs/apidoc/index.md index 95e7f8e..76a4a95 100644 --- a/docs/apidoc/index.md +++ b/docs/apidoc/index.md @@ -4,7 +4,7 @@ The EaglerForge ModAPI is housed in a global JavaScript object stored on `global The global object has the following properties: - `ModAPI.player: EntityPlayerSP` - Only accessible after `ModAPI.require("player")` is called, this is the local player entity. It is regenerated every time the `update` event is called. -- `ModAPI.world: WorldClient` + - `ModAPI.world: WorldClient` - Only accessible after `ModAPI.require("world")` is called, this is the client-side world. It is regenerated every time the `update` event is called. - `ModAPI.network: NetHandlerPlayClient` - Only accessible after `ModAPI.require("network")` is called, this is the client's networking handler. It is regenerated every time the `update` event is called. diff --git a/docs/apidoc/utils.md b/docs/apidoc/utils.md index 35e2440..5e9533f 100644 --- a/docs/apidoc/utils.md +++ b/docs/apidoc/utils.md @@ -24,11 +24,16 @@ Methods: - Alias: `ModAPI.util.ustr()` - Alias: `ModAPI.util.unstring()` - Alias: `ModAPI.util.jclStrToJsStr()` -- `ModAPI.util.getMethodFromPackage(classId: String, methodName: String)` +- `ModAPI.util.getMethodFromPackage(classId: String, methodName: String) : String` - Takes a class id (eg: `net.minecraft.client.Minecraft`) and a method name (eg: `middleClickMouse`) and returns its key in `ModAPI.hooks.methods`. - `ModAPI.util.stringToUint16Array(string: String) : Uint16Array` - Encodes a string into a uint16array. - `ModAPI.util.setStringContent(jclString: java.lang.String, contents: String) : void` - Writes a new javascript string into the contents of a java string. -- `ModAPI.util.getMethodFromPackage(classId: String, methodName: String)` - - Takes a class id (eg: `net.minecraft.client.Minecraft`) and a method name (eg: `middleClickMouse`) and returns its key in `ModAPI.hooks.methods`. \ No newline at end of file +- `ModAPI.util.getMethodFromPackage(classId: String, methodName: String) : String` + - Takes a class id (eg: `net.minecraft.client.Minecraft`) and a method name (eg: `middleClickMouse`) and returns its key in `ModAPI.hooks.methods`. +- `ModAPI.util.hashCode(string: String) : String` + - Returns the hash of a string. +- `ModAPI.util.isCritical() : boolean` + - Checks wether the thread is in a critical state. + - When patching methods, it is good practice to allow the method to resume as usual if this is `true`, to avoid stack implosions. (yes, those are real) \ No newline at end of file diff --git a/docs/quirks.md b/docs/quirks.md index ea05c72..6f9b156 100644 --- a/docs/quirks.md +++ b/docs/quirks.md @@ -11,4 +11,7 @@ When I was trying to hook into the server-side processing of chat messages, I fo Incorrectly patching methods with ModAPI.hooks (such as returning `false` instead of `0`, or a javascript string without using `ModAPI.str()`) will effectively cause the memory stack to implode, along with all crash handlers. I came across this when I was using the `processcommand` event with preventDefault set to true. I didn't return any value when patching methods for the event being `preventDefault`ed, when the output expected was a java boolean (`0`/`1`). This would cause the dedicated server to freeze/lock up, without triggering any form of crash. Update 13/09/2024: -Any form of incorrect data type, even passing the wrong values, can cause this sort of hang. I encountered this when trying to set a block in the world to any form of wood or leaf block, without adding iproperties to the tree type. \ No newline at end of file +Any form of incorrect data type, even passing the wrong values, can cause this sort of hang. I encountered this when trying to set a block in the world to any form of wood or leaf block, without adding iproperties to the tree type. + +Update 13/09/2024: +Calling methods while the TeaVM thread is in a critical transition state (see `ModAPI.util.isCritical()`) will shift the call stack, cause methods to access the incorrect values at runtime, and also cause the stack to implode. Gotta love TeaVM. \ No newline at end of file diff --git a/examplemods/blocklook.js b/examplemods/blocklook.js index 6c339ed..b68004e 100644 --- a/examplemods/blocklook.js +++ b/examplemods/blocklook.js @@ -8,8 +8,16 @@ ModAPI.dedicatedServer.appendCode(function () { var rayTraceMethod = worldMethodMap[Object.keys(worldMethodMap).filter(key => { return key.startsWith("rayTraceBlocks") && worldMethodMap[key].method.length === 4; })].method; - var blockPosConstructor = ModAPI.reflect.getClassById("net.minecraft.util.BlockPos").constructors.find((x) => { return x.length === 3 }); - var blockTypesList = Object.keys(ModAPI.blocks); + var blockTypesList = []; + ModAPI.addEventListener("serverstart", () => { + blockTypesList = Object.keys(ModAPI.blocks).filter(key => { + var blockType = ModAPI.blocks[key]; + if (!blockType) { + return false; + } + return blockType.fullBlock && !blockType.needsRandomTick; + }); + }); function getPlayerEntitiesAndTheirWorld() { var out = []; ModAPI.server.worldServers.forEach(x => { @@ -31,11 +39,12 @@ ModAPI.dedicatedServer.appendCode(function () { ModAPI.addEventListener("processcommand", (event) => { if (event.command.toLowerCase().startsWith("/blocklook")) { active = !active; + console.log(blockTypesList); var playerEntities = getPlayerEntitiesAndTheirWorld(); playerEntities.forEach(pair => { pair.player.addChatMessage( ModAPI.reflect.getClassById("net.minecraft.util.ChatComponentText").constructors[0](ModAPI.util.str( - "[BlockLook] Toggled to " + (active ? "on" : off) + "[BlockLook] Toggled to " + (active ? "on" : "off") )) ) }); @@ -45,7 +54,7 @@ ModAPI.dedicatedServer.appendCode(function () { var t = 0; ModAPI.addEventListener("tick", () => { t++; - if (t > 20) { + if (t > 5) { t = 0; } else { return; @@ -53,6 +62,9 @@ ModAPI.dedicatedServer.appendCode(function () { if (!active) { return; } + if (blockTypesList.length < 1) { + return; + } var playerEntities = getPlayerEntitiesAndTheirWorld(); playerEntities.forEach(pair => { var start = pair.player.getPositionEyes(1).getRef(); @@ -63,21 +75,22 @@ ModAPI.dedicatedServer.appendCode(function () { lookVector.addVector(start.$xCoord, start.$yCoord, start.$zCoord); var hitResult = rayTraceMethod(pair.world.getRef(), start, lookVector.getRef(), 0); if (hitResult) { - console.log(hitResult); + if (ModAPI.util.unstr(hitResult.$typeOfHit.$name5) !== "BLOCK") { + return console.log("Non block collision detected.") + } var blockPos = hitResult.$blockPos; if (!pair.world.isBlockLoaded(blockPos)) { - console.log("[BlockLook] Block is not loaded!"); + return console.log("[BlockLook] Block is not loaded!"); } var blockType = blockTypesList[Math.floor(Math.random() * blockTypesList.length)]; blockType = ModAPI.blocks[blockType]; - if (!blockType.fullBlock) { + if (!blockType.fullBlock || blockType.needsRandomTick) { return; } console.log("[BlockLook] " + ModAPI.util.unstr(blockType.unlocalizedName.getRef())); var block = blockType.getDefaultState(); pair.world.setBlockState(blockPos, block.getRef(), 2); - pair.world.notifyNeighborsRespectDebug(blockPos, block.getRef()); } }); - }); + }) }); \ No newline at end of file diff --git a/postinit.injector.js b/postinit.injector.js index 55093c5..aecff35 100644 --- a/postinit.injector.js +++ b/postinit.injector.js @@ -509,6 +509,12 @@ globalThis.modapi_postinit = `(() => { return Math.floor(Math.abs(hash)) + ""; }; + //Check whether the thread is in a critical transition state (resuming or suspending) + //Calling functions in a critical state will cause stack implosions. + ModAPI.util.isCritical = function isCritical() { + return ModAPI.hooks._teavm.$rt_suspending() || ModAPI.hooks._teavm.$rt_resuming(); + } + ModAPI.clickMouse = function () { ModAPI.hooks.methods["nmc_Minecraft_clickMouse"](ModAPI.javaClient); } @@ -573,6 +579,9 @@ globalThis.modapi_postinit = `(() => { const serverTickMethodName = ModAPI.util.getMethodFromPackage("net.minecraft.server.MinecraftServer", "tick"); const serverTickMethod = ModAPI.hooks.methods[serverTickMethodName]; ModAPI.hooks.methods[serverTickMethodName] = function ($this) { + if (ModAPI.util.isCritical()) { + return serverTickMethod.apply(this, [$this]); + } var data = { preventDefault: false } ModAPI.events.callEvent("tick", data); if (data.preventDefault) { diff --git a/postinit.js b/postinit.js index 0444bc7..ff687dd 100644 --- a/postinit.js +++ b/postinit.js @@ -509,6 +509,12 @@ return Math.floor(Math.abs(hash)) + ""; }; + //Check whether the thread is in a critical transition state (resuming or suspending) + //Calling functions in a critical state will cause stack implosions. + ModAPI.util.isCritical = function isCritical() { + return ModAPI.hooks._teavm.$rt_suspending() || ModAPI.hooks._teavm.$rt_resuming(); + } + ModAPI.clickMouse = function () { ModAPI.hooks.methods["nmc_Minecraft_clickMouse"](ModAPI.javaClient); } @@ -573,6 +579,9 @@ const serverTickMethodName = ModAPI.util.getMethodFromPackage("net.minecraft.server.MinecraftServer", "tick"); const serverTickMethod = ModAPI.hooks.methods[serverTickMethodName]; ModAPI.hooks.methods[serverTickMethodName] = function ($this) { + if (ModAPI.util.isCritical()) { + return serverTickMethod.apply(this, [$this]); + } var data = { preventDefault: false } ModAPI.events.callEvent("tick", data); if (data.preventDefault) {