diff --git a/backgroundLogger.js b/backgroundLogger.js
new file mode 100644
index 0000000..e2398bd
--- /dev/null
+++ b/backgroundLogger.js
@@ -0,0 +1,32 @@
+var backgroundLogs = document.createElement("div");
+backgroundLogs.style = `
+ color: lime;
+ opacity: 0.1;
+ font-family: monospace;
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ z-index: -1;
+ pointer-events: none;
+ overflow: none;
+ user-select: none;
+ `;
+const bgLogsList = [];
+document.documentElement.appendChild(backgroundLogs);
+var dirty = true;
+function backgroundLog(text, unSuppress) {
+ var linesExcess = backgroundLogs.scrollHeight - window.innerHeight;
+ for (i = 0; i < linesExcess; i++) {
+ bgLogsList.shift();
+ }
+ bgLogsList.push(text);
+ dirty = true;
+ if (!unSuppress) {
+ return;
+ }
+ dirty = false;
+ backgroundLogs.innerText = bgLogsList.join("\n");
+}
+backgroundLog("Awaiting input...");
\ No newline at end of file
diff --git a/docs/apidoc/index.md b/docs/apidoc/index.md
index 8f1d0dd..d1665ca 100644
--- a/docs/apidoc/index.md
+++ b/docs/apidoc/index.md
@@ -99,7 +99,10 @@ The ModAPI object has the following methods:
- 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)
+ - More: [PromisifyDocumentation](promisify.md)
+ - `addCredit(category: String, contributor: String, contents: String)`
+ - Lets you easily add credits to Eaglercraft's credits.txt
+ - eg: `ModAPI.addCredit("My Cool Mod", "Username", " - Coded the mod\n - Wrote somne credits")`
## Handling strings, numbers and booleans to and from java.
diff --git a/docs/apidoc/meta.md b/docs/apidoc/meta.md
index f226829..346d030 100644
--- a/docs/apidoc/meta.md
+++ b/docs/apidoc/meta.md
@@ -10,4 +10,8 @@ Methods:
- Sets the description of the mod. Character limit of 160.
- `ModAPI.meta.icon(iconURL: String)`
- Sets the icon of the mod.
- - It can be extremely low res, it will not appear blurry.
\ No newline at end of file
+ - It can be extremely low res, it will not appear blurry.
+- `ModAPI.meta.version(versionCode: String)`
+ - Sets the version of the mod. Appended after the title.
+- `ModAPI.meta.config(configFn: Function)`
+ - Once the client is fully loaded, creates a button in the mod manager GUI that runs the specified function when pressed.
\ No newline at end of file
diff --git a/docs/apidoc/promisify.md b/docs/apidoc/promisify.md
index 46dd7ec..195e108 100644
--- a/docs/apidoc/promisify.md
+++ b/docs/apidoc/promisify.md
@@ -31,7 +31,7 @@ var asyncDownloadRemoteURI = ModAPI.promisify(ModAPI.hooks.methods.nlevi_Platfor
console.log(typeof asyncDownloadRemoteURI); //Logs function
```
-When it is called, like any other asyncronoush function, it returns a `Promise` object.
+When it is called, like any other asyncronous function, it returns a `Promise` object.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
You can replace the argument with any other method or constructor, including non asynchronous ones.
\ No newline at end of file
diff --git a/index.html b/index.html
index 6032025..7b73985 100644
--- a/index.html
+++ b/index.html
@@ -90,8 +90,12 @@
>Choose .html file...
-
-
+
+
+
+
+
+
Awaiting input...
@@ -163,6 +167,7 @@
`;
var freezeCallstack = `if(ModAPI.hooks.freezeCallstack){return false};`;
+
diff --git a/injector.js b/injector.js
index fcf1995..13ce07d 100644
--- a/injector.js
+++ b/injector.js
@@ -1,4 +1,4 @@
-globalThis.ModAPIVersion = "v2.7";
+globalThis.ModAPIVersion = "v2.7.3";
globalThis.doEaglerforge = true;
document.querySelector("title").innerText = `EaglerForge Injector ${ModAPIVersion}`;
document.querySelector("h1").innerText = `EaglerForge Injector ${ModAPIVersion}`;
@@ -8,6 +8,7 @@ function wait(ms) {
});
}
function _status(x) {
+ backgroundLog(x, true);
document.querySelector("#status").innerText = x;
}
function entriesToStaticVariableProxy(entries, prefix, clinitList) {
@@ -77,6 +78,7 @@ async function processClasses(string) {
if (!confirm("The minify step is extremely slow, especially on lower-end devices, and can take upwards of 15 minutes.")) {
return;
}
+ backgroundLog("[MINIFY] Minify warning bypassed.");
}
_status("Beginning patch process...");
await wait(50);
@@ -101,6 +103,7 @@ var main;(function(){`
"var main;\n(function() {",
modapi_preinit + "var main;\n(function() {"
);
+ backgroundLog("[JSPATCH] Adding pre-init script");
patchedFile = patchedFile.replace(
/function \$rt_metadata\(data\)( ?){/gm,
`function $rt_metadata(data) {
@@ -108,20 +111,21 @@ var main;(function(){`
ModAPI.hooks._rippedData.push(data);
/*/EaglerForge Client Patch/*/`
);
-
+ backgroundLog("[JSPATCH] Redirecting $rt_metadata to ModAPI.hooks._rippedData");
patchedFile = patchedFile.replaceAll(
`return thread != null && thread.isResuming()`,
(match) => {
return freezeCallstack + match;
}
);
-
+ backgroundLog("[JSPATCH] Freeze-callstack patch on TeaVMThread.isResuming()");
patchedFile = patchedFile.replaceAll(
`return thread != null && thread.isSuspending();`,
(match) => {
return freezeCallstack + match;
}
);
+ backgroundLog("[JSPATCH] Freeze-callstack patch on TeaVMThread.isSuspending()");
patchedFile = patchedFile.replaceAll(
`return $rt_currentNativeThread;`,
@@ -132,12 +136,12 @@ var main;(function(){`
);
}
);
+ backgroundLog("[JSPATCH] Freeze-callstack patch thread getter");
patchedFile = patchedFile.replaceAll("function TeaVMThread(", "globalThis.ModAPI.hooks.TeaVMThread = TeaVMThread;\nfunction TeaVMThread(");
_status("Getting clinit list...");
var clinitList = [...patchedFile.matchAll(/^[\t ]*function \S+?_\S+?_\$callClinit\(/gm)].map(x => x[0].replaceAll("function ", "").replaceAll("(", "").trim());
- console.log(clinitList);
_status("Extracting constructors and methods...");
await wait(50);
@@ -158,6 +162,8 @@ var main;(function(){`
}
);
+ backgroundLog("-> Extract contructor 1");
+
const extractInternalConstructorRegex =
/^\s*function (\S*?)__init_\d*?\(\$this/gm; //same as extract constructor regex, but only allow $this as first argument
patchedFile = patchedFile.replaceAll(
@@ -172,6 +178,8 @@ var main;(function(){`
}
);
+ backgroundLog("-> Extract contructor 2");
+
const extractInstanceMethodRegex =
/^[\t ]*function \S+?_\S+?_\S+?\((\$this)?/gm; // /^[\t ]*function \S+?_\S+?_\S+?\(\$this/gm
const extractInstanceMethodFullNameRegex = /function (\S*?)\(/gm; // /function (\S*?)\(\$this/gm
@@ -198,6 +206,10 @@ var main;(function(){`
);
}
);
+
+ backgroundLog("-> Extract instance methods");
+ backgroundLog("-> Expose instance methods");
+
var staticVariables = [
...patchedFile.matchAll(/var \S+?_\S+?_\S+? = /gm),
].flatMap((x) => {
@@ -205,6 +217,7 @@ var main;(function(){`
}).filter(x => {
return (!x.includes("$_clinit_$")) && (!x.includes("$lambda$"))
});
+ backgroundLog("-> Extract static variables");
//Also stores classes from $rt_classWithoutFields(0)
patchedFile = patchedFile.replaceAll(
/var \S+?_\S+? = \$rt_classWithoutFields\(\S*?\);/gm,
@@ -240,6 +253,7 @@ var main;(function(){`
);
//Edge cases. sigh
//Done: add support for static properties on classes with constructors like this: function nmcg_GuiMainMenu() {
+ backgroundLog("-> Expose static variables");
patchedFile = patchedFile.replaceAll(
@@ -307,10 +321,13 @@ var main;(function(){`
\`;
}
- _status("[ASYNC_PLUGIN_1] Parsing html...");
+ _status("[MINIFY] Parsing html...");
await wait(50);
const parser = new DOMParser();
const doc = parser.parseFromString(inputHtml, 'text/html');
@@ -40,18 +34,20 @@ async function shronk(input) {
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...");
+ _status("[MINIFY] Transpiling script #" + (i + 1) + " of length " + Math.round(code.length / 1000) + "k...");
await wait(150);
const output = Babel.transform(code, {
- plugins: []
+ plugins: globalThis.doShronkPlus ? [
+ MINIFY()
+ ] : []
});
scriptTag.textContent = output.code;
await wait(10);
}
- _status("[ASYNC_PLUGIN_1] Job complete!");
+ _status("[MINIFY] Job complete!");
await wait(50);
if (isHtml) {
diff --git a/modgui.js b/modgui.js
index 3eb1d96..6b58b16 100644
--- a/modgui.js
+++ b/modgui.js
@@ -10,7 +10,9 @@ globalThis.modapi_guikit = "(" + (() => {
"hey you should check out https://github.com/ZXMushroom63/scratch-gui",
"99% of people stop gambling before they win big.",
"Now with free estradiol!",
- "Now with H.I.V (Hyper Injected Virtual-debugger)"
+ "Now with H.I.V (Hyper Injected Virtual-debugger)",
+ "asdasd",
+ "Star us on GitHub to support us! https://github.com/EaglerForge/EaglerForgeInjector"
];
var gui = `
@@ -184,7 +186,8 @@ globalThis.modapi_guikit = "(" + (() => {
cb ||= document.querySelector("#modapi_gui_container")._cb;
document.querySelector("#modapi_gui_container").remove();
}
-
+
+
var element = document.createElement("div");
element.innerHTML = gui.replace("{splash_msg}", splashes[Math.floor(Math.random() * splashes.length)]);
@@ -245,15 +248,28 @@ globalThis.modapi_guikit = "(" + (() => {
spacer.classList.add("nothing");
var controls = document.createElement("td");
- var button = document.createElement("button");
- button.innerText = "Delete";
- button.style.height = "3rem";
- button.addEventListener("click", async () => {
+ var deleteBtn = document.createElement("button");
+ deleteBtn.innerText = "Delete";
+ deleteBtn.style.height = "3rem";
+ deleteBtn.addEventListener("click", async () => {
await removeMod(i);
window.modapi_displayModGui();
});
- button.classList.add("button");
- controls.appendChild(button);
+ deleteBtn.classList.add("button");
+ controls.appendChild(deleteBtn);
+
+ if (typeof ModAPI.meta._configMap[hash] === "function") {
+ var configBtn = document.createElement("button");
+ configBtn.innerText = "Config";
+ configBtn.style.height = "3rem";
+ configBtn.style.marginLeft = "1rem";
+ configBtn.addEventListener("click", async () => {
+ ModAPI.meta._configMap[hash]();
+ });
+ configBtn.classList.add("button");
+ controls.appendChild(configBtn);
+ }
+
tr.appendChild(mod);
tr.appendChild(spacer);
tr.appendChild(controls);
diff --git a/postinit.js b/postinit.js
index 53b8868..f75e2df 100644
--- a/postinit.js
+++ b/postinit.js
@@ -3,6 +3,7 @@ globalThis.modapi_postinit = "(" + (() => {
//This script cannot contain backticks, escape characters, or backslashes in order to inject into the dedicated server code.
var startedModLoader = false;
var BACKSLASH = String.fromCharCode(92);
+ var LF = String.fromCharCode(10);
var STRIP_COMMENTS = new RegExp(atob("KChcL1wvLiokKXwoXC9cKltcc1xTXSo/XCpcLykp"), "gm");
var ARGUMENT_NAMES = new RegExp(atob("KFteXHMsXSsp"), "g");
@@ -21,17 +22,66 @@ globalThis.modapi_postinit = "(" + (() => {
ModAPI.meta = {};
ModAPI.meta._titleMap = {};
ModAPI.meta._descriptionMap = {};
+ ModAPI.meta._configMap = {};
ModAPI.meta._developerMap = {};
ModAPI.meta._iconMap = {};
ModAPI.meta._versionMap = {};
+ const credits = {};
+ ModAPI.addCredit = function (category, name, contents) {
+ if (!credits[category]) {
+ credits[category] = [];
+ }
+ credits[category].push(LF + LF + " " + name + ": " + LF + LF + contents);
+ }
+ function getCreditsString() {
+ return Object.entries(credits).map((entry) => {
+ return " "+entry[0] + LF + " " + (new Array(entry[0].length)).fill("~").join("") + entry[1].join("") + LF + LF + LF;
+ }).join("");
+ }
ModAPI.array = {};
ModAPI.version = "__modapi_version_code__";
ModAPI.flavour = "injector";
ModAPI.GNU = "terry pratchett";
- ModAPI.credits = ["ZXMushroom63", "radmanplays", "Murturtle", "OtterCodes101", "TheIdiotPlays", "OeildeLynx31", "Stpv22"];
+
+ ModAPI.addCredit("EaglerForge Devs", "ZXMushroom63",
+ " - Built the original PluginAPI for EaglerReborn" + LF +
+ " - Built EaglerForgeInjector as a procedural replacement for EaglerForge clients" + LF +
+ " - Made the mod loader and gui loader" + LF +
+ " - Added singleplayer support" + LF +
+ " - Made the AsyncSink corelib");
+
+ ModAPI.addCredit("EaglerForge Devs", "radmanplays",
+ " - Ported and maintained EaglerReborn's PluginAPI to modern version of eaglercrafts (u22+)" + LF +
+ " - Rebranded PluginAPI to ModAPI" + LF +
+ " - Added various new features to ModAPI" + LF +
+ " - Made the worldedit mod + a few other mods");
+
+ ModAPI.addCredit("EaglerForge Devs", "LeahOnBrainrot / OtterCodes101 / OtterDev",
+ " - Created EaglerReborn" + LF +
+ " - EaglerForge developer" + LF +
+ " - Helped update the client to newer versions" + LF +
+ " - Made signed clients work" + LF +
+ " - Maintainer nowadays" + LF +
+ " - Various bug fixes for EaglerForgeInjector");
+
+ ModAPI.addCredit("EaglerForge Devs", "Murturtle",
+ " - Added the render event to EaglerForgeInjector" + LF +
+ " - Added pi optimiser to the injector (now removed)");
+
+ ModAPI.addCredit("EaglerForge Devs", "TheIdiotPlays",
+ " - Made the mod manager GUI");
+
+ ModAPI.addCredit("EaglerForge Devs", "OeildeLynx31",
+ " - Work on the worldedit mod");
+
+ ModAPI.addCredit("EaglerForge Devs", "Stpv22",
+ " - Made the mod gui open before the client starts");
function limitSize(x, n) {
+ if (!x) {
+ return "";
+ }
if (x.length > n) {
return x.substring(0, n) + "…";
} else {
@@ -41,20 +91,20 @@ globalThis.modapi_postinit = "(" + (() => {
function arraysAreSame(arr1, arr2) {
if (!arr1 || !arr2)
return false;
- if(arr1 === arr2)
+ if (arr1 === arr2)
return true;
if (arr1.length !== arr2.length)
return false;
- for (var i = 0, l=arr1.length; i < l; i++) {
+ for (var i = 0, l = arr1.length; i < l; i++) {
if (arr1[i] instanceof Array && arr2[i] instanceof Array) {
if (!arr1[i].equals(arr2[i]))
- return false;
+ return false;
}
else if (arr1[i] !== arr2[i]) {
- return false;
- }
- }
+ return false;
+ }
+ }
return true;
}
function getParamNames(func) {
@@ -115,6 +165,18 @@ globalThis.modapi_postinit = "(" + (() => {
}
ModAPI.meta._descriptionMap[document.currentScript.getAttribute("data-hash")] = limitSize(desc, 160);
}
+ ModAPI.meta.config = function (conf) {
+ if (typeof conf !== "function") {
+ return console.log("[ModAPIMeta] Config value was not a function");
+ }
+ if (!document.currentScript || document.currentScript.getAttribute("data-isMod") !== "true") {
+ return console.log("[ModAPIMeta] Cannot set meta for non-mod script.");
+ }
+ if (!document.currentScript.hasAttribute("data-hash")) {
+ return console.log("[ModAPIMeta] Script does not have a hashcode.");
+ }
+ ModAPI.meta._configMap[document.currentScript.getAttribute("data-hash")] = conf;
+ }
ModAPI.meta.version = function (ver) {
if (!document.currentScript || document.currentScript.getAttribute("data-isMod") !== "true") {
return console.log("[ModAPIMeta] Cannot set meta for non-mod script.");
@@ -343,7 +405,7 @@ globalThis.modapi_postinit = "(" + (() => {
"getConstructorByArgs": function (...argNames) {
if (!argumentCache) {
argumentCache = [];
- this.internalConstructors.forEach(x=>{
+ this.internalConstructors.forEach(x => {
argumentCache.push(getParamNames(x).slice(1).map(y => y.substring(1)));
});
}
@@ -771,7 +833,7 @@ globalThis.modapi_postinit = "(" + (() => {
if (!object) {
return null;
}
- if (prop in object) {
+ if ((prop in object) && Object.keys(object).includes(prop)) {
return prop;
}
var possibleKeys = Object.keys(object).filter(x => { return x.startsWith(prop) });
@@ -780,7 +842,7 @@ globalThis.modapi_postinit = "(" + (() => {
})
return possibleKeys.sort((a, b) => {
return a.length - b.length;
- })[0] || null;
+ })[0] || prop;
}
ModAPI.util.modifyFunction = function (fn, patcherFn) {
@@ -821,6 +883,22 @@ globalThis.modapi_postinit = "(" + (() => {
return originalUpdate.apply(this, args);
};
+ const getCreditsName = ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.EagRuntime", "getResourceString");
+ const originalGetCredits = ModAPI.hooks.methods[getCreditsName];
+ ModAPI.hooks.methods[getCreditsName] = function ($path) {
+ if (!$path) {
+ return originalGetCredits.apply(this, [$path]);
+ }
+ if (ModAPI.util.ustr($path).toLowerCase().endsWith("credits.txt")) {
+ var out = originalGetCredits.apply(this, [$path]);
+ out = ModAPI.util.ustr(out);
+ out = getCreditsString() + out;
+ out = ModAPI.util.str(out);
+ return out;
+ }
+ return originalGetCredits.apply(this, [$path]);
+ };
+
const initMethodName = ModAPI.util.getMethodFromPackage("net.minecraft.client.Minecraft", "startGame");
const originalInit = ModAPI.hooks.methods[initMethodName];
ModAPI.hooks.methods[initMethodName] = function (...args) {
@@ -1105,7 +1183,7 @@ globalThis.modapi_postinit = "(" + (() => {
}
ModAPI.keygen.entity = function (entity) {
var hashMap = ModAPI.util.wrap(ModAPI.reflect.getClassById("net.minecraft.entity.EntityList").staticVariables.idToClassMapping).getCorrective();
- var values = hashMap.keys.getRef().data.filter(x=>hashMap.get(x));
+ var values = hashMap.keys.getRef().data.filter(x => hashMap.get(x));
return qhash(entity, values, 127);
}
}).toString() + ")();";