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

Windows only. Linux and macOS are not currently supported. Support will be added in a future release.

Installation

You will need Visual Studio 2022 with Desktop Development for C++ package for the autocompiler to work.

  1. Clone the repository of a supported McLCE version, or download the Nightly Source Code (zip).
  2. Download the ModLoader installer from the GitHub Releases page.
  3. Run the downloaded .exe file and complete the setup.
The compilation step of the installer opens a 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 binary
  • manifest.json — mod metadata
  • textures/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

  1. Create a blank C++ project in Visual Studio 2022 and configure it for DLL export.
  2. Download AxoAPI.h from the releases page and place it in your source files.
  3. Create YourModName.cpp with 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.");
Set 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);
Effect name is a string identifier such as "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:

012
345
678
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.");
Leave 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);
Stage textures are sourced from 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);
Colors use hex notation with the 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 via iconName in AxoBlockDef)
  • textures/items/ — item textures (referenced via iconName in AxoItemDef)

Exporting a Mod

Compress the following into a single .zip file:

  • manifest.json
  • mod.dll
  • textures/ 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.

Zoomed view