Skip to content

Commit

Permalink
Merge pull request #12 from eaglerforge/main
Browse files Browse the repository at this point in the history
Update stable to latest build
  • Loading branch information
ZXMushroom63 authored Sep 22, 2024
2 parents cce5305 + 76ca61e commit d0853bc
Show file tree
Hide file tree
Showing 14 changed files with 210 additions and 816 deletions.
15 changes: 13 additions & 2 deletions docs/quirks.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## Quirks in TeaVM
## TeaVM Quirks and Caveats
When TeaVM compiles code, it sometimes does strange things.

#### Property Suffixes
Expand All @@ -14,4 +14,15 @@ 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.

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.
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.

Update 22/09/2024:
See Asynchronous Code

#### 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
- Setting blocks in the world in some occasions

Potential workarounds: This isn't confirmed yet, but there is a probable chance that overriding or patching methods in classes like VFile2 or PlatformFilesystem is a viable workaround. (22/09/2024).
I'll be verifying this is the future and if it is possible, releasing a library for it. (the maybe-library is going to be called AsyncSink if it will exist)
3 changes: 2 additions & 1 deletion examplemods/npcspawner.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@

// Get the EntityPlayerMP class to spawn the fake player
const EntityPlayerMPClass = ModAPI.reflect.getClassById("net.minecraft.entity.player.EntityPlayerMP");
console.log(ModAPI.server.getConfigurationManager());
const fakePlayer = EntityPlayerMPClass.constructors[0](
world.getMinecraftServer(), world.getRef(), fakeProfile, playerInteractionManager
ModAPI.server.getRef(), world.getRef(), fakeProfile, playerInteractionManager
);

// Set the fake player position to be near the command sender
Expand Down
63 changes: 63 additions & 0 deletions examplemods/threadtesting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//SUCCESS - While there is no TeaVM thread actively running, I am able to run an asyncronous function, and get a result.
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) })


//WIP - Pausing and resuming client thread
globalThis.suspendTest = function (...args) {
if (!ModAPI.util.isCritical()) {
var thread = ModAPI.hooks._teavm.$rt_nativeThread();
var javaThread = ModAPI.hooks._teavm.$rt_getThread();
globalThis.testThread = thread;
console.log("BeforeAnything: ", thread.stack);
thread.suspend(function () {
console.log("Pausing for 10 seconds.", thread.stack);
setTimeout(function () {
console.log("Resuming...", thread.stack);
ModAPI.hooks._teavm.$rt_setThread(javaThread);
thread.resume();
console.log("After resume: ", thread.stack);
}, 10000);
});
}
return suspendTest.apply(this, args);
}





function jl_Thread_sleep$_asyncCall_$(millis) {
var thread = $rt_nativeThread();
var javaThread = $rt_getThread();
var callback = function () { };
callback.$complete = function (val) {
thread.attribute = val;
$rt_setThread(javaThread);
thread.resume();
};
callback.$error = function (e) {
thread.attribute = $rt_exception(e);
$rt_setThread(javaThread);
thread.resume();
};
callback = otpp_AsyncCallbackWrapper_create(callback);
thread.suspend(function () {
try {
jl_Thread_sleep0(millis, callback);
} catch ($e) {
callback.$error($rt_exception($e));
}
});
return null;
}
function jl_Thread_sleep0($millis, $callback) {
var $current, $handler;
$current = jl_Thread_currentThread();
$handler = new jl_Thread$SleepHandler;
$handler.$thread = $current;
$handler.$callback = $callback;
$handler.$scheduleId = otp_Platform_schedule($handler, Long_ge($millis, Long_fromInt(2147483647)) ? 2147483647 : Long_lo($millis));
$current.$interruptHandler = $handler;
}
14 changes: 11 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ <h6>
<label class="custom-file-label" for="htmlFile"
>Choose .html file...</label
>
<br />
<span><code id="status">Awaiting input...</code></span>
<br /><br />
<button class="btn btn-primary" id="giveme">Make modded client</button>
<button class="btn btn-primary" id="givemeserver" disabled>
Expand Down Expand Up @@ -123,7 +125,9 @@ <h6>
</details>
<details>
<summary>Roadmap?</summary>
<a href="https://eaglerforge.github.io/EaglerForgeInjector/roadmap/">roadmap.</a>
<a href="https://eaglerforge.github.io/EaglerForgeInjector/roadmap/"
>roadmap.</a
>
</details>
<details>
<summary>How does this tool work?</summary>
Expand All @@ -143,7 +147,9 @@ <h6>
</div>
</div>

