Merge pull request #41 from eaglerforge/main

Tutorial Drop 2
This commit is contained in:
ZXMushroom63 2024-12-09 17:01:26 +08:00 committed by GitHub
commit bc0bba9bde
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 840 additions and 441 deletions

View File

@ -1,5 +1,10 @@
# EaglerForgeInjector # EaglerForgeInjector
A modding API injector for vanilla eaglercraft builds. An advanced modding API injector for vanilla eaglercraft builds.
Current features:
- Method hooking/monkey patching
- Reflection
- Custom classes
- Modify the dedicated server
### How to use: ### How to use:
#### Online #### Online

File diff suppressed because one or more lines are too long

View File

@ -23,7 +23,7 @@ Tutorials:
- [Disable All Particles](disable_all_particles.md) - [Disable All Particles](disable_all_particles.md)
- [Slippery Mod](slippery.md) - [Slippery Mod](slippery.md)
- [/hat mod](hat.md) - [/hat mod](hat.md)
- [/spawnxp command](comingsoon) - [/spawnxp command](spawnxp.md)
### Advanced ### Advanced
Prerequisites: Prerequisites:
@ -33,6 +33,6 @@ Prerequisites:
- Your EaglerForgeInjector processed.html opened in an editor (optional) - Your EaglerForgeInjector processed.html opened in an editor (optional)
Tutorials: Tutorials:
- [Custom Blocks](comingsoon) - [Custom Blocks](custom_block.md)
- [Custom Items](comingsoon) - [Custom Items](comingsoon)
- [Timescale Command](comingsoon) - [Timescale Command](comingsoon)

67
docs/tutorials/spawnxp.md Normal file
View File

