diff --git a/README.md b/README.md index ae2bdd5..d4b7319 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # EaglerForgeInjector -An advanced modding API injector for vanilla eaglercraft builds. +An advanced modding API injector for unminified, unobfuscated, unsigned eaglercraft builds. Current features: - Method hooking/monkey patching - Reflection @@ -13,5 +13,11 @@ 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. +#### 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. + ## Discord server [https://discord.gg/rbxN7kby5W](https://discord.gg/rbxN7kby5W) diff --git a/docs/apidoc/keygen.md b/docs/apidoc/keygen.md index 17c5cd9..1e3a401 100644 --- a/docs/apidoc/keygen.md +++ b/docs/apidoc/keygen.md @@ -1,8 +1,10 @@ ## ModAPI.keygen -ModAPI.keygen contains the API for getting item and block IDs from a string. It looks at the registries for items and blocks to derive the IDs, so IDs will not be automatically reserved until a block/item is actually registered. Ideally, you'd want to call a keygen method just before registering your block. +ModAPI.keygen contains the API for getting numerical item, block and entity IDs from a string. It looks at registries to derive the IDs, so IDs will not be automatically reserved until a block/item is actually registered into the game. Ideally, you'd want to call a keygen method just before registering your block. Methods: - `ModAPI.keygen.item(itemId: String) : number` - Example usage is: `var id = ModAPI.keygen.item("my_example_item");` - `ModAPI.keygen.block(blockId: String) : number` - - Example usage is: `var id = ModAPI.keygen.block("my_example_block");` \ No newline at end of file + - Example usage is: `var id = ModAPI.keygen.block("my_example_block");` +- `ModAPI.keygen.entity(entityId: String) : number` + - Example usage is: `var id = ModAPI.keygen.entity("my_example_entity");` \ No newline at end of file diff --git a/docs/apidoc/reflect.md b/docs/apidoc/reflect.md index b8bdd88..586342c 100644 --- a/docs/apidoc/reflect.md +++ b/docs/apidoc/reflect.md @@ -6,6 +6,7 @@ Properties: - `classes: ReflectClass[]` - `ModAPI.reflect.classes` is an array of ReflectClasses, representing (almost) every java class. +- `classMap: Map` is a map of every class. Methods: @@ -16,12 +17,17 @@ Methods: - This method is used to find a class by its id. - For example, to get the `Minecraft` class, you can use `ModAPI.reflect.getClassById("Minecraft")` - This runs slower than `getClassById` because it has to filter through all classes. Make sure to cache the result rather than calling it over and over again. -- `ModAPI.reflect.getSuper(rClass: ReflectClass, filter: Function) : Function` +- `ModAPI.reflect.getSuper(rClass: ReflectClass, filter: Function?) : Function` - Gets a super function from a reflect class. This is used to extend built in classes, like `Block`. - - For an example, see lines [29](https://github.com/eaglerforge/EaglerForgeInjector/blob/6e8598c180f96a65c0c101be72e6d0fa53195404/examplemods/unlucky_blocks.js#L29) and [33](https://github.com/eaglerforge/EaglerForgeInjector/blob/6e8598c180f96a65c0c101be72e6d0fa53195404/examplemods/unlucky_blocks.js#L33) in unlucky_blocks.js + - For an example, see lines [29](https://github.com/eaglerforge/EaglerForgeInjector/blob/6e8598c180f96a65c0c101be72e6d0fa53195404/examplemods/unlucky_blocks.js#L29) and [33](https://github.com/eaglerforge/EaglerForgeInjector/blob/6e8598c180f96a65c0c101be72e6d0fa53195404/examplemods/unlucky_blocks.js#L33) in `unlucky_blocks.js` + - When called without a filter function, the filter defaults to `(fn)=>fn.length === 1` - `ModAPI.reflect.prototypeStack(rClass: ReflectClass, target: Class/ConstructorFunction) : void` - Copies methods from a reflect class and it's parents onto a target native JavaScript class. This allows TeaVM to use these objects normally, without you having to manually reimplement every method. In other words, this is the equivalent of extending a class. + - Also adds some metadata to make the class work with `ModAPI.util.asClass` - [Example usage](https://github.com/eaglerforge/EaglerForgeInjector/blob/6e8598c180f96a65c0c101be72e6d0fa53195404/examplemods/unlucky_blocks.js#L37) +- `ModAPI.reflect.implements(target: Class/ConstructorFunction, interface: ReflectClass)` + - Marks the provided interface as a supertype of the target class. + - JavaScript equivalent of the `implements` keyword ### ReflectClass Definition Each `ReflectClass` has the following properties: @@ -49,11 +55,8 @@ Each `ReflectClass` has the following properties: - List of all the static variable names for the class. - `staticVariables: Map` - key-value dictionary of all the static variables in a class. -- `superclass: Class?` - - The raw teavm class of the superclass. -- `superclassName: String?` - - The class id of the class's superclass. Eg: `net.minecraft.client.entity.AbstractClientPlayer` - - Will be `null` if `hasMeta` is equal to `false` +- `superclass: ReflectClass?` + - The superclass. Each `ReflectClass` has the following methods: diff --git a/docs/apidoc/utils.md b/docs/apidoc/utils.md index 054469e..cead964 100644 --- a/docs/apidoc/utils.md +++ b/docs/apidoc/utils.md @@ -81,4 +81,7 @@ Methods: - `ModAPI.util.getBlockById(id: number) : Block` - Gets a block by it's ID - `ModAPI.util.getBlockFromItem(item: Item) : Block` - - Gets a block from an ItemBlock instance. \ No newline at end of file + - Gets a block from an ItemBlock instance. +- `ModAPI.util.asClass(class: Class) : Class` + - Converts a javascript class to a java class. + - Equivalent of using `MyClass.class` in java. \ No newline at end of file diff --git a/docs/tutorials/custom_block.md b/docs/tutorials/custom_block.md index e4e9aa0..682c242 100644 --- a/docs/tutorials/custom_block.md +++ b/docs/tutorials/custom_block.md @@ -185,7 +185,7 @@ When it's loaded, we'll: ModAPI.addEventListener("lib:asyncsink", async () => { //Add an asyncronous listener to AsyncSink loading. ModAPI.addEventListener("lib:asyncsink:registeritems", (renderItem)=>{ //when asyncsink yells at us to register the custom block, register it - renderItem.registerItem(custom_block, ModAPI.util.str("custom_block")); + renderItem.registerBlock(custom_block, ModAPI.util.str("custom_block")); }); AsyncSink.L10N.set("tile.custom_block.name", "My Custom Block"); //Set the name of the block diff --git a/examplemods/AsyncSink.js b/examplemods/AsyncSink.js index dd61ac8..abb02a2 100644 --- a/examplemods/AsyncSink.js +++ b/examplemods/AsyncSink.js @@ -4,6 +4,7 @@ const asyncSinkIcon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAY ModAPI.meta.icon(asyncSinkIcon); ModAPI.meta.credits("By ZXMushroom63"); (function AsyncSinkFn() { + const ResourceLocation = ModAPI.reflect.getClassByName("ResourceLocation").constructors.find(x => x.length === 1); //AsyncSink is a plugin to debug and override asynchronous methods in EaglercraftX async function runtimeComponent() { const booleanResult = (b) => ModAPI.hooks.methods.nlevit_BooleanResult__new(b * 1); @@ -43,6 +44,17 @@ ModAPI.meta.credits("By ZXMushroom63"); AsyncSink.L10N = new Map(); AsyncSink.FSOverride = new Set(); AsyncSink.MIDDLEWARE = []; + + //takes in a ResourceLocation and removes cached data. Use to only reload a specific texture if you know where it is stored. + AsyncSink.clearResourcePointer = function clearResourcePointer(resourceLocation) { + if (!resourceLocation) { + return; + } + var res = ModAPI.util.wrap((resourceLocation.isModProxy === true) ? resourceLocation.getRef() : resourceLocation); + res.cachedPointer = null; + res.cachedPointerType = 0; + ModAPI.mc.getTextureManager().mapTextureObjects.remove(res.getRef()); + } AsyncSink.setFile = function setFile(path, data) { if (typeof data === "string") { data = encoder.encode(data).buffer; @@ -58,6 +70,12 @@ ModAPI.meta.credits("By ZXMushroom63"); return true; } + AsyncSink.hideFile = function hideFile(path) { + AsyncSink.FSOverride.add(path); + AsyncSink.FS.delete(path); + return true; + } + AsyncSink.getFile = function getFile(path) { return AsyncSink.FS.get(path) || new ArrayBuffer(0); } @@ -160,6 +178,16 @@ ModAPI.meta.credits("By ZXMushroom63"); return originalL10NRead.apply(this, args); }; + const L18NFormat = ModAPI.util.getMethodFromPackage("net.minecraft.client.resources.I18n", "format"); + const originalL18NFormat = ModAPI.hooks.methods[L18NFormat]; + ModAPI.hooks.methods[L18NFormat] = function (...args) { + var key = ModAPI.util.ustr(args[0]); + if (AsyncSink.L10N.has(key)) { + args[0] = ModAPI.util.str(AsyncSink.L10N.get(key)); + } + return originalL18NFormat.apply(this, args); + }; + const L10NCheck = ModAPI.util.getMethodFromPackage("net.minecraft.util.StatCollector", "canTranslate"); const originalL10NCheck = ModAPI.hooks.methods[L10NCheck]; ModAPI.hooks.methods[L10NCheck] = function (...args) { @@ -203,7 +231,7 @@ ModAPI.meta.credits("By ZXMushroom63"); } else { resourcePackList.resourcePacks.push(pack); } - + const writeableTransaction = db.transaction(["filesystem"], "readwrite"); const writeableObjectStore = writeableTransaction.objectStore("filesystem"); await promisifyIDBRequest(writeableObjectStore.put({ @@ -255,8 +283,9 @@ ModAPI.meta.credits("By ZXMushroom63"); if (e.message.toLowerCase().startsWith(".reload_tex")) { e.preventDefault = true; ModAPI.mc.renderItem.itemModelMesher.simpleShapesCache.clear(); - ModAPI.promisify(ModAPI.mc.refreshResources)().then(()=>{ + ModAPI.promisify(ModAPI.mc.refreshResources)().then(() => { ModAPI.events.callEvent("custom:asyncsink_reloaded", {}); + ModAPI.events.callEvent("lib:asyncsink:registeritems", ModAPI.mc.renderItem); }); } }); @@ -268,4 +297,69 @@ ModAPI.meta.credits("By ZXMushroom63"); ModAPI.events.callEvent("lib:asyncsink:registeritems", ModAPI.util.wrap(args[0])); } + AsyncSink.Audio = {}; + AsyncSink.Audio.Category = ModAPI.reflect.getClassByName("SoundCategory").staticVariables; + AsyncSink.Audio.Objects = []; + const SoundHandler_onResourceManagerReload = ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.client.audio.SoundHandler", "onResourceManagerReload")]; + ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.client.audio.SoundHandler", "onResourceManagerReload")] = function (...args) { + SoundHandler_onResourceManagerReload.apply(this, args); + if (ModAPI.util.isCritical()) { + return; + } + var snd = ModAPI.mc.mcSoundHandler; + var registry = snd.sndRegistry.soundRegistry; + console.log("[AsyncSink] Populating sound registry hash map with " + AsyncSink.Audio.Objects.length + " sound effects."); + AsyncSink.Audio.Objects.forEach(pair => { + registry.put(pair[0], pair[1]); + }); + } + + // key = "mob.entity.say" + // values = SoundEntry[] + // category: AsyncSink.Audio.Category.* + // SoundEntry = {path: String, pitch: 1, volume: 1, streaming: false} + const SoundPoolEntry = ModAPI.reflect.getClassByName("SoundPoolEntry").constructors.find(x => x.length === 4); + const EaglercraftRandom = ModAPI.reflect.getClassByName("EaglercraftRandom").constructors.find(x=>x.length===0); + const SoundEventAccessorClass = ModAPI.reflect.getClassByName("SoundEventAccessor").class; + function makeSoundEventAccessor(soundpoolentry, weight) { + var object = new SoundEventAccessorClass; + var wrapped = ModAPI.util.wrap(object).getCorrective(); + wrapped.entry = soundpoolentry; + wrapped.weight = weight; + return object; + } + const SoundEventAccessorCompositeClass = ModAPI.reflect.getClassByName("SoundEventAccessorComposite").class; + function makeSoundEventAccessorComposite(rKey, pitch, volume, category) { + var object = new SoundEventAccessorCompositeClass; + var wrapped = ModAPI.util.wrap(object).getCorrective(); + wrapped.soundLocation = rKey; + wrapped.eventPitch = pitch; + wrapped.eventVolume = volume; + wrapped.category = category; + wrapped.soundPool = ModAPI.hooks.methods.cgcc_Lists_newArrayList1(); + wrapped.rnd = EaglercraftRandom(); + return object; + } + AsyncSink.Audio.register = function addSfx(key, category, values) { + if (!category) { + throw new Error("[AsyncSink] Invalid audio category provided: "+category); + } + var snd = ModAPI.mc.mcSoundHandler; + var registry = snd.sndRegistry.soundRegistry; + var rKey = ResourceLocation(ModAPI.util.str(key)); + var soundPool = values.map(se => { + var path = ResourceLocation(ModAPI.util.str(se.path)); + return SoundPoolEntry(path, se.pitch, se.volume, 1 * se.streaming); + }).map(spe => { + return makeSoundEventAccessor(spe, 1); // 1 = weight + }); + var compositeSound = makeSoundEventAccessorComposite(rKey, 1, 1, category); + var compositeSoundWrapped = ModAPI.util.wrap(compositeSound); + soundPool.forEach(sound => { + compositeSoundWrapped.soundPool.add(sound); + }); + AsyncSink.Audio.Objects.push([rKey, compositeSound]); + registry.put(rKey, compositeSound); + values.map(x=>"resourcepacks/AsyncSinkLib/assets/minecraft/" + x.path + ".mcmeta").forEach(x=>AsyncSink.setFile(x, new ArrayBuffer(0))); + } })(); \ No newline at end of file diff --git a/examplemods/CustomItemsDemo.js b/examplemods/CustomItemsDemo.js index e062cc7..19b9b31 100644 --- a/examplemods/CustomItemsDemo.js +++ b/examplemods/CustomItemsDemo.js @@ -1,4 +1,4 @@ -//This mod also requires lib.customitems.js +//Demo mod showing how to use lib.customitems.js ModAPI.addEventListener("lib:libcustomitems:loaded", () => { console.log("Registered my cool custom item."); LibCustomItems.registerItem({ diff --git a/examplemods/DuckMod.js b/examplemods/DuckMod.js new file mode 100644 index 0000000..9f2a89a --- /dev/null +++ b/examplemods/DuckMod.js @@ -0,0 +1,209 @@ +(function DuckEntity() { + ModAPI.meta.title("Duck Mod 🦆"); + ModAPI.meta.version("v1"); + ModAPI.meta.description("adds ducks to the game"); + ModAPI.meta.credits("By ZXMushroom63"); + + function registerEntity() { + ModAPI.hooks.methods.jl_String_format = ModAPI.hooks.methods.nlev_HString_format; //temporary thing to fix an issue in eaglercraft + // Utils + function AITask(name, length) { + return ModAPI.reflect.getClassById("net.minecraft.entity.ai." + name).constructors.find(x => x.length === length); + } + const ResourceLocation = ModAPI.reflect.getClassByName("ResourceLocation").constructors.find(x => x.length === 1); + const EntityPlayer = ModAPI.reflect.getClassByName("EntityPlayer"); + const GlStateManager = Object.fromEntries(Object.values(ModAPI.reflect.getClassByName("GlStateManager").staticMethods).map(x => [x.methodNameShort, x.method])); + const SharedMonsterAttributes = ModAPI.reflect.getClassByName("SharedMonsterAttributes").staticVariables; + + // START CUSTOM ENTITY + var entityClass = ModAPI.reflect.getClassById("net.minecraft.entity.passive.EntityAnimal"); + var entitySuper = ModAPI.reflect.getSuper(entityClass, (x) => x.length === 2); + var nme_EntityDuck = function nme_EntityDuck($worldIn) { + entitySuper(this, $worldIn); + this.wrapped ||= ModAPI.util.wrap(this).getCorrective(); + this.wrapped.setSize(0.4, 0.7); + this.wrapped.tasks.addTask(0, AITask("EntityAISwimming", 1)(this)); + this.wrapped.tasks.addTask(1, AITask("EntityAIPanic", 2)(this, 1.9)); + this.wrapped.tasks.addTask(2, AITask("EntityAIMate", 2)(this, 1.0)); + this.wrapped.tasks.addTask(3, AITask("EntityAITempt", 4)(this, 1.5, ModAPI.items.bread.getRef(), 0)); //won't cause a problem as the bread is obtained when the entity is constructed. + this.wrapped.tasks.addTask(4, AITask("EntityAIFollowParent", 2)(this, 1.2)); + this.wrapped.tasks.addTask(5, AITask("EntityAIWander", 2)(this, 1.1)); + this.wrapped.tasks.addTask(6, AITask("EntityAIWatchClosest", 3)(this, ModAPI.util.asClass(EntityPlayer.class), 6)); + this.wrapped.tasks.addTask(7, AITask("EntityAILookIdle", 1)(this)); + } + ModAPI.reflect.prototypeStack(entityClass, nme_EntityDuck); + nme_EntityDuck.prototype.$getEyeHeight = function () { + this.wrapped ||= ModAPI.util.wrap(this).getCorrective(); + return this.wrapped.height; + } + + const originalApplyEntityAttributes = nme_EntityDuck.prototype.$applyEntityAttributes; + nme_EntityDuck.prototype.$applyEntityAttributes = function () { + this.wrapped ||= ModAPI.util.wrap(this).getCorrective(); + originalApplyEntityAttributes.apply(this, []); + this.wrapped.getEntityAttribute(SharedMonsterAttributes.maxHealth).setBaseValue(5); + this.wrapped.getEntityAttribute(SharedMonsterAttributes.movementSpeed).setBaseValue(0.25); + } + + const originalLivingUpdate = nme_EntityDuck.prototype.$onLivingUpdate; + nme_EntityDuck.prototype.$onLivingUpdate = function () { + this.wrapped ||= ModAPI.util.wrap(this).getCorrective(); + originalLivingUpdate.apply(this, []); + if (this.wrapped.isInWater()) { + this.wrapped.motionY *= 0.5; + this.wrapped.getEntityAttribute(SharedMonsterAttributes.movementSpeed).setBaseValue(1.4); + } else { + this.wrapped.getEntityAttribute(SharedMonsterAttributes.movementSpeed).setBaseValue(0.25); + } + } + + nme_EntityDuck.prototype.$getLivingSound = function () { + return ModAPI.util.str("mob.duck.quack"); + } + nme_EntityDuck.prototype.$getHurtSound = function () { + return ModAPI.util.str("mob.duck.quack"); + } + nme_EntityDuck.prototype.$getDeathSound = function () { + return ModAPI.util.str("mob.duck.quack"); + } + nme_EntityDuck.prototype.$playStepSound = function () { + this.wrapped ||= ModAPI.util.wrap(this).getCorrective(); + this.wrapped.playSound(ModAPI.util.str("mob.duck.step"), 0.2, 1); + } + nme_EntityDuck.prototype.$getDropItem = function () { + return ModAPI.items.feather.getRef(); + } + nme_EntityDuck.prototype.$createChild = function (otherParent) { + this.wrapped ||= ModAPI.util.wrap(this).getCorrective(); + return new nme_EntityDuck(this.wrapped.worldObj?.getRef() ?? null); + } + nme_EntityDuck.prototype.$isBreedingItem = function (itemstack) { + return itemstack !== null && itemstack.$getItem() === ModAPI.items.bread.getRef(); + } + // END CUSTOM ENTITY + + + // START CUSTOM MODEL + var modelChickenClass = ModAPI.reflect.getClassById("net.minecraft.client.model.ModelChicken"); + var modelChickenSuper = ModAPI.reflect.getSuper(modelChickenClass); //while super isn't used when extending this class, java implies the call. + var nmcm_ModelDuck = function nmcm_ModelDuck() { + modelChickenSuper(this); + } + ModAPI.reflect.prototypeStack(modelChickenClass, nmcm_ModelDuck); + // END CUSTOM MODEL + + + // START CUSTOM RENDERER + var renderClass = ModAPI.reflect.getClassById("net.minecraft.client.renderer.entity.RenderLiving"); + var renderSuper = ModAPI.reflect.getSuper(renderClass, (x) => x.length === 4); + const duckTextures = ResourceLocation(ModAPI.util.str("textures/entity/duck.png")); + var nmcre_RenderDuck = function nmcre_RenderDuck(renderManager, modelBaseIn, shadowSizeIn) { + renderSuper(this, renderManager, modelBaseIn, shadowSizeIn); + } + ModAPI.reflect.prototypeStack(renderClass, nmcre_RenderDuck); + nmcre_RenderDuck.prototype.$getEntityTexture = function (entity) { + return duckTextures; + } + nmcre_RenderDuck.prototype.$handleRotationFloat = function (entity, partialTicks) { + entity = ModAPI.util.wrap(entity); + if ((!entity.onGround) && (!entity.isInWater())) { + return 2; //falling + } else { + return 0; + } + } + + const ID = ModAPI.keygen.entity("duck"); + ModAPI.reflect.getClassById("net.minecraft.entity.EntityList").staticMethods.addMapping0.method( + ModAPI.util.asClass(nme_EntityDuck), + { + $createEntity: function ($worldIn) { + return new nme_EntityDuck($worldIn); + } + }, + ModAPI.util.str("Duck"), + ID, + 0x5e3e2d, //egg base + 0x269166 //egg spots + ); + + const SpawnPlacementType = ModAPI.reflect.getClassById("net.minecraft.entity.EntityLiving$SpawnPlacementType").staticVariables; + const ENTITY_PLACEMENTS = ModAPI.util.wrap( + ModAPI.reflect.getClassById("net.minecraft.entity.EntitySpawnPlacementRegistry") + .staticVariables.ENTITY_PLACEMENTS + ); + ENTITY_PLACEMENTS.put(ModAPI.util.asClass(nme_EntityDuck), SpawnPlacementType.ON_GROUND); + ModAPI.addEventListener('bootstrap', ()=>{ + const SpawnListEntry = ModAPI.reflect + .getClassById("net.minecraft.world.biome.BiomeGenBase$SpawnListEntry") + .constructors.find(x => x.length === 4); + const BiomeGenSwamp = ModAPI.util.wrap( + ModAPI.reflect.getClassById("net.minecraft.world.biome.BiomeGenBase") + .staticVariables.swampland + ); + const BiomeGenRiver = ModAPI.util.wrap( + ModAPI.reflect.getClassById("net.minecraft.world.biome.BiomeGenBase") + .staticVariables.river + ); + const BiomeGenBeach = ModAPI.util.wrap( + ModAPI.reflect.getClassById("net.minecraft.world.biome.BiomeGenBase") + .staticVariables.beach + ); + const duckSpawnSwamp = SpawnListEntry(ModAPI.util.asClass(nme_EntityDuck), 22, 3, 5); + const duckSpawnRiverBed = SpawnListEntry(ModAPI.util.asClass(nme_EntityDuck), 10, 5, 9); + const duckSpawnBeach = SpawnListEntry(ModAPI.util.asClass(nme_EntityDuck), 24, 2, 3); + BiomeGenSwamp.spawnableCreatureList.add(duckSpawnSwamp); + BiomeGenRiver.spawnableCreatureList.add(duckSpawnRiverBed); + BiomeGenBeach.spawnableCreatureList.add(duckSpawnBeach); + }); + + + ModAPI.addEventListener("lib:asyncsink", async () => { + AsyncSink.L10N.set("entity.Duck.name", "Duck"); + }); + + + return { + EntityDuck: nme_EntityDuck, + ModelDuck: nmcm_ModelDuck, + RenderDuck: nmcre_RenderDuck, + duckTextures: duckTextures + } + } + + ModAPI.dedicatedServer.appendCode(registerEntity); + var data = registerEntity(); + + ModAPI.addEventListener("lib:asyncsink", async () => { + AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/textures/entity/duck.png", await (await fetch( + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAAgBAMAAABQs2O3AAAAAXNSR0IB2cksfwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAACdQTFRFAAAALJBrKYdk1pcLp3UI4JEzL5pyaFY4AAAANa6BW0AjYUQlVTwhXohvWAAAAA10Uk5TAP///////////////y0EQa0AAADJSURBVHicY2RgVPj/gIGBwZhBkGEPkGb9zYACGBmYEv4tADKKj/CCFYT+3oCmgE3g1weQAQwQIzAUJLIyMACF9MA8kBEMASgqGIXYGl8ArbiPEEJToMbg8AdIz8OpIJGBFSTwCqcCIQYGfgb8VjDYoFqBChiBeDWYFYpbwSkwywy3ArDvGVyoZMVuVwJWrApDV7AaiQO05YwJuoJTSBwzbFbsQeK4UGzFIwaGf0wMDCE4rQDGGuN/BgZTnFZAFbiRZgUDATCqAAIA8Z45IRCQkrIAAAAASUVORK5CYII=" + )).arrayBuffer()); + AsyncSink.hideFile("resourcepacks/AsyncSinkLib/assets/minecraft/textures/entity/duck.png.mcmeta"); + ModAPI.mc.renderManager.entityRenderMap.put(ModAPI.util.asClass(data.EntityDuck), new data.RenderDuck(ModAPI.mc.renderManager.getRef(), new data.ModelDuck(), 0.3)); + ModAPI.promisify(ModAPI.mc.renderEngine.bindTexture)(data.duckTextures).then(() => { + console.log("Loaded duck texture into cache."); + }); + AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/sounds/mob/duck/quack.ogg", await (await fetch( + "data:audio/ogg;base64,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Mdaaa869p9ZijLHWmnPuvQAAkwcHAKgEG2dYSTorHA0uNGQlAJAbAIAgxJhzzkEIIYQQQgghUoox5yCEEEIIIZRSSqQUY85BCCGEEEIIIYSMMeeggxBCCKWUUkopGWPOQQghhBBKKKWEEjrnoIMQQgmllFJKKaV0zjkIIYQQSimllFJK6SCEEEIIpZRSSimllNJBCCGEUEoppZRSSiklhBBCCKWUUkoppZRSSgghhBBKKaWUUkoppZQQQgillFJKKaWUUkopIYQQSimllFJKKaWUUkIIpZRSSimllFJKKaWEEEoppZRSSimllFJKCaGUUkoppZRSSimllBJKKaWUUkoppZRSSikllFJKKaWUUkoppZRSSiillFJKKaWUUkoppZRQSimllFJKKaWUUkopoZRSSimllFJKKaWUUgoAADpwAAAIMKLSQuw048ojcEQhwwRUaMhKACAcAABABDoIIYQQQggRcxBCCCGEEEKImIMQQgghhBBCCCGEEEIIpZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppRQAdZnhABg9YeMMK0lnhaPBhYasBADSAgAAYxhjjCnIpLMWY60NYxBCB52EFGqoJaaGMQghdFBKSi22WHMGoaRSSkktxliDzT2DUEoppaQWY605F+NBSCWl1GKrteccjO4glJJSSjHWmnPuvWjQSUmptVpz7j0HXzwIpaTWWow9Bx+MMKKUlmKssdYcfBFGGFFKSy3GmnvNvRhjhEopxlp7zrnnXIwRPqUWY6659x58LsL44mLMOffigw8+CGGMkDHm2HPwvRdjjA/CyFxzLsIY44swwvggbK25B1+MEUYYY3zvNfigezHCCCOMMcII3XPRRfhijDFGGF+EAQC5EQ4AiAtGElJnGVYaceMJGCKQQkNWAQAxAAAEMcYgpJBSSinFGGOMMcYYY4wxxhhjjDHGnGPOOeecAADABAcAgAAr2JVZWrVR3NRJXvRB4BM6YjMy5FIqZnIi6JEaarES7NAKbvACsNCQlQAAGQAA5KSUlFotGkLKQWk1iMgg5STFJCJjkILSgqeQMYhJyh1jCiEFqXbQMYUUoxpSCplS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" + )).arrayBuffer()); + AsyncSink.Audio.register("mob.duck.quack", AsyncSink.Audio.Category.ANIMALS, [ + { + path: "sounds/mob/duck/quack.ogg", + pitch: 1, + volume: 1, + streaming: false //use for large audio files + } + ]); + AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/sounds/mob/duck/step.ogg", await (await fetch( + "data:audio/ogg;base64,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LbZYa60159RajDHWmmvOvafWYoyx1ppz7r0AAJMHBwCoBBtnWEk6KxwNLjRkJQCQGwCAIMSYc85BCCGEEEIIIVKKMecghBBCCCGUUkqkFGPOQQghhBBCCCGEjDHnoIMQQgillFJKKRljzkEIIYQQSiilhBI656CDEEIJpZRSSimldM45CCGEEEoppZRSSukghBBCCKWUUkoppZTSQQghhFBKKaWUUkopJYQQQgillFJKKaWUUkoIIYQQSimllFJKKaWUEEIIpZRSSimllFJKKSGEEEoppZRSSimllFJCCKWUUkoppZRSSimlhBBKKaWUUkoppZRSSgmhlFJKKaWUUkoppZQSSimllFJKKaWUUkopJZRSSimllFJKKaWUUkoopZRSSimllFJKKaWUUEoppZRSSimllFJKKaGUUkoppZRSSimllFIKAAA6cAAACDCi0kLsNOPKI3BEIcMEVGjISgAgHAAAQAQ6CCGEEEIIEXMQQgghhBBCiJiDEEIIIYQQQgghhBBCCKWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaUUAHWZ4QAYPWHjDCtJZ4WjwYWGrAQA0gIAAGMYY4wpyKSzFmOtDWMQQgedhBRqqCWmhjEIIXRQSkottlhzBqGkUkpJLcZYg809g1BKKaWkFmOtORfjQUglpdRiq7XnHIzuIJSSUkox1ppz7r1o0ElJqbVac+49B188CKWk1lqMPQcfjDCilJZirLHWHHwRRhhRSkstxpp7zb0YY4RKKcZae86551yMET6lFmOuufcefC7C+OJizDn34oMPPghhjJAx5thz8L0XY4wPwshccy7CGOOLMML4IGytuQdfjBFGGGN87zX4oHsxwggjjDHCCN1z0UX4YowxRhhfhAEAuREOAIgLRhJSZxlWGnHjCRgikEJDVgEAMQAABDHGIKSQUkopxRhjjDHGGGOMMcYYY4wxxpxjzjnnnAAAwAQHAIAAK9iVWVq1UdzUSV70QeATOmIzMuRSKmZyIuiRGmqxEuzQCm7wArDQkJUAABkAAOSklJRaLRpCykFpNYjIIOUkxSQiY5CC0oKnkDGIScodYwohBal2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" + )).arrayBuffer()); + AsyncSink.Audio.register("mob.duck.step", AsyncSink.Audio.Category.ANIMALS, [ + { + path: "sounds/mob/duck/step.ogg", + pitch: 1, + volume: 1, + streaming: false //use for large audio files + } + ]); + }); +})(); \ No newline at end of file diff --git a/examplemods/Tutorial_Custom_Block.js b/examplemods/Tutorial_Custom_Block.js index fced749..157524d 100644 --- a/examplemods/Tutorial_Custom_Block.js +++ b/examplemods/Tutorial_Custom_Block.js @@ -92,7 +92,7 @@ ModAPI.addEventListener("lib:asyncsink", async () => { //Add an asyncronous listener to AsyncSink loading. ModAPI.addEventListener("lib:asyncsink:registeritems", (renderItem)=>{ - renderItem.registerItem(custom_block, ModAPI.util.str("custom_block")); + renderItem.registerBlock(custom_block, ModAPI.util.str("custom_block")); }); AsyncSink.L10N.set("tile.custom_block.name", "My Custom Block"); //Set the name of the block diff --git a/examplemods/afkmod.js b/examplemods/afkmod.js new file mode 100644 index 0000000..2983495 --- /dev/null +++ b/examplemods/afkmod.js @@ -0,0 +1,28 @@ +(function AntiAFKMod() { + ModAPI.meta.title("Anti AFK Mod"); + ModAPI.meta.credits("By ZXMushroom63"); + ModAPI.meta.description("Type .afk in game chat to toggle the mod"); + ModAPI.require("player"); + var active = null; + function queueJump() { + try { + ModAPI.player.jump(); + } catch (error) { + + } + active = setTimeout(queueJump, 15000 + (10000 * Math.random())); + } + ModAPI.addEventListener("sendchatmessage", (e)=>{ + if (e.message.toLowerCase() === ".afk") { + if (active === null) { + queueJump(); + ModAPI.displayToChat("Activated anti-afk mod!\nI recommend using the sliders mod to decrease your max fps to save battery."); + } else { + clearTimeout(active); + active = null; + ModAPI.displayToChat("Deactivated anti-afk mod."); + } + e.preventDefault = true; + } + }); +})(); \ No newline at end of file diff --git a/examplemods/block_of_steve_advanced.js b/examplemods/block_of_steve_advanced.js index 5a5fbe3..b90bcd4 100644 --- a/examplemods/block_of_steve_advanced.js +++ b/examplemods/block_of_steve_advanced.js @@ -43,7 +43,7 @@ function registerSteveClientSide() { itemClass.staticMethods.registerItemBlock0.method(block_of_steve); ModAPI.addEventListener("lib:asyncsink", async () => { ModAPI.addEventListener("lib:asyncsink:registeritems", (renderItem)=>{ - renderItem.registerItem(block_of_steve, ModAPI.util.str("steve")); + renderItem.registerBlock(block_of_steve, ModAPI.util.str("steve")); }); AsyncSink.L10N.set("tile.steve.name", "Block Of Steve"); AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/models/block/steve.json", JSON.stringify( diff --git a/examplemods/block_of_steve_simple.js b/examplemods/block_of_steve_simple.js index a3cf599..1c48a19 100644 --- a/examplemods/block_of_steve_simple.js +++ b/examplemods/block_of_steve_simple.js @@ -34,7 +34,7 @@ function registerSteveClientSide() { ModAPI.addEventListener("lib:asyncsink", async () => { ModAPI.addEventListener("lib:asyncsink:registeritems", (renderItem)=>{ - renderItem.registerItem(block_of_steve, ModAPI.util.str("steve")); + renderItem.registerBlock(block_of_steve, ModAPI.util.str("steve")); }); AsyncSink.L10N.set("tile.steve.name", "Block Of Steve"); AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/models/block/steve.json", JSON.stringify( diff --git a/examplemods/cubeentity.js b/examplemods/cubeentity.js new file mode 100644 index 0000000..e0cd7ac --- /dev/null +++ b/examplemods/cubeentity.js @@ -0,0 +1,135 @@ +(function CubeEntity() { + ModAPI.meta.title("Cube Entity"); + ModAPI.meta.version("v0"); + ModAPI.meta.description("testing custom entities"); + ModAPI.meta.credits("By ZXMushroom63"); + + function registerEntity() { + // Utils + const ResourceLocation = ModAPI.reflect.getClassByName("ResourceLocation").constructors.find(x => x.length === 1); + const GlStateManager = Object.fromEntries(Object.values(ModAPI.reflect.getClassByName("GlStateManager").staticMethods).map(x => [x.methodNameShort, x.method])); + const IAnimals = ModAPI.reflect.getClassById("net.minecraft.entity.passive.IAnimals"); + + // START CUSTOM ENTITY + var entityClass = ModAPI.reflect.getClassById("net.minecraft.entity.Entity"); + var entitySuper = ModAPI.reflect.getSuper(entityClass, (x) => x.length === 2); + var nme_EntityCube = function nme_EntityCube($worldIn) { + entitySuper(this, $worldIn); + this.$preventEntitySpawning = 1; + this.$setSize(1, 1); + } + ModAPI.reflect.prototypeStack(entityClass, nme_EntityCube); + + //Turns out that minecraft 1.8's networking code is really stupid. Notch hardcoded every entity except for ones that implement the IAnimals interface. + //I don't know why, and I don't really care either. As long as it works (been working on this for too long, losing sanity) + ModAPI.reflect.implements(nme_EntityCube, IAnimals); + + nme_EntityCube.prototype.$canTriggerWalking = function () { return 0 }; + nme_EntityCube.prototype.$canBePushed = function () { return 1 }; + nme_EntityCube.prototype.$getCollisionBox = function () { return this.$getEntityBoundingBox() }; + nme_EntityCube.prototype.$getCollisionBoundingBox = function () { return this.$getEntityBoundingBox() }; + nme_EntityCube.prototype.$readEntityFromNBT = function (nbtTagCompount) { // Needed, is an abstract method in parent class + nbtTagCompount = ModAPI.util.wrap(nbtTagCompount); + }; + nme_EntityCube.prototype.$writeEntityToNBT = function (nbtTagCompount) { // Needed, is an abstract method in parent class + nbtTagCompount = ModAPI.util.wrap(nbtTagCompount); + }; + nme_EntityCube.prototype.$entityInit = function () { + console.log("Cube entity created!"); + }; + // END CUSTOM ENTITY + + + // START CUSTOM MODEL + var ModelRenderer = ModAPI.reflect.getClassById("net.minecraft.client.model.ModelRenderer").constructors.find(x => x.length === 1); + var modelBaseClass = ModAPI.reflect.getClassById("net.minecraft.client.model.ModelBase"); + var modelBaseSuper = ModAPI.reflect.getSuper(modelBaseClass); //while super isn't used when extending this class, java implies the call. + var nmcm_ModelCube = function nmcm_ModelCube() { + modelBaseSuper(this); + this.$textureWidth = 64; + this.$textureHeight = 64; + this.$cubeRenderer = ModelRenderer(this).$setTextureOffset(0, 0); + this.$cubeRenderer.$addBox0(0, 0, 0, 64, 64, 64); + this.$cubeRenderer.$setRotationPoint(0, 0, 0); + } + ModAPI.reflect.prototypeStack(modelBaseClass, nmcm_ModelCube); + nmcm_ModelCube.prototype.$render = function ($entity, useless1, useless2, partialTicks, useless3, useless4, degToRad) { + this.$cubeRenderer.$render(degToRad); + } + // END CUSTOM MODEL + + + // START CUSTOM RENDERER + var renderClass = ModAPI.reflect.getClassById("net.minecraft.client.renderer.entity.Render"); + var renderSuper = ModAPI.reflect.getSuper(renderClass, (x) => x.length === 2); + const cubeTextures = ResourceLocation(ModAPI.util.str("textures/entity/cube.png")); + var nmcre_RenderCube = function nmcre_RenderCube(renderManager) { + renderSuper(this, renderManager); + this.$modelCube = new nmcm_ModelCube(); + this.$shadowSize = 0.5; + } + ModAPI.reflect.prototypeStack(renderClass, nmcre_RenderCube); + nmcre_RenderCube.prototype.$getEntityTexture = function (entity) { + return cubeTextures; + } + const parentDoRender = nmcre_RenderCube.prototype.$doRender; + nmcre_RenderCube.prototype.$doRender = function (entity, x, y, z, yaw, pitch) { + GlStateManager.pushMatrix(); + GlStateManager.translate(x + 0.5, y, z + 0.5); + GlStateManager.rotate(180 - yaw, 0, 1, 0); + GlStateManager.scale(0.25, 0.25, 0.25); + this.$bindEntityTexture(entity); + this.$modelCube.$render(entity, 0, 0, -0.1, 0, 0, 0.0625); + GlStateManager.popMatrix(); + parentDoRender.apply(this, [entity, x, y, z, yaw, pitch]); + } + const ID = ModAPI.keygen.entity("cube"); + ModAPI.reflect.getClassById("net.minecraft.entity.EntityList").staticMethods.addMapping0.method( + ModAPI.util.asClass(nme_EntityCube), + { + $createEntity: function ($worldIn) { + return new nme_EntityCube($worldIn); + } + }, + ModAPI.util.str("Cube"), + ID, + 0x000000, //egg base + 0x00FF00 //egg spots + ); + // Note that the spawn egg for this will not work, as spawn eggs only spawn entities that are a subclass of EntityLivingBase + console.log(ID); + + ModAPI.addEventListener("lib:asyncsink", async () => { + AsyncSink.L10N.set("entity.Cube.name", "Cube (TM)"); + }); + + + return { + EntityCube: nme_EntityCube, + ModelCube: nmcm_ModelCube, + RenderCube: nmcre_RenderCube, + cubeTexture: cubeTextures + } + } + + ModAPI.dedicatedServer.appendCode(registerEntity); + var data = registerEntity(); + + ModAPI.addEventListener("lib:asyncsink", async () => { + AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/textures/entity/cube.png", await (await fetch( + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAQBJREFUeF7l0BFzAmAAgOGvKxgMgiAYDIJgEARBEASDQTAIgiAYBEEQBN0NBkEQBEEQBIMgCAZBEAwGgyAIgiAIgiConxE88PJ790RCCNdYCOGeRe/4j4SYDvCgAzzqAHEdIKEDJHWAJx3gWQdI6QBpHeBFB8joAFkdIKcD5HWAgg5Q1AFedYA3HaCkA7zrAGUdoKIDVHWAmg7woQPUdYCGDtDUAVo6QFsH6OgAnzrAlw7Q1QF6OkBfBxjoAEMdYKQDjHWAiQ7wrQNMdYCZDjDXAX50gIUOsNQBVjrArw7wpwP86wBrHWCjA2x1gJ0OsNcBDjrAUQc46QBnHeBiA9wALSueIjTE4PwAAAAASUVORK5CYII=" + )).arrayBuffer()); + AsyncSink.hideFile("resourcepacks/AsyncSinkLib/assets/minecraft/textures/entity/cube.png.mcmeta"); + ModAPI.mc.renderManager.entityRenderMap.put(ModAPI.util.asClass(data.EntityCube), new data.RenderCube(ModAPI.mc.renderManager.getRef())); + ModAPI.promisify(ModAPI.mc.renderEngine.bindTexture)(data.cubeTexture).then(() => { + console.log("Loaded cube texture into cache."); + }); + }); + console.log(data); + window.temp1 = data; +})(); +//var cube_man = new temp1.EntityCube(ModAPI.mc.theWorld.getRef()); +//cube_man.$setPosition(-191, 74, 26); +//AsyncSink.startDebugging(); +//AsyncSink.startDebuggingFS(); +//ModAPI.mc.theWorld.spawnEntityInWorld(cube_man); \ No newline at end of file diff --git a/examplemods/guns.js b/examplemods/guns.js index 5bca369..215d926 100644 --- a/examplemods/guns.js +++ b/examplemods/guns.js @@ -5,6 +5,8 @@ ModAPI.meta.icon(itemTexture); ModAPI.meta.description("Requires AsyncSink."); + ModAPI.require("player"); + function PistolItem() { var recoilSpeed = 0; //recoil controller var DamageSourceClass = ModAPI.reflect.getClassByName("DamageSource"); diff --git a/examplemods/guns_craftable.js b/examplemods/guns_craftable.js index 9dc6286..e0275e7 100644 --- a/examplemods/guns_craftable.js +++ b/examplemods/guns_craftable.js @@ -5,6 +5,8 @@ ModAPI.meta.icon(itemTexture); ModAPI.meta.description("Requires AsyncSink."); + ModAPI.require("player"); + function PistolItem() { var recoilSpeed = 0; //recoil controller var DamageSourceClass = ModAPI.reflect.getClassByName("DamageSource"); diff --git a/examplemods/unlucky_blocks.js b/examplemods/unlucky_blocks.js index ba048a0..90e613c 100644 --- a/examplemods/unlucky_blocks.js +++ b/examplemods/unlucky_blocks.js @@ -87,7 +87,7 @@ var block_of_unluckiness = UnluckyBlocks(); ModAPI.addEventListener("lib:asyncsink", async () => { ModAPI.addEventListener("lib:asyncsink:registeritems", (renderItem)=>{ - renderItem.registerItem(block_of_unluckiness, ModAPI.util.str("unluckiness")); + renderItem.registerBlock(block_of_unluckiness, ModAPI.util.str("unluckiness")); }); AsyncSink.L10N.set("tile.unluckiness.name", "Unlucky Block"); AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/models/block/unluckiness.json", JSON.stringify( diff --git a/index.html b/index.html index ffbb2b7..6032025 100644 --- a/index.html +++ b/index.html @@ -151,6 +151,7 @@ ModAPI.hooks ||= {}; ModAPI.hooks.freezeCallstack = false; ModAPI.hooks._rippedData ||= []; + ModAPI.hooks._rippedInterfaceMap ||= {}; ModAPI.hooks._teavm ||= {}; ModAPI.hooks._rippedConstructors ||= {}; ModAPI.hooks._rippedInternalConstructors ||= {}; diff --git a/injector.js b/injector.js index 7048e86..30b93ce 100644 --- a/injector.js +++ b/injector.js @@ -1,4 +1,4 @@ -globalThis.ModAPIVersion = "v2.6"; +globalThis.ModAPIVersion = "v2.7"; globalThis.doEaglerforge = true; document.querySelector("title").innerText = `EaglerForge Injector ${ModAPIVersion}`; document.querySelector("h1").innerText = `EaglerForge Injector ${ModAPIVersion}`; @@ -50,13 +50,13 @@ function entriesToStaticVariableProxy(entries, prefix, clinitList) { .join(",")}]; /*/ var proxy = `ModAPI.hooks._rippedStaticProperties[\`${prefix.replace( - "var ", - "" - )}\`] = new Proxy({${entries - .flatMap((x) => { - return '"' + x.name + '"'; - }) - .join(":null,") + (entries.length > 0 ? ":null" : "") + "var ", + "" + )}\`] = new Proxy({${entries + .flatMap((x) => { + return '"' + x.name + '"'; + }) + .join(":null,") + (entries.length > 0 ? ":null" : "") }}, { get: function (a,b,c) { switch (b) { @@ -136,7 +136,7 @@ var main;(function(){` 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()); + 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..."); @@ -171,7 +171,7 @@ var main;(function(){` ); } ); - + 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 @@ -205,6 +205,7 @@ var main;(function(){` }).filter(x => { return (!x.includes("$_clinit_$")) && (!x.includes("$lambda$")) }); + //Also stores classes from $rt_classWithoutFields(0) patchedFile = patchedFile.replaceAll( /var \S+?_\S+? = \$rt_classWithoutFields\(\S*?\);/gm, function (match) { @@ -213,6 +214,7 @@ var main;(function(){` "" ); var entries = []; + staticVariables.forEach((entry) => { if (entry.startsWith(prefix)) { var variableName = entry @@ -229,14 +231,17 @@ var main;(function(){` }); var proxy = entriesToStaticVariableProxy(entries, prefix, clinitList); - - return match + proxy; + var shortPrefix = prefix.replace( + "var ", + "" + ); + return match + `ModAPI.hooks._rippedInterfaceMap[\`${shortPrefix}\`]=${shortPrefix};` + proxy; } ); //Edge cases. sigh //Done: add support for static properties on classes with constructors like this: function nmcg_GuiMainMenu() { - + patchedFile = patchedFile.replaceAll( /function [a-z]+?_([a-zA-Z0-9\$]+?)\(\) \{/gm, (match) => { @@ -335,7 +340,7 @@ document.querySelector("#giveme").addEventListener("click", () => { } else if (globalThis.doShronk) { patchedFile = await shronk(patchedFile); } - + patchedFile.replace(`{"._|_libserverside_|_."}`, ""); var blob = new Blob([patchedFile], { type: file.type }); saveAs(blob, "processed." + fileType); @@ -356,13 +361,13 @@ document.querySelector("#givemeserver").addEventListener("click", () => { file.text().then(async (string) => { var patchedFile = string; - + if (globalThis.doEaglerforge) { patchedFile = await processClasses(patchedFile); } else if (globalThis.doShronk) { patchedFile = await shronk(patchedFile); } - + patchedFile.replace(`{"._|_libserverside_|_."}`, `(${EFServer.toString()})()`); var blob = new Blob([patchedFile], { type: file.type }); saveAs(blob, "efserver." + fileType); diff --git a/postinit.js b/postinit.js index 344d338..53b8868 100644 --- a/postinit.js +++ b/postinit.js @@ -166,6 +166,8 @@ globalThis.modapi_postinit = "(" + (() => { return name; } + ModAPI.util.asClass = ModAPI.hooks._teavm.$rt_cls; + ModAPI.util.wrap = function (outputValue, target, corrective, disableFunctions) { target ||= {}; corrective ||= false; @@ -187,9 +189,9 @@ globalThis.modapi_postinit = "(" + (() => { if (!disableFunctions && outputValue && typeof outputValue === "function" && target) { return function (...args) { var xOut = outputValue.apply(target, args); - if (xOut && typeof xOut === "object" && Array.isArray(xOut.data) && typeof outputValue.type === "function") { + if (xOut && typeof xOut === "object" && Array.isArray(xOut.data) && typeof xOut.type === "function") { if (corrective) { - return new Proxy(outputValue.data, CorrectiveArray); + return new Proxy(xOut.data, CorrectiveArray); } return new Proxy(xOut.data, ModAPI.util.TeaVMArray_To_Recursive_BaseData_ProxyConf); } @@ -267,6 +269,7 @@ globalThis.modapi_postinit = "(" + (() => { ModAPI.hooks._rippedConstructorKeys = Object.keys(ModAPI.hooks._rippedConstructors); ModAPI.hooks._rippedInternalConstructorKeys = Object.keys(ModAPI.hooks._rippedInternalConstructors); ModAPI.hooks._rippedMethodKeys = Object.keys(ModAPI.hooks._rippedMethodTypeMap); + ModAPI.hooks._rippedInterfaceKeys = Object.keys(ModAPI.hooks._rippedInterfaceMap); var compiledNames = new Set(); var metaMap = {}; @@ -300,10 +303,19 @@ globalThis.modapi_postinit = "(" + (() => { } }); + ModAPI.hooks._rippedInterfaceKeys.forEach(className => { + if (typeof className === "string" && className.length > 0) { + //Interfaces using $rt_classWithoutFields(0) and no constructors. + if (className && className.includes("_")) { + compiledNames.add(className); + } + } + }); + //Initialise all compiled names into the class map compiledNames.forEach(compiledName => { - var item = metaMap[compiledName]; + var item = metaMap[compiledName] || ModAPI.hooks._rippedInterfaceMap[compiledName]; var classId = item?.$meta?.name || null; if (!ModAPI.hooks._classMap[compiledName]) { @@ -319,7 +331,7 @@ globalThis.modapi_postinit = "(" + (() => { "staticVariables": {}, "staticVariableNames": [], "class": item || null, - "hasMeta": !!item, + "hasMeta": !!(item?.$meta), "instanceOf": function (object) { try { return ModAPI.hooks._teavm.$rt_isInstance(object, item || null); @@ -341,15 +353,14 @@ globalThis.modapi_postinit = "(" + (() => { return this.constructors[i]; } } - } + }, } } + if (typeof item?.$meta?.superclass === "function" && item?.$meta?.superclass?.$meta) { - ModAPI.hooks._classMap[compiledName].superclassName = item.$meta.superclass.$meta.name; - ModAPI.hooks._classMap[compiledName].superclass = item.$meta.superclass; + ModAPI.hooks._classMap[compiledName].superclassRaw = item.$meta.superclass; } else { - ModAPI.hooks._classMap[compiledName].superclass = null; - ModAPI.hooks._classMap[compiledName].superclassName = null; + ModAPI.hooks._classMap[compiledName].superclassRaw = null; } if (item?.["$$constructor$$"]) { @@ -385,9 +396,14 @@ globalThis.modapi_postinit = "(" + (() => { //Prototype Injection, allows for far easier access to methods if (typeof item === "function" && ModAPI.hooks._rippedMethodTypeMap[method] === "instance") { - item.prototype["$" + method.replace(compiledName + "_", "")] ||= function (...args) { + var prototypeInjectedMethod = function prototypeInjectedMethod(...args) { return ModAPI.hooks.methods[method].apply(this, [this, ...args]); } + if ((item.prototype["$" + method.replace(compiledName + "_", "")]?.name ?? "prototypeInjectedMethod") === "prototypeInjectedMethod") { + item.prototype["$" + method.replace(compiledName + "_", "")] = prototypeInjectedMethod; + } else { + item.prototype["$" + method.replace(compiledName + "_", "")] ||= prototypeInjectedMethod; + } } } }); @@ -396,7 +412,19 @@ globalThis.modapi_postinit = "(" + (() => { })); ModAPI.hooks._classMap[compiledName].staticVariableNames = Object.keys(ModAPI.hooks._classMap[compiledName].staticVariables); }); + + //populate superclasses + compiledNames.forEach(compiledName => { + var item = ModAPI.hooks._classMap[compiledName]; + if (item.superclassRaw) { + item.superclass = ModAPI.hooks._classMap[item.superclassRaw.name]; + } else { + item.superclass = null; + } + }); + ModAPI.reflect.classes = Object.values(ModAPI.hooks._classMap); + ModAPI.reflect.classMap = ModAPI.hooks._classMap; console.log("[ModAPI] Regenerated hook classmap."); } ModAPI.hooks.regenerateClassMap(); @@ -411,8 +439,14 @@ globalThis.modapi_postinit = "(" + (() => { //Magical function for making a subclass with a custom constructor that you can easily use super(...) on. ModAPI.reflect.getSuper = function getSuper(reflectClass, filter) { - filter ||= () => true; + filter ||= (x) => x.length === 1; + while (!reflectClass.internalConstructors.find(filter) && reflectClass.superclass) { + reflectClass = reflectClass.superclass; + } var initialiser = reflectClass.internalConstructors.find(filter); + if (!initialiser) { + throw new Error("[ModAPI] Failed to find matching superclass constructor in tree."); + } return function superFunction(thisArg, ...extra_args) { reflectClass.class.call(thisArg); initialiser(thisArg, ...extra_args); @@ -427,18 +461,27 @@ globalThis.modapi_postinit = "(" + (() => { item: null, supertypes: [reflectClass.class] }; + classFn.classObject = null; + } + ModAPI.reflect.implements = function impl(classFn, reflectClass) { + classFn.$meta ||= {}; + classFn.$meta.supertypes ||= []; + if (reflectClass && reflectClass.class) { + classFn.$meta.supertypes.push(reflectClass.class); + } } var reloadDeprecationWarnings = 0; const TeaVMArray_To_Recursive_BaseData_ProxyConf = { get(target, prop, receiver) { + var corrective = !!this._corrective; if (prop === "getRef") { return function () { return target; } } var outputValue = Reflect.get(target, prop, receiver); - var wrapped = ModAPI.util.wrap(outputValue, target, this._corrective, true); + var wrapped = ModAPI.util.wrap(outputValue, target, corrective, true); if (wrapped) { return wrapped; } @@ -460,6 +503,7 @@ globalThis.modapi_postinit = "(" + (() => { return ("$" + prop) in target; }, get(target, prop, receiver) { + var corrective = !!this._corrective; if (prop === "getCorrective") { return function () { return new Proxy(target, patchProxyConfToCorrective(TeaVM_to_Recursive_BaseData_ProxyConf)); @@ -467,7 +511,7 @@ globalThis.modapi_postinit = "(" + (() => { } if (prop === "isCorrective") { return function () { - return !!this._corrective; + return corrective; } } if (prop === "getRef") { @@ -484,22 +528,26 @@ globalThis.modapi_postinit = "(" + (() => { } } if (prop === "isModProxy") { - return true + return true; } var outProp = "$" + prop; - if (this._corrective) { + if (corrective) { outProp = ModAPI.util.getNearestProperty(target, outProp); } var outputValue = Reflect.get(target, outProp, receiver); - var wrapped = ModAPI.util.wrap(outputValue, target, this._corrective, false); + var wrapped = ModAPI.util.wrap(outputValue, target, corrective, false); if (wrapped) { return wrapped; } return outputValue; }, set(object, prop, value) { + var corrective = !!this._corrective; var outProp = "$" + prop; + if (corrective) { + outProp = ModAPI.util.getNearestProperty(object, outProp); + } object[outProp] = value; return true; }, @@ -1019,9 +1067,9 @@ globalThis.modapi_postinit = "(" + (() => { ModAPI.util.getBlockFromItem = easyStaticMethod("net.minecraft.block.Block", "getBlockFromItem", true); ModAPI.util.getIdFromBlock = easyStaticMethod("net.minecraft.block.Block", "getIdFromBlock", true); - function qhash(txt, arr) { - var interval = 4095; //used to be 4095 - arr.length, but that increases incompatibility based on load order and otehr circumstances - if (arr.length >= 4095) { + function qhash(txt, arr, interval) { + // var interval = 4095; //used to be 4095 - arr.length, but that increases incompatibility based on load order and other circumstances + if (arr.length >= interval) { console.error("[ModAPI.keygen] Ran out of IDs while generating for " + txt); return -1; } @@ -1049,10 +1097,15 @@ globalThis.modapi_postinit = "(" + (() => { } ModAPI.keygen.item = function (item) { var values = [...ModAPI.reflect.getClassById("net.minecraft.item.Item").staticVariables.itemRegistry.$modapi_specmap.values()]; - return qhash(item, values); + return qhash(item, values, 4095); } ModAPI.keygen.block = function (block) { var values = [...ModAPI.reflect.getClassById("net.minecraft.block.Block").staticVariables.blockRegistry.$modapi_specmap.values()]; - return qhash(block, values); + return qhash(block, values, 4095); + } + 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)); + return qhash(entity, values, 127); } }).toString() + ")();";