Overview
Axo McLCE ModLoader is a mod loader for Minecraft: Legacy Console Edition (McLCE) that adds full mod compatibility to the game. Mods are written in C++ and compiled as .dll files that Axo loads at runtime.
Supported platforms
- McLCE version by smartcmd
- More platforms coming soon
Installation
You will need Visual Studio 2022 with Desktop Development for C++ package for the autocompiler to work.
- Clone the repository of a supported McLCE version, or download the Nightly Source Code (zip).
- Download the ModLoader installer from the GitHub Releases page.
- Run the downloaded
.exefile and complete the setup.
cmd window. This is expected behaviour.
Updating
You only need to download a new installer when there are major changes. For all other updates, the installer fetches the latest ModLoader builds automatically from the API.
AxoAPI
AxoAPI is the official modding API for Axo. It is in full active development and lets you register custom content from inside your .dll mod.
Available features
- Custom blocks with custom drop
- Custom items
- Food items
- Custom swords, pickaxes, axes and shovels
- Custom recipes (may be slightly bugged)
- Custom block generation in the overworld and nether
- Custom plants
- Cross texture blocks
- Custom block models
- Custom world generation (custom biomes)
- Multi-sided texture for blocks
- Custom plants with seeds
- Food effects
Work in progress
- Custom entities
- Custom item models
- Custom armor
Planned features
- Custom dimensions
- And many more
Mod Setup
Every Axo mod requires the following components:
mod.dll— compiled mod binarymanifest.json— mod metadatatextures/items/— item textures (only if you add items)textures/terrain/— block textures (only if you add blocks)models/blocks/— JSON block models (only if you add 3D blocks)
Creating mod.dll
- Create a blank C++ project in Visual Studio 2022 and configure it for DLL export.
- Download
AxoAPI.hfrom the releases page and place it in your source files. - Create
YourModName.cppwith the following entry points:
#define AXO_MOD
#define MOD_ID "your_mod"
#include "AxoAPI.h"
extern "C" __declspec(dllexport)
void ModEntry(AxoMod* mod, AxoAPITable* api) {
AxoMod_SetAPI(api);
}
extern "C" __declspec(dllexport)
void OnTick() {
}
extern "C" __declspec(dllexport)
void OnShutdown() {
}
manifest.json
Place a manifest.json file alongside your mod.dll:
{
"mod_id": "example_mod",
"name": "Example Mod",
"version": "1.0.0",
"author": "you",
"description": "Simple example mod for AxoLoader"
}
Adding Blocks
Register blocks inside the void ModEntry function using AxoBlockDef. Block textures are loaded from textures/terrain/.
AxoBlockDef exampleBlock;
exampleBlock.dropItemName = "Example Item";
exampleBlock.dropCount = 1;
exampleBlock.iconName = L"example_block";
exampleBlock.name = "Example Block";
exampleBlock.hardness = 3.0f;
exampleBlock.resistance = 5.0f;
exampleBlock.creativeTab = AxoTab_BuildingBlocks;
if (AxoAPI_RegisterBlock(&exampleBlock))
AxoAPI_Log("Example Block DONE.");
else
AxoAPI_Log("Example Block ERROR.");
dropItemName to "" to make the block drop itself.
Custom Items
Register items using AxoItemDef. Item textures are loaded from textures/items/.
AxoItemDef exampleItem;
exampleItem.iconName = L"example_item";
exampleItem.name = "Example Item";
exampleItem.maxStackSize = 16;
if (AxoAPI_RegisterItem(&exampleItem))
AxoAPI_Log("Example Item DONE.");
else
AxoAPI_Log("Example Item ERROR.");
Custom Food
Food uses the same AxoItemDef structure as regular items, with additional food flags set.
AxoItemDef exampleFood;
exampleFood.iconName = L"example_food";
exampleFood.name = "Example Food";
exampleFood.maxStackSize = 16;
exampleFood.isEdible = true;
exampleFood.food.nutrition = 6;
exampleFood.food.saturation = AXO_SATURATION_GOOD;
exampleFood.food.isMeat = true;
exampleFood.food.canAlwaysEat = true;
if (AxoAPI_RegisterItem(&exampleFood))
AxoAPI_Log("Example Food DONE.");
else
AxoAPI_Log("Example Food ERROR.");
Saturation constants and other references are available in AxoAPI.h.
Food Effects
You can apply a status effect when food is consumed by setting the effect fields inside food.
AxoItemDef magicFood;
magicFood.name = "Magic Food";
magicFood.iconName = L"magic_food";
magicFood.creativeTab = AxoTab_Food;
magicFood.isEdible = true;
magicFood.food.nutrition = 4;
magicFood.food.canAlwaysEat = true;
magicFood.food.saturation = AXO_SATURATION_NORMAL;
magicFood.food.effect.effectName = "nausea";
magicFood.food.effect.duration = 200;
magicFood.food.effect.amplifier = 0;
AxoAPI_RegisterItem(&magicFood);
"nausea", "speed", "poison", etc. Duration is measured in ticks (20 ticks = 1 second).
Custom Tools
Swords
Use AxoItemDef with sword flags. The base attack damage value is 1.
AxoItemDef exampleSword;
exampleSword.iconName = L"example_sword";
exampleSword.name = "Example Sword";
exampleSword.attackDamage = 99;
exampleSword.isHandheld = true;
if (AxoAPI_RegisterItem(&exampleSword))
AxoAPI_Log("Example Sword DONE.");
else
AxoAPI_Log("Example Sword ERROR.");
Pickaxes, Axes and Shovels
Available flags are isPickaxe, isAxe, and isShovel. The base mining speed is 1.0f.
AxoItemDef examplePickaxe;
examplePickaxe.iconName = L"example_pickaxe";
examplePickaxe.name = "Example Pickaxe";
examplePickaxe.isPickaxe = true;
examplePickaxe.miningSpeed = 9.0f;
if (AxoAPI_RegisterItem(&examplePickaxe))
AxoAPI_Log("Example Pickaxe DONE.");
else
AxoAPI_Log("Example Pickaxe ERROR.");
Logging
Use AxoAPI_Log to write messages to the debug console:
AxoAPI_Log("Example Log");
Recipes
Furnace Recipe
AxoRecipeDef exampleFurnace;
exampleFurnace.isFurnace = true;
exampleFurnace.furnaceInputName = "Example Item";
exampleFurnace.resultItemName = "diamond";
exampleFurnace.resultCount = 1;
exampleFurnace.furnaceXP = 0.7f;
AxoAPI_RegisterRecipe(&exampleFurnace);
Shapeless Crafting Recipe
AxoRecipeDef exampleShapeless;
exampleShapeless.isShaped = false;
exampleShapeless.isFurnace = false;
exampleShapeless.resultItemName = "Example Item";
exampleShapeless.resultCount = 1;
exampleShapeless.ingredients[0] = "diamond";
exampleShapeless.ingredients[1] = "emerald";
exampleShapeless.ingredients[2] = "ender_pearl";
exampleShapeless.ingredientCount = 3;
exampleShapeless.recipeGroup = AxoRecipe_Armor;
AxoAPI_RegisterRecipe(&exampleShapeless);
Shaped Crafting Recipe
The crafting grid uses indices 0–8, mapped as follows:
| 0 | 1 | 2 |
| 3 | 4 | 5 |
| 6 | 7 | 8 |
AxoRecipeDef exampleShaped;
exampleShaped.isShaped = true;
exampleShaped.isFurnace = false;
exampleShaped.resultItemName = "Example Item";
exampleShaped.resultCount = 1;
exampleShaped.grid[0] = { "emerald" };
exampleShaped.grid[1] = { "diamond" };
exampleShaped.grid[2] = { "diamond" };
exampleShaped.grid[3] = { "" };
exampleShaped.grid[4] = { "stick" };
exampleShaped.grid[5] = { "" };
exampleShaped.grid[6] = { "" };
exampleShaped.grid[7] = { "stick" };
exampleShaped.grid[8] = { "" };
exampleShaped.recipeGroup = AxoRecipe_Armor;
AxoAPI_RegisterRecipe(&exampleShaped);
World Generation
Custom Ore Generation in the Overworld
Add spawn flags to a standard block definition to make it generate in the world.
AxoBlockDef exampleOre;
exampleOre.dropItemName = "Ruby";
exampleOre.dropCount = 3;
exampleOre.iconName = L"ruby_ore";
exampleOre.name = "Ruby Ore";
exampleOre.hardness = 0.5f;
exampleOre.resistance = 10.0f;
exampleOre.creativeTab = AxoTab_BuildingBlocks;
exampleOre.spawn.enabled = true;
exampleOre.spawn.likeOre = true;
exampleOre.spawn.frequency = 20;
exampleOre.spawn.veinSize = 4;
exampleOre.spawn.yLevelMin = 0;
exampleOre.spawn.yLevelMax = 32;
exampleOre.spawn.inOverworld = true;
if (AxoAPI_RegisterBlock(&exampleOre))
AxoAPI_Log("Example Ore DONE.");
else
AxoAPI_Log("Example Ore ERROR.");
Custom Ore Generation in the Nether
AxoBlockDef exampleNetherOre;
exampleNetherOre.dropItemName = "Ruby";
exampleNetherOre.dropCount = 3;
exampleNetherOre.iconName = L"hell_ore";
exampleNetherOre.name = "Hell Ore";
exampleNetherOre.hardness = 0.5f;
exampleNetherOre.resistance = 10.0f;
exampleNetherOre.creativeTab = AxoTab_BuildingBlocks;
exampleNetherOre.spawn.enabled = true;
exampleNetherOre.spawn.likeOre = true;
exampleNetherOre.spawn.inOverworld = false;
exampleNetherOre.spawn.inNether = true;
exampleNetherOre.spawn.frequency = 100;
exampleNetherOre.spawn.veinSize = 4;
exampleNetherOre.spawn.yLevelMin = 0;
exampleNetherOre.spawn.yLevelMax = 70;
if (AxoAPI_RegisterBlock(&exampleNetherOre))
AxoAPI_Log("Example Nether Ore DONE.");
else
AxoAPI_Log("Example Nether Ore ERROR.");
Custom Plants
Plants use the same block registration flow with additional spawn and render flags.
AxoBlockDef exampleFlower;
exampleFlower.name = "Glow Flower";
exampleFlower.iconName = L"flower_vines";
exampleFlower.dropItemName = "";
exampleFlower.hardness = 0.2f;
exampleFlower.resistance = 0.5f;
exampleFlower.creativeTab = AxoTab_Decoration;
exampleFlower.spawn.enabled = true;
exampleFlower.spawn.likeOre = false;
exampleFlower.spawn.likeGrass = true;
exampleFlower.spawn.onTerrain = true;
exampleFlower.spawn.onWater = false;
exampleFlower.spawn.frequency = 30;
exampleFlower.spawn.yLevelMin = 60;
exampleFlower.spawn.yLevelMax = 128;
exampleFlower.spawn.inOverworld = true;
exampleFlower.spawn.inBiome = "Plains";
exampleFlower.noCollision = true;
exampleFlower.canBePlacedOnlyOn = "grass";
exampleFlower.renderShape = AxoShape_Cross;
exampleFlower.canBeBrokenByHand = true;
if (AxoAPI_RegisterBlock(&exampleFlower))
AxoAPI_Log("Example Flower DONE.");
else
AxoAPI_Log("Example Flower ERROR.");
inBiome as "" to allow the plant to generate in every biome.
Custom Crops
Use AxoCropDef to register a crop with 8 growth stages, a seed item, and a grown drop. Crops support bonemeal acceleration automatically.
AxoCropDef crop;
crop.name = "Crop";
crop.stageTextures[0] = L"crop_stage_0";
crop.stageTextures[1] = L"crop_stage_1";
crop.stageTextures[2] = L"crop_stage_2";
crop.stageTextures[3] = L"crop_stage_3";
crop.stageTextures[4] = L"crop_stage_4";
crop.stageTextures[5] = L"crop_stage_5";
crop.stageTextures[6] = L"crop_stage_6";
crop.stageTextures[7] = L"crop_stage_7";
crop.seedIconName = L"crop_seeds";
crop.seedName = "Crop Seeds";
crop.seedCreativeTab = AxoTab_ToolsArmor;
crop.growDrop.itemName = "Custom Item";
crop.growDrop.count = 1;
crop.growDrop.seedDropCount = 1;
crop.growDrop.bonusDropMax = 2;
AxoAPI_RegisterCrop(&crop);
textures/terrain/. The seed is registered as a standalone item automatically — no separate AxoItemDef needed.
Block Models
Custom 3D Models
You can attach a JSON block model to any block by setting the customModel field. Place the model file in models/blocks/ inside your mod.
AxoBlockDef rubySlab;
rubySlab.name = "Ruby Slab";
rubySlab.iconName = L"ruby_slab_top";
rubySlab.hardness = 1.5f;
rubySlab.resistance = 10.0f;
rubySlab.creativeTab = AxoTab_BuildingBlocks;
rubySlab.customModel = "ruby_slab";
AxoAPI_RegisterBlock(&rubySlab);
Multi-Sided Texture Blocks
For blocks that need different textures on each face (like logs), set hasDifferentSides to true and assign each face individually.
AxoBlockDef crystalLog;
crystalLog.name = "Crystal Log";
crystalLog.iconName = L"crystal_log_side";
crystalLog.hasDifferentSides = true;
crystalLog.iconTop = L"crystal_log_top";
crystalLog.iconBottom = L"crystal_log_top";
crystalLog.iconNorth = L"crystal_log_side";
crystalLog.iconSouth = L"crystal_log_side";
crystalLog.iconEast = L"crystal_log_side";
crystalLog.iconWest = L"crystal_log_side";
AxoAPI_RegisterBlock(&crystalLog);
iconName is still required as a fallback placeholder when hasDifferentSides is enabled.
Custom Biomes
Use AxoBiomeDef to define a new biome with custom colors, terrain parameters, and vegetation density. Biomes are registered before world generation runs.
AxoBiomeDef crystalForest;
crystalForest.name = "Crystal Forest";
crystalForest.temperature = 0.7f;
crystalForest.downfall = 0.8f;
crystalForest.depth = 0.1f;
crystalForest.scale = 0.3f;
crystalForest.grassColor = 0x00FF88;
crystalForest.foliageColor = 0x00CC66;
crystalForest.waterColor = 0x0055FF;
crystalForest.skyColor = 0x88DDFF;
crystalForest.hasRain = true;
crystalForest.hasSnow = false;
crystalForest.spawnWeight = 8;
crystalForest.treeCount = 5;
crystalForest.grassCount = 3;
crystalForest.flowerCount = 4;
crystalForest.topMaterial = "grass";
crystalForest.material = "dirt";
crystalForest.hilliness = 1.0f;
AxoAPI_RegisterBiome(&crystalForest);
0x prefix instead of #, e.g. 0x00FF88. spawnWeight controls how frequently the biome appears relative to other biomes — higher values mean more common.
Textures & Exporting
Adding Textures
Place your texture files inside a textures/ folder alongside manifest.json and mod.dll:
textures/terrain/— block textures (referenced viaiconNameinAxoBlockDef)textures/items/— item textures (referenced viaiconNameinAxoItemDef)
Exporting a Mod
Compress the following into a single .zip file:
manifest.jsonmod.dlltextures/folder (if applicable)models/folder (if applicable)
Place the resulting .zip into the mods/ folder in your game directory and it will be loaded automatically on the next launch.
FAQ
Was AI used to create Axo?
Yes. AI was used to help find solutions to bugs, suggest code optimisations, and speed up development by analysing functions made by 4J — reducing the time needed to read through thousands of lines of code. Every line of code is understandable to humans. If you are not comfortable with that, you are not required to use Axo Loader.
My compilation takes a long time. Is that normal?
Yes. The installer compiles the entire project, which can take anywhere from 2 to 5 minutes. If it takes too long you can cancel the compilation in the installer and compile manually in Visual Studio 2022.
Is Axo malware?
No. Axo only modifies the game's code. Every file is visible and auditable in the GitHub repository.
Credits
Axo ModLoader is created and maintained by @KaDerox.
Thanks to @ZaQsu. This site was provided by him.
Contributions are welcome — see CONTRIBUTING.md on GitHub.
Special thanks to the McLCE community for keeping the Legacy Console experience alive, and to smartcmd for the McLCE port that Axo builds on.
Join the community on Discord.