diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..377be45
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+node_modules
+EaglercraftX_1.8_Offline_en_US.html
+processed.html
\ No newline at end of file
diff --git a/README.md b/README.md
index d4b7319..5a0f60c 100644
--- a/README.md
+++ b/README.md
@@ -13,11 +13,17 @@ Go to https://eaglerforge.github.io/EaglerForgeInjector/ and upload an unminifie
#### Portable Offline
Download this repository as a .zip, and extract it. Open index.html with your preferred browser (use `ctrl` + `O` on a new tab) and upload an unminified, unobfuscated, unsigned EaglercraftX offline download.
+#### Offline CLI
+- Clone the repository and run `npm ci` to install the required libraries.
+- In the project directory, run `npm run efi /help` for use instructions.
+- This is good for bypassing browser memory limitations, for minifying.
+- Yes, I am forcing DOS command syntax upon you
+
#### How does it work?
This tool matches patterns in eaglercraft builds and adds patching code to let you modify how the code works at runtime. It then adds a [corelib](./postinit.js) that initialises the `ModAPI` object.
#### History
-EaglerForgeInjector is a replacement for the `ModAPI` in the [old eaglerforge](https://github.com/EaglerForge/EaglerForge-old), which was maintained by @radmanplays. The legacy eaglerforge was a port of [OtterDev's EaglerReborn (dmca'd)](https://github.com/EaglerReborn/reborn)'s `PluginAPI` (created by me, @ZXMushroom63) to run on newer versions of Eaglercraft, with a few improvements and new features. Unlike EaglerForgeInjector, both legacy eaglerforge and eaglerreborn manually exposed properties and methods one by one.
+EaglerForgeInjector is a replacement for the `ModAPI` in the [old eaglerforge](https://github.com/EaglerForge/EaglerForge-old), which was maintained by @radmanplays. The legacy eaglerforge was a port of [Leah Anderson's EaglerReborn (dmca'd)](https://github.com/EaglerReborn/reborn)'s `PluginAPI` (created by me, @ZXMushroom63) to run on newer versions of Eaglercraft, with a few improvements and new features. Unlike EaglerForgeInjector, both legacy eaglerforge and eaglerreborn manually exposed properties and methods one by one.
## Discord server
[https://discord.gg/rbxN7kby5W](https://discord.gg/rbxN7kby5W)
diff --git a/browser.js b/browser.js
new file mode 100644
index 0000000..645ed12
--- /dev/null
+++ b/browser.js
@@ -0,0 +1,50 @@
+document.querySelector("title").innerText = `EaglerForge Injector ${EFIConfig.ModAPIVersion}`;
+document.querySelector("h1").innerText = `EaglerForge Injector ${EFIConfig.ModAPIVersion}`;
+
+document.querySelector("#giveme").addEventListener("click", () => {
+ if (
+ !document.querySelector("input").files ||
+ !document.querySelector("input").files[0]
+ ) {
+ return;
+ }
+ // @type File
+ var file = document.querySelector("input").files[0];
+ var fileType = file.name.split(".");
+ fileType = fileType[fileType.length - 1];
+
+ file.text().then(async (string) => {
+ var patchedFile = string;
+
+ EFIConfig.doServerExtras = false;
+ patchedFile = patchClient(string, new DOMParser());
+
+ var blob = new Blob([patchedFile], { type: file.type });
+ saveAs(blob, "processed." + fileType);
+ backgroundLog("Saving file...", true);
+ });
+});
+
+document.querySelector("#givemeserver").addEventListener("click", () => {
+ if (
+ !document.querySelector("input").files ||
+ !document.querySelector("input").files[0]
+ ) {
+ return;
+ }
+ // @type File
+ var file = document.querySelector("input").files[0];
+ var fileType = file.name.split(".");
+ fileType = fileType[fileType.length - 1];
+
+ file.text().then(async (string) => {
+ var patchedFile = string;
+
+ EFIConfig.doServerExtras = true;
+ patchedFile = patchClient(string, new DOMParser());
+
+ var blob = new Blob([patchedFile], { type: file.type });
+ saveAs(blob, "efserver." + fileType);
+ backgroundLog("Saving file...", true);
+ });
+});
diff --git a/cli.js b/cli.js
new file mode 100644
index 0000000..0ce7143
--- /dev/null
+++ b/cli.js
@@ -0,0 +1,46 @@
+const { DOMParser } = require("@xmldom/xmldom");
+const fs = require("fs/promises");
+const EFI = require("./core/core");
+
+async function main() {
+ const args = process.argv.slice(2);
+
+ if (args.includes("/help") || args.length === 0) {
+ console.log("***************************");
+ console.log("* EaglerForgeInjector CLI *");
+ console.log("***************************");
+ console.log("");
+ console.log("> npm run efi /help #shows this help text");
+ console.log("> npm run efi my_unminified_file.html #does nothing");
+ console.log("> npm run efi my_unminified_file.html /eaglerforge #injects eaglerforge");
+ console.log("> npm run efi my_unminified_file.html /minify #minifies file");
+ console.log("> npm run efi my_unminified_file.html /minify /minify_extras #minifies file with extra optimisations");
+ console.log("> npm run efi my_unminified_file.html /eaglerforge /minify #minifies file and injects eaglerforge");
+ console.log("> npm run efi my_unminified_file.html /server_extras #inject server hosting stuff");
+ console.log("> npm run efi my_unminified_file.html output.html /eaglerforge /minify #minifies file and injects eaglerforge, write to file named output.html");
+ console.log("> npm run efi my_unminified_file.html /verbose #much logging");
+ console.log("By default, output is written to processed.html");
+ return;
+ }
+ const inputFile = args[0];
+ if (!inputFile) {
+ return console.error("No file provided!");
+ }
+ EFI.conf.doMinify = args.includes("/minify");
+ EFI.conf.doEaglerforge = args.includes("/eaglerforge");
+ EFI.conf.doServerExtras = args.includes("/server_extras");
+ EFI.conf.doMinifyPlus = args.includes("/minify_extras");
+ EFI.conf.verbose = args.includes("/verbose");
+ const string = await fs.readFile(inputFile, {encoding: 'utf-8'});
+ const res = await EFI.patchClient(string, new DOMParser());
+ if (res) {
+ var output = args[1];
+ if (!output || !output.endsWith(".html")) {
+ output = "processed.html";
+ }
+ console.log("Writing to " + output);
+ await fs.writeFile(output, res);
+ console.log("Done!");
+ }
+}
+main();
\ No newline at end of file
diff --git a/injector.js b/core/core.js
similarity index 81%
rename from injector.js
rename to core/core.js
index 892919c..bdb2be4 100644
--- a/injector.js
+++ b/core/core.js
@@ -1,13 +1,44 @@
-globalThis.ModAPIVersion = "v2.7.3";
-globalThis.doEaglerforge = true;
-document.querySelector("title").innerText = `EaglerForge Injector ${ModAPIVersion}`;
-document.querySelector("h1").innerText = `EaglerForge Injector ${ModAPIVersion}`;
+var modapi_preinit = `globalThis.ModAPI ||= {};
+ ModAPI.hooks ||= {};
+ ModAPI.hooks.freezeCallstack = false;
+ ModAPI.hooks._rippedData ||= [];
+ ModAPI.hooks._rippedInterfaceMap ||= {};
+ ModAPI.hooks._teavm ||= {};
+ ModAPI.hooks._rippedConstructors ||= {};
+ ModAPI.hooks._rippedInternalConstructors ||= {};
+ ModAPI.hooks.methods ||= {};
+ ModAPI.hooks._rippedMethodTypeMap ||= {};
+ ModAPI.hooks._postInit ||= ()=>{};
+ ModAPI.hooks._rippedStaticProperties ||= {};
+ ModAPI.hooks._rippedStaticIndexer ||= {};
+ `;
+var freezeCallstack = `if(ModAPI.hooks.freezeCallstack){return false};`;
+const EFIConfig = {
+ ModAPIVersion: "v2.7.3",
+ doEaglerforge: true,
+ verbose: false,
+ doServerExtras: false,
+ doMinify: false,
+ doMinifyPlus: false
+}
+if (globalThis.process) {
+ var backgroundLog = (x) => {
+ if (EFIConfig.verbose) {
+ console.log(x);
+ }
+ };
+ var alert = console.error;
+ var confirm = console.warn;
+}
function wait(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => { resolve(); }, ms);
});
}
function _status(x) {
+ if (globalThis.process) {
+ return console.log(x);
+ }
backgroundLog(x, true);
document.querySelector("#status").innerText = x;
}
@@ -73,7 +104,15 @@ function entriesToStaticVariableProxy(entries, prefix, clinitList) {
});`;
return proxy;
}
-async function processClasses(string) {
+async function processClasses(string, parser) {
+ if (globalThis.process) {
+ var { modapi_guikit } = require("./modgui");
+ var { modapi_postinit } = require("./postinit");
+ var { modapi_modloader } = require("./modloader");
+ var { PatchesRegistry } = require("./patches");
+ var { EFServer } = require("./efserver");
+ var { minify } = require("./minify");
+ }
if (string.includes("__eaglerforgeinjector_installation_flag__")) {
backgroundLog("Detected input containing EFI installation flag.", true);
return alert("this file already has EaglerForge injected in it, you nonce.\nif you're trying to update, you need a vanilla file.")
@@ -82,11 +121,14 @@ async function processClasses(string) {
backgroundLog("Detected invalid input.\nPlease ensure file is unsigned, unminified and unobfuscated.", true);
return alert("This file does not match the requirements for EaglerForgeInjector. (not unminified & unobfuscated). Check info.")
}
- if (globalThis.doShronk) {
- if (!confirm("The minify step is extremely slow, especially on lower-end devices, and can take upwards of 15 minutes.")) {
+ if (EFIConfig.doMinify) {
+ if (!confirm("The minify step is extremely slow, especially on lower-end devices, and can take upwards of 15 minutes.") && !module) {
return;
}
backgroundLog("[MINIFY] Minify warning bypassed.");
+ if (globalThis.process) {
+ await wait(1000);
+ }
}
_status("Beginning patch process...");
await wait(50);
@@ -308,11 +350,11 @@ var main;(function(){`
await wait(50);
patchedFile = PatchesRegistry.patchFile(patchedFile);
- if (globalThis.doShronk) {
+ if (EFIConfig.doMinify) {
_status("Shrinking file...");
await wait(50);
- patchedFile = await shronk(patchedFile);
+ patchedFile = await minify(patchedFile, parser, EFIConfig);
}
@@ -322,15 +364,15 @@ var main;(function(){`
` id="game_frame">`,
` id="game_frame">
\`;
@@ -27,10 +37,9 @@ async function shronk(input) {
_status("[MINIFY] Parsing html...");
await wait(50);
- const parser = new DOMParser();
const doc = parser.parseFromString(inputHtml, 'text/html');
- const scriptTags = doc.querySelectorAll('script');
- await wait(100); //trying to get chrome to gc
+ const scriptTags = doc.getElementsByTagName('script');
+ await wait(200); //trying to get chrome to gc
for (let i = 0; i < scriptTags.length; i++) {
const scriptTag = scriptTags[i];
const code = scriptTag.textContent;
@@ -39,7 +48,7 @@ async function shronk(input) {
const output = Babel.transform(code, {
- plugins: globalThis.doShronkPlus ? [
+ plugins: EFIConfig.doMinifyPlus ? [
MINIFY()
] : []
});
@@ -51,8 +60,14 @@ async function shronk(input) {
await wait(50);
if (isHtml) {
- return doc.documentElement.outerHTML;
+ return globalThis.process ? doc.toString() : doc.documentElement.outerHTML;
} else {
return doc.querySelector('script').textContent;
}
+}
+
+if (globalThis.process) {
+ module.exports = {
+ minify: minify
+ }
}
\ No newline at end of file
diff --git a/modgui.js b/core/modgui.js
similarity index 98%
rename from modgui.js
rename to core/modgui.js
index 6b58b16..87d0853 100644
--- a/modgui.js
+++ b/core/modgui.js
@@ -1,5 +1,5 @@
-globalThis.modapi_guikit = "(" + (() => {
+const modapi_guikit = "(" + (() => {
// ModAPI GUI made by TheIdiotPlays
// https://github.com/TheIdiotPlays
var splashes = [
@@ -316,4 +316,10 @@ globalThis.modapi_guikit = "(" + (() => {
});
f.click();
}
-}).toString() + ")();";
\ No newline at end of file
+}).toString() + ")();";
+
+if (globalThis.process) {
+ module.exports = {
+ modapi_guikit: modapi_guikit
+ }
+}
\ No newline at end of file
diff --git a/modloader.js b/core/modloader.js
similarity index 98%
rename from modloader.js
rename to core/modloader.js
index fd21afe..06155d9 100644
--- a/modloader.js
+++ b/core/modloader.js
@@ -1,4 +1,4 @@
-globalThis.modapi_modloader = "(" + (() => {
+const modapi_modloader = "(" + (() => {
globalThis.promisifyIDBRequest = function promisifyIDBRequest(request) {
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
@@ -252,4 +252,10 @@ globalThis.modapi_modloader = "(" + (() => {
return totalLoaded;
};
};
-}).toString() + ")();"
\ No newline at end of file
+}).toString() + ")();"
+
+if (globalThis.process) {
+ module.exports = {
+ modapi_modloader: modapi_modloader
+ }
+}
\ No newline at end of file
diff --git a/patches.js b/core/patches.js
similarity index 91%
rename from patches.js
rename to core/patches.js
index 8087300..27d130a 100644
--- a/patches.js
+++ b/core/patches.js
@@ -35,4 +35,10 @@ PatchesRegistry.addPatch(function (input) {
if (!$this.$renderHand)`
);
return output;
-});
\ No newline at end of file
+});
+
+if (globalThis.process) {
+ module.exports = {
+ PatchesRegistry: PatchesRegistry
+ }
+}
\ No newline at end of file
diff --git a/postinit.js b/core/postinit.js
similarity index 99%
rename from postinit.js
rename to core/postinit.js
index f75e2df..e45bfb9 100644
--- a/postinit.js
+++ b/core/postinit.js
@@ -1,4 +1,4 @@
-globalThis.modapi_postinit = "(" + (() => {
+const modapi_postinit = "(" + (() => {
//EaglerForge post initialization code.
//This script cannot contain backticks, escape characters, or backslashes in order to inject into the dedicated server code.
var startedModLoader = false;
@@ -61,7 +61,7 @@ globalThis.modapi_postinit = "(" + (() => {
" - Created EaglerReborn" + LF +
" - EaglerForge developer" + LF +
" - Helped update the client to newer versions" + LF +
- " - Made signed clients work" + LF +
+ " - Made signed clients work in the legacy version" + LF +
" - Maintainer nowadays" + LF +
" - Various bug fixes for EaglerForgeInjector");
@@ -1187,3 +1187,9 @@ globalThis.modapi_postinit = "(" + (() => {
return qhash(entity, values, 127);
}
}).toString() + ")();";
+
+if (globalThis.process) {
+ module.exports = {
+ modapi_postinit: modapi_postinit
+ }
+ }
\ No newline at end of file
diff --git a/docs/tutorials/disable_all_particles.md b/docs/tutorials/disable_all_particles.md
index 99526ff..40a816a 100644
--- a/docs/tutorials/disable_all_particles.md
+++ b/docs/tutorials/disable_all_particles.md
@@ -62,7 +62,7 @@ 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).
+So when we override `hasParticlesInAlphaLayer`, we'll need to return a `0` or a `1`. Since we want the game to think that there aren't any particles in the alpha layer, we'll return `0` (false).
```javascript
(function NoParticles() {
diff --git a/docs/tutorials/hat.md b/docs/tutorials/hat.md
index 89c48d2..5ad5e67 100644
--- a/docs/tutorials/hat.md
+++ b/docs/tutorials/hat.md
@@ -1,6 +1,6 @@
## /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]()
+[`S09PacketHeldItemChange` constructors](https://nurmarvin.github.io/Minecraft-1.8-JavaDocs/net/minecraft/network/play/server/S09PacketHeldItemChange.html)
As always, start with the basic boilerplate IIFE with credits:
diff --git a/docs/tutorials/slippery.md b/docs/tutorials/slippery.md
index 7a13370..7c7fd61 100644
--- a/docs/tutorials/slippery.md
+++ b/docs/tutorials/slippery.md
@@ -15,6 +15,7 @@ We'll begin with the basic boilerplate mod code:
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);
diff --git a/examplemods/servermod.js b/examplemods/servermod.js
index 9c54614..db0acd2 100644
--- a/examplemods/servermod.js
+++ b/examplemods/servermod.js
@@ -114,7 +114,7 @@
gui.innerText += "\n" + ModAPI.util.ustr(status);
}
}, ModAPI.util.str(worldName), 0).then(code => {
- opening = true; //change to false later
+ opening = false;
if (code != null) {
ModAPI.hooks.methods.nlevs_SingleplayerServerController_configureLAN(ModAPI.mc.playerController.currentGameType.getRef(), 0);
var msg = "code: " + ModAPI.util.ustr(code) + " relay: " + ModAPI.util.ustr(ModAPI.hooks.methods.nlevsl_LANServerController_getCurrentURI());
diff --git a/index.html b/index.html
index b3cc149..502360d 100644
--- a/index.html
+++ b/index.html
@@ -91,11 +91,11 @@
>
-
+
-
+
-
+
Awaiting input...
@@ -151,31 +151,16 @@
e.target.files[0].name;
}
});
- var modapi_preinit = `globalThis.ModAPI ||= {};
- ModAPI.hooks ||= {};
- ModAPI.hooks.freezeCallstack = false;
- ModAPI.hooks._rippedData ||= [];
- ModAPI.hooks._rippedInterfaceMap ||= {};
- ModAPI.hooks._teavm ||= {};
- ModAPI.hooks._rippedConstructors ||= {};
- ModAPI.hooks._rippedInternalConstructors ||= {};
- ModAPI.hooks.methods ||= {};
- ModAPI.hooks._rippedMethodTypeMap ||= {};
- ModAPI.hooks._postInit ||= ()=>{};
- ModAPI.hooks._rippedStaticProperties ||= {};
- ModAPI.hooks._rippedStaticIndexer ||= {};
- `;
- var freezeCallstack = `if(ModAPI.hooks.freezeCallstack){return false};`;
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+