@ -0,0 +1,67 @@
## /spawnxp command
This tutorial will cover spawning in entities using the [`ModAPI.promisify()`](../apidoc/promisify.md) API.
As usual, we'll start with the boilerplate:
```javascript
(function SpawnXPMod() {
ModAPI.meta.title("Spawn XP");
ModAPI.meta.description("Adds a /spawnxp command.");
ModAPI.meta.credits("By <author_name>");
//future code
})();
```
We'll move context to the server, and add a command listener, and ensure the sender is a player:
```javascript
PluginAPI.dedicatedServer.appendCode(function () { //Run this code on the server
PluginAPI.addEventListener("processcommand", (event) => { //Command processing event
if (
!ModAPI.reflect.getClassById("net.minecraft.entity.player.EntityPlayerMP")
.instanceOf(event.sender.getRef())
) {
return;
}
});
});
```
Next, we'll check that the command is /spawnxp. If it is, we'll get the current dimension of the player, and spawn an XP orb at the sender's position. To construct the XP orb, we'll get the `EntityXPOrb` class and get the [first constructor](https://nurmarvin.github.io/Minecraft-1.8-JavaDocs/net/minecraft/entity/EntityXPOrb.html)
```javascript
PluginAPI.dedicatedServer.appendCode(function () { //Run this code on the server
PluginAPI.addEventListener("processcommand", (event) => { //Command processing event
if (
!ModAPI.reflect.getClassById("net.minecraft.entity.player.EntityPlayerMP") //If it isn't a player, exit the method
.instanceOf(event.sender.getRef())
) {
return;
}
if (event.command.toLowerCase().startWith("/spawnxp")) {
const world = event.sender.getServerForPlayer(); //get the current dimension
const senderPos = event.sender.getPosition(); //get the player's current position
const EntityXP = ModAPI.reflect.getClassByName("EntityXPOrb"); // Get the entity xp orb class
const xporb = EntityXP.constructors[0](world.getRef(), senderPos.getX(), senderPos.getY(), senderPos.getZ(), 10); //Construct the first constructor, with arguments, World, x, y, z, xp value
//Get the spawn entity in world method
var spawnEntityInWorldMethod = ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.world.World", "spawnEntityInWorld")];
//Because this method reads and writes data to chunks, it tries to save and load the eaglercraft state, which won't work if eaglercraft is also running its own code.
//To work around this issue, use ModAPI.promisify() to convert the method into one that returns a promise
spawnEntityInWorldMethod = ModAPI.promisify(spawnEntityInWorldMethod);
//we can now use the spawnEntityInWorld method
//wherever you see getRef(), it means to retrieve the raw, untouched data that TeaVM can use.
spawnEntityInWorldMethod(world.getRef(), xporb).then(result => {
// Get the chat component class
const ChatComponentTextClass = ModAPI.reflect.getClassById("net.minecraft.util.ChatComponentText");
// Construct the chat component and send it to the client.
event.sender.addChatMessage(ChatComponentTextClass.constructors[0](ModAPI.util.str("An xp orb has been spawned!")));
});
//Stop the unknown command error
event.preventDefault = true;
}
});
});

File diff suppressed because one or more lines are too long

View File

@ -19,22 +19,14 @@ function fixupBlockIds() {
} }
function makeSteveBlock() { function makeSteveBlock() {
var blockClass = ModAPI.reflect.getClassById("net.minecraft.block.Block"); var blockClass = ModAPI.reflect.getClassById("net.minecraft.block.Block");
var iproperty = ModAPI.reflect.getClassById("net.minecraft.block.properties.IProperty").class;
var makeBlockState = ModAPI.reflect.getClassById("net.minecraft.block.state.BlockState").constructors.find(x => x.length === 2);
var blockSuper = ModAPI.reflect.getSuper(blockClass, (x) => x.length === 2); var blockSuper = ModAPI.reflect.getSuper(blockClass, (x) => x.length === 2);
var creativeBlockTab = ModAPI.reflect.getClassById("net.minecraft.creativetab.CreativeTabs").staticVariables.tabBlock; var creativeBlockTab = ModAPI.reflect.getClassById("net.minecraft.creativetab.CreativeTabs").staticVariables.tabBlock;
var nmb_BlockSteve = function nmb_BlockSteve() { function nmb_BlockSteve() {
blockSuper(this, ModAPI.materials.rock.getRef()); blockSuper(this, ModAPI.materials.rock.getRef());
this.$defaultBlockState = this.$blockState.$getBaseState(); this.$defaultBlockState = this.$blockState.$getBaseState();
this.$setCreativeTab(creativeBlockTab); this.$setCreativeTab(creativeBlockTab);
} }
ModAPI.reflect.prototypeStack(blockClass, nmb_BlockSteve); ModAPI.reflect.prototypeStack(blockClass, nmb_BlockSteve);
nmb_BlockSteve.prototype.$isOpaqueCube = function () {
return 1;
}
nmb_BlockSteve.prototype.$createBlockState = function (t) {
return makeBlockState(this, ModAPI.array.object(iproperty, 0));
}
globalThis.nmb_BlockSteve = nmb_BlockSteve; globalThis.nmb_BlockSteve = nmb_BlockSteve;
} }
function registerSteveClientSide() { function registerSteveClientSide() {

View File

@ -0,0 +1,27 @@
(() => {
ModAPI.meta.title("/ray_trace_test");
ModAPI.meta.description("Mod to test server-side raycasting.");
ModAPI.meta.credits("By ZXMushroom63");
PluginAPI.dedicatedServer.appendCode(function () {
PluginAPI.addEventListener("processcommand", (event) => {
if (event.command.toLowerCase().startsWith("/ray_trace_test")) {
if (
ModAPI.reflect.getClassById("net.minecraft.entity.player.EntityPlayerMP").instanceOf(event.sender.getRef())
) {
//raytrace distance = 6
//the 0 on the end is for client side view bobbing (frame based). we are on the server so using 0 as default.
var movingObjectPosition = event.sender.rayTrace(6, 0).getCorrective();
console.log(movingObjectPosition);
var hitVec = movingObjectPosition.hitVec;
event.sender.addChatMessage(ModAPI.reflect.getClassById("net.minecraft.util.ChatComponentText").constructors[0](
movingObjectPosition.toString() //This is a java string, but that's ok since it goes into a java method.
));
}
event.preventDefault = true;
}
});
});
})();

121
examplemods/guns.js Normal file
View File

@ -0,0 +1,121 @@
(()=>{
const itemTexture = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADySURBVFhH7ZQxDoMwDEVNr4G6VmJC6mk4Itdh7VpxjsBPDaKVCLZxypInIccg5zs/CVQoKAj8uHLjeETouo6HvlQcU3yJ932PIKkTceRAFH8NA6dE3IzbVogdQBOPtuVXfk5IJ8jWhKY4SxPaQvcmLEWuTUj/A1sqiEEcTQBENDWjvh3mvZtxccLiwMqvE0DrhNUBCCy1KSdAUsPiwFYcpM5ENrYWh3tdI4cT4dk0MSKPXzMSRSC+PMi14mcO4e7esv2iJqyHEGDVPCR6jyMC5luERXOfuoY7QFi8MKsDe6tXk8OBv7Ge/E96DZeKFwoOEE1wUX7TFh5zsgAAAABJRU5ErkJggg==";
ModAPI.meta.title("guns");
ModAPI.meta.version("v1.0");
ModAPI.meta.icon(itemTexture);
ModAPI.meta.description("Requires AsyncSink.");
function PistolItem() {
var DamageSourceClass = ModAPI.reflect.getClassByName("DamageSource");
var creativeMiscTab = ModAPI.reflect.getClassById("net.minecraft.creativetab.CreativeTabs").staticVariables.tabMisc;
var itemClass = ModAPI.reflect.getClassById("net.minecraft.item.Item");
var itemSuper = ModAPI.reflect.getSuper(itemClass, (x) => x.length === 1);
var nmi_ItemPistol = function nmi_ItemPistol() {
itemSuper(this); //Use super function to get block properties on this class.
this.$setCreativeTab(creativeMiscTab);
}
function entityRayCast(player, world, range){
var eyePosition = player.getPositionEyes(0.0);
var lookVector = player.getLook(0.0);
var targetPosition = eyePosition.addVector(lookVector.xCoord * range, lookVector.yCoord * range, lookVector.zCoord * range);
var entities = world.getEntitiesWithinAABBExcludingEntity(
player.getRef(),
player.getEntityBoundingBox().expand(range, range, range).getRef()
).getCorrective().array;
var closestEntity = null;
var closestDistance = range;
// Iterate through all entities to find the one the player is looking at
for (var i = 0; i < entities.length; i++) {
if (!entities[i]) {
continue;
}
var entity = entities[i];
// Check if the entity's bounding box intersects with the player's ray
var entityBB = entity.getEntityBoundingBox().expand(0.3, 0.3, 0.3);
var intercept = entityBB.calculateIntercept(eyePosition.getRef(), targetPosition.getRef());
if (intercept != null) {
var distance = eyePosition.distanceTo(intercept.hitVec.getRef());
if (distance < closestDistance) {
closestDistance = distance;
closestEntity = entity;
}
}
}
var rayTraceResult = closestEntity;
if (rayTraceResult != null){
return rayTraceResult;
} else{
return null;
}
}
ModAPI.reflect.prototypeStack(itemClass, nmi_ItemPistol);
nmi_ItemPistol.prototype.$onItemRightClick = function ($itemstack, $world, $player) {
DamageSourceClass.staticMethods.$callClinit.method();
//Noticed that the gun only worked after an entity in the world takes damage XD
//TeaVM is very optimised. Using $callClinit tells it to hurry up pretty much lol
var cactus = DamageSourceClassstaticVariables.cactus;
var world = ModAPI.util.wrap($world);
var entityplayer = ModAPI.util.wrap($player);
var shotentity = entityRayCast(entityplayer, world, 12.0)
if (shotentity != null){
shotentity.attackEntityFrom(cactus, 10);
world.playSoundAtEntity(entityplayer.getRef(), ModAPI.util.str("tile.piston.out"), 1.0, 1.8);
}
return $itemstack;
}
function internal_reg() {
var pistol_item = (new nmi_ItemPistol()).$setUnlocalizedName(
ModAPI.util.str("pistol")
);
itemClass.staticMethods.registerItem.method(ModAPI.keygen.item("pistol"), ModAPI.util.str("pistol"), pistol_item);
ModAPI.items["pistol"] = pistol_item;
return pistol_item;
}
if (ModAPI.items) {
return internal_reg();
} else {
ModAPI.addEventListener("bootstrap", internal_reg);
}
}
ModAPI.dedicatedServer.appendCode(PistolItem);
var pistol_item = PistolItem();
ModAPI.addEventListener("lib:asyncsink", async () => {
ModAPI.addEventListener("custom:asyncsink_reloaded", ()=>{
ModAPI.mc.renderItem.registerItem(pistol_item, ModAPI.util.str("pistol"));
});
AsyncSink.L10N.set("item.pistol.name", "Pistol");
AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/models/item/pistol.json", JSON.stringify(
{
"parent": "builtin/generated",
"textures": {
"layer0": "items/pistol"
},
"display": {
"thirdperson": {
"rotation": [ 5, 80, -45 ],
"translation": [ 0, 1, -3 ],
"scale": [ 1.0, 1.0, 1.0 ]
},
"firstperson": {
"rotation": [ 0, -135, 25 ],
"translation": [ 0, 4, 2 ],
"scale": [ 1.8, 1.8, 1.8 ]
}
}
}
));
AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/textures/items/pistol.png", await (await fetch(
itemTexture
)).arrayBuffer());
});
})();

View File

@ -24,29 +24,21 @@
} }
var itemClass = ModAPI.reflect.getClassById("net.minecraft.item.Item"); var itemClass = ModAPI.reflect.getClassById("net.minecraft.item.Item");
var blockClass = ModAPI.reflect.getClassById("net.minecraft.block.Block"); var blockClass = ModAPI.reflect.getClassById("net.minecraft.block.Block");
var iproperty = ModAPI.reflect.getClassById("net.minecraft.block.properties.IProperty").class;
var makeBlockState = ModAPI.reflect.getClassById("net.minecraft.block.state.BlockState").constructors.find(x => x.length === 2);
var blockSuper = ModAPI.reflect.getSuper(blockClass, (x) => x.length === 2); //Get super function from the block class with a target length of two. ($this (mandatory), material (optional)) var blockSuper = ModAPI.reflect.getSuper(blockClass, (x) => x.length === 2); //Get super function from the block class with a target length of two. ($this (mandatory), material (optional))
var creativeBlockTab = ModAPI.reflect.getClassById("net.minecraft.creativetab.CreativeTabs").staticVariables.tabBlock; var creativeBlockTab = ModAPI.reflect.getClassById("net.minecraft.creativetab.CreativeTabs").staticVariables.tabBlock;
var breakBlockMethod = blockClass.methods.breakBlock.method; var breakBlockMethod = blockClass.methods.breakBlock.method;
var nmb_BlockUnlucky = function nmb_BlockUnlucky() { function nmb_BlockUnlucky() {
blockSuper(this, ModAPI.materials.rock.getRef()); //Use super function to get block properties on this class. blockSuper(this, ModAPI.materials.rock.getRef()); //Use super function to get block properties on this class.
this.$defaultBlockState = this.$blockState.$getBaseState(); this.$defaultBlockState = this.$blockState.$getBaseState();
this.$setCreativeTab(creativeBlockTab); this.$setCreativeTab(creativeBlockTab);
} }
ModAPI.reflect.prototypeStack(blockClass, nmb_BlockUnlucky); ModAPI.reflect.prototypeStack(blockClass, nmb_BlockUnlucky);
nmb_BlockUnlucky.prototype.$isOpaqueCube = function () {
return 1;
}
nmb_BlockUnlucky.prototype.$createBlockState = function () {
return makeBlockState(this, ModAPI.array.object(iproperty, 0));
}
nmb_BlockUnlucky.prototype.$breakBlock = function ($world, $blockpos, $blockstate) { nmb_BlockUnlucky.prototype.$breakBlock = function ($world, $blockpos, $blockstate) {
var world = ModAPI.util.wrap($world); var world = ModAPI.util.wrap($world);
var blockpos = ModAPI.util.wrap($blockpos); var blockpos = ModAPI.util.wrap($blockpos);
if (Math.random() < 1) { //was gonna add random events but couldn't be bothered. Enjoy exploding! if (Math.random() < 1) { //was gonna add random events but couldn't be bothered. Enjoy exploding!
world.newExplosion(null, blockpos.getX() + 0.5, blockpos.getY() + 0.5, world.newExplosion(null, blockpos.getX() + 0.5, blockpos.getY() + 0.5,
blockpos.getZ() + 0.5, 9, 1, 1); blockpos.getZ() + 0.5, 9, 1, 0);
} }
return breakBlockMethod(this, $world, $blockpos, $blockstate); return breakBlockMethod(this, $world, $blockpos, $blockstate);
} }

View File

@ -24,7 +24,7 @@
var example_item = (new nmi_ItemExample()).$setUnlocalizedName( var example_item = (new nmi_ItemExample()).$setUnlocalizedName(
ModAPI.util.str("exampleitem") ModAPI.util.str("exampleitem")
); );
itemClass.staticMethods.registerItem0.method(ModAPI.keygen.item("exampleitem"), ModAPI.util.str("exampleitem"), example_item); itemClass.staticMethods.registerItem.method(ModAPI.keygen.item("exampleitem"), ModAPI.util.str("exampleitem"), example_item);
ModAPI.items["exampleitem"] = example_item; ModAPI.items["exampleitem"] = example_item;
return example_item; return example_item;

View File

@ -117,7 +117,7 @@
<summary> <summary>
How do I compile my own unobfuscated unsigned Eaglercraft build? How do I compile my own unobfuscated unsigned Eaglercraft build?
</summary> </summary>
<a href="docs/compiling_client.md">tutorial here</a> <a href="docs/compiling_client">tutorial here</a>
</details> </details>
<details> <details>
<summary>How does this tool work?</summary> <summary>How does this tool work?</summary>
@ -169,7 +169,7 @@
<!-- Code assets --> <!-- Code assets -->
<script src="postinit.js"></script> <script src="postinit.js"></script>
<script src="modloader.injector.js"></script> <script src="modloader.js"></script>
<script src="modgui.js"></script> <script src="modgui.js"></script>
<script src="efserver.js"></script> <script src="efserver.js"></script>
</body> </body>

View File

@ -18,24 +18,17 @@ function entriesToStaticVariableProxy(entries, prefix) {
var getComponents = ""; var getComponents = "";
entries.forEach((entry) => { entries.forEach((entry) => {
getComponents += ` getComponents += `
case \`${entry.name}\`: case \`${entry.name}\`: return ${entry.variable};`;
return ${entry.variable};
break;`;
}); });
getComponents += ` getComponents += `
default: default: return Reflect.get(a,b,c);`
return Reflect.get(a,b,c);`
var setComponents = ""; var setComponents = "";
entries.forEach((entry) => { entries.forEach((entry) => {
setComponents += ` setComponents += `
case \`${entry.name}\`: case \`${entry.name}\`: ${entry.variable} = c; break;`;
${entry.variable} = c;
break;`;
}); });
setComponents += ` setComponents += ` default: a[b]=c;`
default:
a[b]=c;`
/*/ /*/
ModAPI.hooks._rippedStaticIndexer[\`${prefix.replace( ModAPI.hooks._rippedStaticIndexer[\`${prefix.replace(
@ -196,8 +189,7 @@ var main;(function(){`
(match) => { (match) => {
if ( if (
match.includes("__init_") || match.includes("__init_") ||
match.includes("__clinit_") || match.includes("__clinit_")
match.includes("_$callClinit")
) { ) {
return match; return match;
} }
@ -212,13 +204,14 @@ var main;(function(){`
ModAPI.hooks.methods[\`${fullName}\`]=` + ModAPI.hooks.methods[\`${fullName}\`]=` +
match.replace(fullName + "(", "(") match.replace(fullName + "(", "(")
); );
return match;
} }
); );
var staticVariables = [ var staticVariables = [
...patchedFile.matchAll(/var \S+?_\S+?_\S+? = null;/gm), ...patchedFile.matchAll(/var \S+?_\S+?_\S+? = /gm),
].flatMap((x) => { ].flatMap((x) => {
return x[0]; return x[0];
}).filter(x => {
return (!x.includes("$_clinit_$")) && (!x.includes("$lambda$"))
}); });
patchedFile = patchedFile.replaceAll( patchedFile = patchedFile.replaceAll(
/var \S+?_\S+? = \$rt_classWithoutFields\(\S*?\);/gm, /var \S+?_\S+? = \$rt_classWithoutFields\(\S*?\);/gm,
@ -232,7 +225,7 @@ var main;(function(){`
if (entry.startsWith(prefix)) { if (entry.startsWith(prefix)) {
var variableName = entry var variableName = entry
.replace("var ", "") .replace("var ", "")
.replace(" = null;", ""); .replace(" = ", "");
var segments = variableName.split("_"); var segments = variableName.split("_");
segments.splice(0, 2); segments.splice(0, 2);
var name = segments.join("_"); var name = segments.join("_");
@ -262,7 +255,7 @@ var main;(function(){`
if (entry.startsWith(prefix)) { if (entry.startsWith(prefix)) {
var variableName = entry var variableName = entry
.replace("var ", "") .replace("var ", "")
.replace(" = null;", ""); .replace(" = ", "");
var segments = variableName.split("_"); var segments = variableName.split("_");
segments.splice(0, 2); segments.splice(0, 2);
var name = segments.join("_"); var name = segments.join("_");
@ -317,6 +310,7 @@ var main;(function(){`
\<script id="modapi_postinit_data"\>globalThis.modapi_postinit = \`${globalThis.modapi_postinit}\`;\<\/script\> \<script id="modapi_postinit_data"\>globalThis.modapi_postinit = \`${globalThis.modapi_postinit}\`;\<\/script\>
\<script id="libserverside"\>{"._|_libserverside_|_."}\<\/script\>` \<script id="libserverside"\>{"._|_libserverside_|_."}\<\/script\>`
); );
patchedFile = patchedFile.replace(`<title>EaglercraftX 1.8</title>`, `<title>EFI ${globalThis.ModAPIVersion}</title>`);
patchedFile = patchedFile.replaceAll(/main\(\);\s*?}/gm, (match) => { patchedFile = patchedFile.replaceAll(/main\(\);\s*?}/gm, (match) => {
return match.replace("main();", "main();ModAPI.hooks._postInit();"); return match.replace("main();", "main();ModAPI.hooks._postInit();");
}); });

View File

@ -36,18 +36,19 @@ async function shronk(input) {
const parser = new DOMParser(); const parser = new DOMParser();
const doc = parser.parseFromString(inputHtml, 'text/html'); const doc = parser.parseFromString(inputHtml, 'text/html');
const scriptTags = doc.querySelectorAll('script'); const scriptTags = doc.querySelectorAll('script');
await wait(100); //trying to get chrome to gc
for (let i = 0; i < scriptTags.length; i++) { for (let i = 0; i < scriptTags.length; i++) {
const scriptTag = scriptTags[i]; const scriptTag = scriptTags[i];
const code = scriptTag.textContent; const code = scriptTag.textContent;
_status("[ASYNC_PLUGIN_1] Transpiling script #" + (i + 1) + " of length " + Math.round(code.length / 1000) + "k..."); _status("[ASYNC_PLUGIN_1] Transpiling script #" + (i + 1) + " of length " + Math.round(code.length / 1000) + "k...");
await wait(50); await wait(150);
const output = Babel.transform(code, { const output = Babel.transform(code, {
plugins: [] plugins: []
}); });
scriptTag.textContent = output.code; scriptTag.textContent = output.code;
await wait(10);
} }
_status("[ASYNC_PLUGIN_1] Job complete!"); _status("[ASYNC_PLUGIN_1] Job complete!");

View File

@ -24,7 +24,7 @@ globalThis.modapi_guikit = "(" + (() => {
</h5> </h5>
</header> </header>
<table class="modTable"> <table id="modapi_gui_modTable">
<thead> <thead>
<tr> <tr>
<td> <td>
@ -162,15 +162,18 @@ globalThis.modapi_guikit = "(" + (() => {
font-size: 1.25rem; font-size: 1.25rem;
box-shadow: 0 -4px 6px rgba(0, 0, 0, 0.1); box-shadow: 0 -4px 6px rgba(0, 0, 0, 0.1);
} }
#modapi_gui_modTable {
min-width: 40vw;
}
</style> </style>
</div>`; </div>`;
async function fileToDataURI(file) { async function fileToText(file) {
return new Promise((res, rej) => { return new Promise((res, rej) => {
var fr = new FileReader(); var fr = new FileReader();
fr.addEventListener("error", (e) => { rej(e); }); fr.addEventListener("error", (e) => { rej(e); });
fr.addEventListener("load", (e) => { res(fr.result); }); fr.addEventListener("load", (e) => { res(fr.result); });
fr.readAsDataURL(file); fr.readAsText(file);
}); });
} }
window.modapi_displayModGui = async function (cb) { window.modapi_displayModGui = async function (cb) {
@ -191,7 +194,7 @@ globalThis.modapi_guikit = "(" + (() => {
document.querySelector("#modapi_gui_container")._cb = cb; document.querySelector("#modapi_gui_container")._cb = cb;
var modsList = await getMods(); var modsList = await getMods();
var tbody = document.querySelector("#modapi_gui_container .modTable tbody"); var tbody = document.querySelector("#modapi_gui_container #modapi_gui_modTable tbody");
tbody.innerHTML = ""; tbody.innerHTML = "";
modsList.forEach((modtxt, i) => { modsList.forEach((modtxt, i) => {
if (!modtxt) { return } if (!modtxt) { return }
@ -245,7 +248,6 @@ globalThis.modapi_guikit = "(" + (() => {
var button = document.createElement("button"); var button = document.createElement("button");
button.innerText = "Delete"; button.innerText = "Delete";
button.style.height = "3rem"; button.style.height = "3rem";
button.style.marginTop = "calc(50% - 1.5rem)";
button.addEventListener("click", async () => { button.addEventListener("click", async () => {
await removeMod(i); await removeMod(i);
window.modapi_displayModGui(); window.modapi_displayModGui();
@ -254,7 +256,7 @@ globalThis.modapi_guikit = "(" + (() => {
controls.appendChild(button); controls.appendChild(button);
tr.appendChild(mod); tr.appendChild(mod);
tr.appendChild(spacer); tr.appendChild(spacer);
tr.appendChild(button); tr.appendChild(controls);
tbody.appendChild(tr); tbody.appendChild(tr);
}); });
var once = false; var once = false;
@ -279,7 +281,7 @@ globalThis.modapi_guikit = "(" + (() => {
if (!mod || mod.length === 0) { if (!mod || mod.length === 0) {
return; return;
} }
await addMod("web@" + mod); await addMod(mod);
window.modapi_displayModGui(); window.modapi_displayModGui();
} }
window.modapi_uploadmod = async () => { window.modapi_uploadmod = async () => {
@ -292,7 +294,7 @@ globalThis.modapi_guikit = "(" + (() => {
return; return;
} }
for (let i = 0; i < f.files.length; i++) { for (let i = 0; i < f.files.length; i++) {
await addMod("web@" + (await fileToDataURI(f.files[i])).replaceAll(";base64", ";fs=" + f.files[i].name + ";base64")); await addFileMod(f.files[i].name, (await fileToText(f.files[i])));
} }
window.modapi_displayModGui(); window.modapi_displayModGui();
}); });

View File

@ -1,212 +0,0 @@
globalThis.modapi_modloader = `function promisifyIDBRequest(request) {
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async function getDatabase() {
const dbRequest = indexedDB.open("EF_MODS");
const db = await promisifyIDBRequest(dbRequest);
if (!db.objectStoreNames.contains("filesystem")) {
db.close();
const version = db.version + 1;
const upgradeRequest = indexedDB.open("EF_MODS", version);
upgradeRequest.onupgradeneeded = (event) => {
const upgradedDb = event.target.result;
upgradedDb.createObjectStore("filesystem");
};
return promisifyIDBRequest(upgradeRequest);
}
return db;
}
async function getMods() {
const db = await getDatabase();
const transaction = db.transaction(["filesystem"], "readonly");
const objectStore = transaction.objectStore("filesystem");
const object = await promisifyIDBRequest(objectStore.get("mods.txt"));
return object ? (await object.text()).split("|") : [];
}
async function saveMods(mods) {
const db = await getDatabase();
const transaction = db.transaction(["filesystem"], "readwrite");
const objectStore = transaction.objectStore("filesystem");
const encoder = new TextEncoder();
const modsData = encoder.encode(mods.join("|"));
const modsBlob = new Blob([modsData], { type: "text/plain" });
await promisifyIDBRequest(objectStore.put(modsBlob, "mods.txt"));
}
async function addMod(mod) {
const mods = await getMods();
mods.push(mod);
await saveMods(mods);
}
async function removeMod(index) {
const mods = await getMods();
if (index >= 0 && index < mods.length) {
mods.splice(index, 1);
await saveMods(mods);
}
}
async function resetMods() {
await saveMods([]);
console.log("Mods reset");
}
window.modLoader = async function modLoader(modsArr = []) {
if (!window.eaglerMLoaderMainRun) {
var searchParams = new URLSearchParams(location.search);
searchParams.getAll("mod").forEach((modToAdd) => {
console.log(
"[EaglerML] Adding mod to loadlist from search params: " + modToAdd
);
modsArr.push(modToAdd);
});
searchParams.getAll("plugin").forEach((modToAdd) => {
console.log(
"[EaglerML] Adding mod to loadlist from search params: " + modToAdd
);
modsArr.push(modToAdd);
});
if (
!!eaglercraftXOpts &&
!!eaglercraftXOpts.Mods &&
Array.isArray(eaglercraftXOpts.Mods)
) {
eaglercraftXOpts.Mods.forEach((modToAdd) => {
console.log(
"[EaglerML] Adding mod to loadlist from eaglercraftXOpts: " +
modToAdd
);
modsArr.push(modToAdd);
});
}
console.log("[EaglerML] Searching in iDB");
try {
var idbMods = await getMods();
modsArr = modsArr.concat(idbMods
.filter(x => { return x && x.length > 0 })
.flatMap(x => { if (x.startsWith("web@")) { return x.replace("web@", "") } return x })
);
} catch (error) {
console.error(error);
}
window.eaglerMLoaderMainRun = true;
}
if (window.noLoadMods === true) {
modsArr.splice(0, modsArr.length);
}
function checkModsLoaded(totalLoaded, identifier) {
console.log(
"[EaglerML] Checking if mods are finished :: " +
totalLoaded +
"/" +
modsArr.length
);
if (totalLoaded >= modsArr.length) {
clearInterval(identifier);
window.ModGracePeriod = false;
if (
window.eaglerMLoaderMainRun &&
ModAPI &&
ModAPI.events &&
ModAPI.events.callEvent
) {
ModAPI.events.callEvent("load", {});
}
console.log(
"[EaglerML] Checking if mods are finished :: All mods loaded! Grace period off."
);
}
}
function methodB(currentMod) {
try {
console.log("[EaglerML] Loading " + currentMod + " via method B.");
var script = document.createElement("script");
script.setAttribute("data-hash", ModAPI.util.hashCode("web@"+currentMod));
script.src = currentMod;
script.setAttribute("data-isMod", "true");
script.onerror = () => {
console.log(
"[EaglerML] Failed to load " + currentMod + " via method B!"
);
script.remove();
totalLoaded++;
};
script.onload = () => {
console.log(
"[EaglerML] Successfully loaded " + currentMod + " via method B."
);
totalLoaded++;
};
document.body.appendChild(script);
} catch (error) {
console.log(
"[EaglerML] Oh no! The mod " + currentMod + " failed to load!"
);
totalLoaded++;
}
}
window.ModGracePeriod = true;
var totalLoaded = 0;
var loaderCheckInterval = null;
modsArr.forEach((c) => {
let currentMod = c;
console.log("[EaglerML] Starting " + currentMod);
try {
var req = new XMLHttpRequest();
req.open("GET", currentMod);
req.onload = function xhrLoadHandler() {
console.log("[EaglerML] Loading " + currentMod + " via method A.");
var script = document.createElement("script");
script.setAttribute("data-hash", ModAPI.util.hashCode("web@"+currentMod));
try {
script.src =
"data:text/javascript," + encodeURIComponent(req.responseText);
} catch (error) {
methodB(currentMod);
return;
}
script.setAttribute("data-isMod", "true");
script.onerror = () => {
console.log(
"[EaglerML] Failed to load " + currentMod + " via method A!"
);
script.remove();
totalLoaded++;
};
script.onload = () => {
console.log(
"[EaglerML] Successfully loaded " + currentMod + " via method A."
);
totalLoaded++;
};
document.body.appendChild(script);
};
req.onerror = function xhrErrorHandler() {
methodB(currentMod);
};
req.send();
} catch (error) {
methodB(currentMod);
}
});
loaderCheckInterval = setInterval(() => {
checkModsLoaded(totalLoaded, loaderCheckInterval);
}, 500);
console.log(
"[EaglerML] Starting to load " + modsArr.length + " mods..."
);
window.returnTotalLoadedMods = function returnTotalLoadedMods() {
return totalLoaded;
};
};`;

View File

@ -1,11 +1,12 @@
function promisifyIDBRequest(request) { globalThis.modapi_modloader = "(" + (() => {
globalThis.promisifyIDBRequest = function promisifyIDBRequest(request) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result); request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error); request.onerror = () => reject(request.error);
}); });
} }
async function getDatabase() { globalThis.getDatabase = async function getDatabase() {
const dbRequest = indexedDB.open("EF_MODS"); const dbRequest = indexedDB.open("EF_MODS");
const db = await promisifyIDBRequest(dbRequest); const db = await promisifyIDBRequest(dbRequest);
@ -21,59 +22,99 @@ async function getDatabase() {
} }
return db; return db;
} }
async function getMods() { globalThis.getMods = async function getMods() {
const db = await getDatabase(); const db = await getDatabase();
const transaction = db.transaction(["filesystem"], "readonly"); const transaction = db.transaction(["filesystem"], "readonly");
const objectStore = transaction.objectStore("filesystem"); const objectStore = transaction.objectStore("filesystem");
const object = await promisifyIDBRequest(objectStore.get("mods.txt")); const object = await promisifyIDBRequest(objectStore.get("mods.txt"));
return object ? (await object.text()).split("|") : []; var out = object ? (await object.text()).split("|").toSorted() : [];
} db.close();
return out;
}
async function saveMods(mods) { globalThis.getMod = async function getMod(mod) {
const db = await getDatabase();
const transaction = db.transaction(["filesystem"], "readonly");
const objectStore = transaction.objectStore("filesystem");
const object = await promisifyIDBRequest(objectStore.get("mods/" + mod));
var out = object ? (await object.text()) : "";
db.close();
return out;
}
globalThis.saveMods = async function saveMods(mods) {
const db = await getDatabase(); const db = await getDatabase();
const transaction = db.transaction(["filesystem"], "readwrite"); const transaction = db.transaction(["filesystem"], "readwrite");
const objectStore = transaction.objectStore("filesystem"); const objectStore = transaction.objectStore("filesystem");
const encoder = new TextEncoder(); const encoder = new TextEncoder();
const modsData = encoder.encode(mods.join("|")); const modsData = encoder.encode(mods.toSorted().join("|"));
const modsBlob = new Blob([modsData], { type: "text/plain" }); const modsBlob = new Blob([modsData], { type: "text/plain" });
await promisifyIDBRequest(objectStore.put(modsBlob, "mods.txt")); await promisifyIDBRequest(objectStore.put(modsBlob, "mods.txt"));
} db.close();
}
async function addMod(mod) { globalThis.addMod = async function addMod(mod) {
const mods = await getMods();
mods.push("web@" + mod);
await saveMods(mods);
}
globalThis.addFileMod = async function addFileMod(mod, textContents) {
const mods = await getMods(); const mods = await getMods();
mods.push(mod); mods.push(mod);
await saveMods(mods); await saveMods(mods);
}
async function removeMod(index) { const db = await getDatabase();
const transaction = db.transaction(["filesystem"], "readwrite");
const objectStore = transaction.objectStore("filesystem");
const encoder = new TextEncoder();
const modsData = encoder.encode(textContents);
const modsBlob = new Blob([modsData], { type: "text/plain" });
await promisifyIDBRequest(objectStore.put(modsBlob, "mods/" + mod));
db.close();
}
globalThis.removeMod = async function removeMod(index) {
const mods = await getMods(); const mods = await getMods();
if (index >= 0 && index < mods.length) { if (index >= 0 && index < mods.length) {
mods.splice(index, 1); var deleted = mods.splice(index, 1)[0];
await saveMods(mods); await saveMods(mods);
if (!deleted.startsWith("web@")) {
const db = await getDatabase();
const transaction = db.transaction(["filesystem"], "readwrite");
const objectStore = transaction.objectStore("filesystem");
await promisifyIDBRequest(objectStore.delete("mods/" + deleted));
db.close();
}
}
} }
}
async function resetMods() { globalThis.resetMods = async function resetMods() {
await saveMods([]); console.log("Resetting mods...");
const db = await getDatabase();
const transaction = db.transaction(["filesystem"], "readwrite");
const objectStore = transaction.objectStore("filesystem");
await promisifyIDBRequest(objectStore.clear());
console.log("Mods reset"); console.log("Mods reset");
} db.close();
}
window.modLoader = async function modLoader(modsArr = []) { globalThis.modLoader = async function modLoader(modsArr = []) {
if (!window.eaglerMLoaderMainRun) { if (!window.eaglerMLoaderMainRun) {
var searchParams = new URLSearchParams(location.search); var searchParams = new URLSearchParams(location.search);
searchParams.getAll("mod").forEach((modToAdd) => { searchParams.getAll("mod").forEach((modToAdd) => {
console.log( console.log(
"[EaglerML] Adding mod to loadlist from search params: " + modToAdd "[EaglerML] Adding mod to loadlist from search params: " + modToAdd
); );
modsArr.push(modToAdd); modsArr.push("web@" + modToAdd);
}); });
searchParams.getAll("plugin").forEach((modToAdd) => { searchParams.getAll("plugin").forEach((modToAdd) => {
console.log( console.log(
"[EaglerML] Adding mod to loadlist from search params: " + modToAdd "[EaglerML] Adding mod to loadlist from search params: " + modToAdd
); );
modsArr.push(modToAdd); modsArr.push("web@" + modToAdd);
}); });
if ( if (
!!eaglercraftXOpts && !!eaglercraftXOpts &&
@ -94,7 +135,6 @@ window.modLoader = async function modLoader(modsArr = []) {
var idbMods = await getMods(); var idbMods = await getMods();
modsArr = modsArr.concat(idbMods modsArr = modsArr.concat(idbMods
.filter(x => { return x && x.length > 0 }) .filter(x => { return x && x.length > 0 })
.flatMap(x => { if (x.startsWith("web@")) { return x.replace("web@", "") } return x })
); );
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@ -132,7 +172,7 @@ window.modLoader = async function modLoader(modsArr = []) {
try { try {
console.log("[EaglerML] Loading " + currentMod + " via method B."); console.log("[EaglerML] Loading " + currentMod + " via method B.");
var script = document.createElement("script"); var script = document.createElement("script");
script.setAttribute("data-hash", ModAPI.util.hashCode("web@"+currentMod)); script.setAttribute("data-hash", ModAPI.util.hashCode("web@" + currentMod));
script.src = currentMod; script.src = currentMod;
script.setAttribute("data-isMod", "true"); script.setAttribute("data-isMod", "true");
script.onerror = () => { script.onerror = () => {
@ -159,19 +199,22 @@ window.modLoader = async function modLoader(modsArr = []) {
window.ModGracePeriod = true; window.ModGracePeriod = true;
var totalLoaded = 0; var totalLoaded = 0;
var loaderCheckInterval = null; var loaderCheckInterval = null;
modsArr.forEach((c) => { modsArr.sort();
let currentMod = c; for (let i = 0; i < modsArr.length; i++) {
let currentMod = modsArr[i];
var isIDBMod = !currentMod.startsWith("web@");
if (!isIDBMod) {
currentMod = currentMod.replace("web@", "");
}
console.log("[EaglerML] Starting " + currentMod); console.log("[EaglerML] Starting " + currentMod);
try { try {
var req = new XMLHttpRequest(); var responseText = isIDBMod ? await getMod(currentMod) : await (await fetch(currentMod)).text();
req.open("GET", currentMod);
req.onload = function xhrLoadHandler() {
console.log("[EaglerML] Loading " + currentMod + " via method A."); console.log("[EaglerML] Loading " + currentMod + " via method A.");
var script = document.createElement("script"); var script = document.createElement("script");
script.setAttribute("data-hash", ModAPI.util.hashCode("web@"+currentMod)); script.setAttribute("data-hash", ModAPI.util.hashCode((isIDBMod ? "" : "web@") + currentMod));
try { try {
script.src = script.src =
"data:text/javascript," + encodeURIComponent(req.responseText); "data:text/javascript," + encodeURIComponent(responseText);
} catch (error) { } catch (error) {
methodB(currentMod); methodB(currentMod);
return; return;
@ -191,15 +234,10 @@ window.modLoader = async function modLoader(modsArr = []) {
totalLoaded++; totalLoaded++;
}; };
document.body.appendChild(script); document.body.appendChild(script);
};
req.onerror = function xhrErrorHandler() {
methodB(currentMod);
};
req.send();
} catch (error) { } catch (error) {
methodB(currentMod); methodB(currentMod);
} }
}); }
loaderCheckInterval = setInterval(() => { loaderCheckInterval = setInterval(() => {
checkModsLoaded(totalLoaded, loaderCheckInterval); checkModsLoaded(totalLoaded, loaderCheckInterval);
}, 500); }, 500);
@ -209,4 +247,5 @@ window.modLoader = async function modLoader(modsArr = []) {
window.returnTotalLoadedMods = function returnTotalLoadedMods() { window.returnTotalLoadedMods = function returnTotalLoadedMods() {
return totalLoaded; return totalLoaded;
}; };
}; };
}).toString() + ")();"

View File

@ -1,3 +1,4 @@
globalThis.ModAPIVersion = "v2.3.3";
globalThis.modapi_postinit = "(" + (() => { globalThis.modapi_postinit = "(" + (() => {
//EaglerForge post initialization code. //EaglerForge post initialization code.
//This script cannot contain backticks, escape characters, or backslashes in order to inject into the dedicated server code. //This script cannot contain backticks, escape characters, or backslashes in order to inject into the dedicated server code.
@ -22,7 +23,7 @@ globalThis.modapi_postinit = "(" + (() => {
ModAPI.meta._versionMap = {}; ModAPI.meta._versionMap = {};
ModAPI.array = {}; ModAPI.array = {};
ModAPI.version = "v2.3.1"; ModAPI.version = "__modapi_version_code__";
ModAPI.flavour = "injector"; ModAPI.flavour = "injector";
ModAPI.GNU = "terry pratchett"; ModAPI.GNU = "terry pratchett";
ModAPI.credits = ["ZXMushroom63", "radmanplays", "Murturtle", "OtterCodes101", "TheIdiotPlays", "OeildeLynx31", "Stpv22"]; ModAPI.credits = ["ZXMushroom63", "radmanplays", "Murturtle", "OtterCodes101", "TheIdiotPlays", "OeildeLynx31", "Stpv22"];
@ -889,12 +890,16 @@ globalThis.modapi_postinit = "(" + (() => {
} }
ModAPI.events.newEvent("bootstrap", "server"); ModAPI.events.newEvent("bootstrap", "server");
const bootstrapClass = ModAPI.reflect.getClassById("net.minecraft.init.Bootstrap");
const originalBootstrap = ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.init.Bootstrap", "register")]; const originalBootstrap = ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.init.Bootstrap", "register")];
ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.init.Bootstrap", "register")] = function (...args) { ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.init.Bootstrap", "register")] = function (...args) {
if (bootstrapClass.staticVariables.alreadyRegistered) {
return;
}
var x = originalBootstrap.apply(this, args); var x = originalBootstrap.apply(this, args);
ModAPI.util.bootstrap(); ModAPI.util.bootstrap();
ModAPI.events.callEvent("bootstrap", {});
console.log("[ModAPI] Hooked into bootstrap. .blocks, .items, .materials and .enchantments are now accessible."); console.log("[ModAPI] Hooked into bootstrap. .blocks, .items, .materials and .enchantments are now accessible.");
ModAPI.events.callEvent("bootstrap", {});
return x; return x;
} }
@ -954,8 +959,8 @@ globalThis.modapi_postinit = "(" + (() => {
} }
function qhash(txt, arr) { function qhash(txt, arr) {
var interval = 4095 - arr.length; var interval = 4095; //used to be 4095 - arr.length, but that increases incompatibility based on load order and otehr circumstances
if (interval < 1) { if (arr.length >= 4095) {
console.error("[ModAPI.keygen] Ran out of IDs while generating for " + txt); console.error("[ModAPI.keygen] Ran out of IDs while generating for " + txt);
return -1; return -1;
} }
@ -968,7 +973,7 @@ globalThis.modapi_postinit = "(" + (() => {
} }
var hash = x; var hash = x;
while (arr.includes(hash)) { while (arr.includes(hash)) {
hash = (hash + 1) % (interval + arr.length); hash = (hash + 1) % interval;
} }
return hash; return hash;
} }
@ -989,4 +994,4 @@ globalThis.modapi_postinit = "(" + (() => {
var values = [...ModAPI.reflect.getClassById("net.minecraft.block.Block").staticVariables.blockRegistry.$modapi_specmap.values()]; var values = [...ModAPI.reflect.getClassById("net.minecraft.block.Block").staticVariables.blockRegistry.$modapi_specmap.values()];
return qhash(block, values); return qhash(block, values);
} }
}).toString() + ")();"; }).toString().replace("__modapi_version_code__", ModAPIVersion) + ")();";