<script src="filesaver.min.js"></script>
<!-- Libraries -->
<script src="libs/babel.min.js"></script>
<script src="libs/filesaver.min.js"></script>

<script>
document.querySelector("#htmlFile").addEventListener("input", (e) => {
Expand All @@ -166,10 +172,12 @@ <h6>
`;
var freezeCallstack = `if(ModAPI.hooks.freezeCallstack){return false};`;
</script>
<script src="injector.stepAsync.js"></script>
<script src="injector.js"></script>

<!-- Code assets -->
<script src="postinit.injector.js"></script>
<script src="postinit.js"></script>
<script src="postinit.async.js"></script>
<script src="modloader.injector.js"></script>
<script src="modgui.injector.js"></script>
<script src="efserver.js"></script>
Expand Down
59 changes: 44 additions & 15 deletions injector.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
function wait(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => { resolve(); }, ms);
});
}
function _status(x) {
document.querySelector("#status").innerText = x;
}
function entriesToStaticVariableProxy(entries, prefix) {
var getComponents = "";
entries.forEach((entry) => {
Expand Down Expand Up @@ -45,7 +53,9 @@ function entriesToStaticVariableProxy(entries, prefix) {
});`;
return proxy;
}
function processClasses(string) {
async function processClasses(string) {
_status("Beginning patch process...");
await wait(50);
var patchedFile = string;
patchedFile = patchedFile.replaceAll(
`(function(root, module) {`,
Expand All @@ -57,6 +67,10 @@ function processClasses(string) {
`${modapi_preinit}
var main;(function(){`
);

_status("Patching threads and reflect metadata...");

await wait(50);
patchedFile = patchedFile
.replace("\r", "")
.replace(
Expand Down Expand Up @@ -95,15 +109,10 @@ var main;(function(){`
}
);

patchedFile = patchedFile.replace(
` id="game_frame">`,
` id="game_frame">
\<script id="modapi_postinit"\>${globalThis.modapi_postinit}\<\/script\>
\<script id="modapi_modloader"\>${globalThis.modapi_modloader}\<\/script\>
\<script id="modapi_guikit"\>${globalThis.modapi_guikit}\<\/script\>
\<script id="modapi_postinit_data"\>globalThis.modapi_postinit = \`${globalThis.modapi_postinit}\`;\<\/script\>
\<script id="libserverside"\>{"._|_libserverside_|_."}\<\/script\>`
);
patchedFile = patchedFile.replaceAll("function TeaVMThread(", "globalThis.ModAPI.hooks.TeaVMThread = TeaVMThread;\nfunction TeaVMThread(");

_status("Extracting constructors and methods...");
await wait(50);

const extractConstructorRegex =
/^\s*function (\S*?)__init_\d*?\((?!\$)/gm;
Expand Down Expand Up @@ -208,7 +217,8 @@ var main;(function(){`
return proxy + "\n" + match;
}
);

_status("Extracting teavm internals...");
await wait(50);
patchedFile = patchedFile.replaceAll(
/function \$rt_\S+?\(/gm,
(match) => {
Expand All @@ -220,9 +230,28 @@ var main;(function(){`
);
}
);

_status("Applying async override...");
await wait(50);

//patchedFile = await asyncify(patchedFile);
_status("Injecting scripts...");
await wait(50);
patchedFile = patchedFile.replace(
` id="game_frame">`,
` id="game_frame">
\<script id="modapi_postinit"\>${globalThis.modapi_postinit}\<\/script\>
\<script id="modapi_postinitasync"\>${globalThis.modapi_postinitasync}\<\/script\>
\<script id="modapi_modloader"\>${globalThis.modapi_modloader}\<\/script\>
\<script id="modapi_guikit"\>${globalThis.modapi_guikit}\<\/script\>
\<script id="modapi_postinit_data"\>globalThis.modapi_postinit = \`${globalThis.modapi_postinit}\`;\<\/script\>
\<script id="libserverside"\>{"._|_libserverside_|_."}\<\/script\>`
);
patchedFile = patchedFile.replaceAll(/main\(\);\s*?}/gm, (match) => {
return match.replace("main();", "main();ModAPI.hooks._postInit();");
});
_status("Done, awaiting input...");
await wait(50);
return patchedFile;
}

Expand All @@ -238,8 +267,8 @@ document.querySelector("#giveme").addEventListener("click", () => {
var fileType = file.name.split(".");
fileType = fileType[fileType.length - 1];

file.text().then((string) => {
var patchedFile = processClasses(string);
file.text().then(async (string) => {
var patchedFile = await processClasses(string);
patchedFile.replace(`{"._|_libserverside_|_."}`)
var blob = new Blob([patchedFile], { type: file.type });
saveAs(blob, "processed." + fileType);
Expand All @@ -258,8 +287,8 @@ document.querySelector("#givemeserver").addEventListener("click", () => {
var fileType = file.name.split(".");
fileType = fileType[fileType.length - 1];

file.text().then((string) => {
var patchedFile = processClasses(string);
file.text().then(async (string) => {
var patchedFile = await processClasses(string);
patchedFile.replace(`{"._|_libserverside_|_."}`, `(${EFServer.toString()})()`);
var blob = new Blob([patchedFile], { type: file.type });
saveAs(blob, "efserver." + fileType);
Expand Down
61 changes: 61 additions & 0 deletions injector.stepAsync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Babel plugin to transform functions and calls
const ASYNC_PLUGIN_1 = function ({ types: t }) {
return {
visitor: {
FunctionDeclaration(path) {
console.log(path);
path.node.async = true;
},
ArrowFunctionExpression(path) {
console.log(path);
path.node.async = true;
},
CallExpression(path) {
console.log(path);
if (path.parent.type !== 'AwaitExpression') {
path.replaceWith(
t.awaitExpression(path.node)
);
}
}
}
};
};
async function asyncify(input) {
let isHtml = true;
let inputHtml = input;

// Check if the input is raw JavaScript
if (!input.trim().startsWith('<')) {
isHtml = false;
inputHtml = `<script>${input}</script>`;
}

_status("[ASYNC_PLUGIN_1] Parsing html...");
await wait(50);
const parser = new DOMParser();
const doc = parser.parseFromString(inputHtml, 'text/html');
const scriptTags = doc.querySelectorAll('script');

for (let i = 0; i < scriptTags.length; i++) {
const scriptTag = scriptTags[i];
const code = scriptTag.textContent;
_status("[ASYNC_PLUGIN_1] Transpiling script #" + (i + 1) + " of length " + Math.round(code.length / 1000) + "k...");
await wait(50);


const output = Babel.transform(code, {
plugins: [ASYNC_PLUGIN_1]
});
scriptTag.textContent = output.code;
}

_status("[ASYNC_PLUGIN_1] Job complete!");
await wait(50);

if (isHtml) {
return doc.documentElement.outerHTML;
} else {
return doc.querySelector('script').textContent;
}
}
2 changes: 2 additions & 0 deletions libs/babel.min.js

Large diffs are not rendered by default.

File renamed without changes.
4 changes: 4 additions & 0 deletions postinit.async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
globalThis.modapi_postinitasync = "(" + (() => {
//EaglerForge post initialization code for async.

}).toString() + ")();";
Loading

0 comments on commit d0853bc

Please sign in to comment.