For developers
Three ways to get involved
Write a mod in the IDE, create one from the terminal, or help build the loader itself.
With the IntelliJ IDEA plugin
Install Sacred Mod Development from the JetBrains Marketplace, or search for it under Settings → Plugins.
Then choose File → New → Project → Sacred Mod. The wizard asks for a name, a description, and a template: a bare entrypoint or one with a working listener. You also pick the mod’s language (Java, Kotlin, or Groovy) and the build script’s DSL (Kotlin or Groovy).
The two choices are independent, so a Groovy mod can sit behind a Kotlin build script. The wizard can also run git init and set up an SRML repository, so a launcher can install the mod from day one.
A green Run Sacred configuration appears as soon as the plugin sees dev.ancaria.coderpack in your build script. It builds the mod and installs it into the Sacred Gold folder set in Settings. If the loader release isn’t cached yet, it downloads it and checks its SHA-256. Then it starts the game with your mod loaded.
With the command line
coderpack new creates the same project from a terminal. --language picks Java, Kotlin, or Groovy, and --dsl picks the build script syntax. The two don’t have to match.
Only Java adds no runtime. A Java mod stays around 1.8 KB, a Kotlin jar carries its standard library at about 1.8 MB, and Groovy’s runtime brings it to about 7.8 MB.
A Kotlin project also gets dev.ancaria.coderpack:api-kotlin, the same API in Kotlin style. The event becomes a type argument instead of a class literal, and one on covers both watching and deciding. To change what the game is about to store, a listener calls mutate, which compiles only for events that can be decided.
The module adds nothing the Java API can’t do—every declaration forwards to it—so a Kotlin mod can drop it and call the Java API directly. The Kotlin tab on the front page uses it.
1$ coderpack new gold-rush --language kotlin2# writes build.gradle.kts, settings.gradle.kts, registry.toml,3# and src/main/kotlin/mods/goldrush/GoldRush.kt4
5$ cd gold-rush6$ ./gradlew assembleSacredModBoth build scripts produce the same descriptor:
1plugins {2 id("dev.ancaria.coderpack") version "0.200.0"3}4
5version = "1.0.0"6
7sacred {8 id = "gold-rush"9 displayName = "Gold Rush"10 description = "Tops up every gold pickup by half again"11 entrypoint = "dev.ancaria.mod.goldrush.GoldRushMod"12 author("MairwunNx (Pavel Erokhin)")13 website = "https://ancaria.dev"14 repository = "https://github.com/me/gold-rush"15 apiVersion = "0.200.0"16 installTo = layout.dir(providers.gradleProperty("sacredDir").map { file("$it/mods") })17}Covered events
These are all the events a mod can subscribe to in API 3. A mutable event arrives before the game commits the write, so a listener can return the event’s Mutation to veto it or change what gets stored. The rest report what already happened.
| Event | Game action | Mutable | Description |
|---|---|---|---|
Gold | Gold about to change | Yes: change delta, veto | Decided as a delta, never a total, so the game keeps its own mirrors in step. |
Experience | Experience about to be granted | Yes: change total, veto | The decision is the new total, and the game clamps it itself. |
Damage | Hero about to take damage or be healed | Yes: change HP, veto | The value is the HP about to be committed, clamped to the hero’s maximum. |
Skill | Skill value about to change | Yes: change value, veto | Slots are reported by index, since the skill set differs per character. |
Attribute | Attribute point spent | Yes: change value, veto | Written straight after the game’s own grant, before anything reads it. |
CombatArt | Rune invested in a combat art | Yes: change level, veto | The decision is the art’s new base level. A veto still spends the rune. |
Pickup | Item about to be picked up | Yes: veto, replace, retype, reshape | Swap which object is picked up, or edit the one that is. |
Console | Line typed into the console | Yes: veto (claim as a command) | A veto claims the line as the mod’s command, and the game never sees it. |
Hero | Hero found in a world | No | Once per world load and on a character switch: class, level, HP, gold. |
World | Session phase changed | No | Attached, loaded, and the moment the hero pointer stops being valid. |
Save | Save file written | No | The moment to store per-save mod data next to the save, keyed by slot. |
Load | Save file read | No | Arrives twice per load, as it starts and as it ends. |
Position | Hero moved | No | Fires only when the HUD coordinates change, in world and HUD units. |
LevelUp | Hero level changed | No | Read-only: the level drives the grant tables and a mirrored anti-cheat field. |
Death | Hero HP reached zero | No | Already happened, with the HP before and the size of the blow. |
NearDeath | Hero fell to 15% HP or below | No | Fires once on the way down, having been above the threshold. |
HealthChanged | Hero HP written | No | What the game stored after Damage, plus the writes nobody can decide. |
MaxHealthChanged | Maximum HP recomputed | No | Once per recalculation after gear, a level or an attribute changed. |
GoldChanged | Gold total moved | No | The total after any change, read a moment after the write. |
ExperienceChanged | Experience stored | No | The total the game actually kept, after its own clamp. |
SkillChanged | Skill slot holds a new value | No | What a Skill decision came to. |
AttributeChanged | Attribute holds a new value | No | What an Attribute decision came to. |
CombatArtChanged | Combat art holds a new level | No | What a CombatArt decision came to. |
SkillPointsChanged | Unspent skill points changed | No | Spent on a skill or granted by a level-up. |
AttributePointsChanged | Unspent attribute points changed | No | Spent on an attribute or granted by a level-up. |
Region | Hero crossed a region | No | The game’s own map cell, entered or left. Ids only, no names. |
Sector | Hero entered a sector | No | The coarse grid the world streams in, once per crossing. |
Spawn | Creature entered the world | No | A monster spawned or an NPC streamed in. Silent while a world loads. |
Despawn | Creature left the world | No | A corpse cleared or a sector streamed out. Not a death. |
MobHit | Creature took a hit | No | Any creature but the hero, with its HP before and after. |
MobDeath | Creature died | No | Any creature but the hero whose HP reached zero, whoever dealt the blow. |
Kill | Journal counted a defeated opponent | No | The game’s own notion of a kill that counts, from the Statistics page. |
Resurrection | Hero brought back after dying | No | The game confirms the death and counts it in the journal. |
Discovery | New area discovered | No | The “World Discovered” count went up. Which area isn’t known. |
Quest | Quest started or ended | No | Identified by the number Sacred’s quest files use, without a title. |
Loot | Creature dropped loot or chest opened | No | Every item is already a world object a mod can retype or reshape. |
Drink | Potion drunk | No | By the hero or by another creature. |
Trade | Bought from or sold to a merchant | No | The payment follows as a Gold event, where the amount can be decided. |
Moved | Item dragged between slots | No | Grid indices only: a bag was rearranged, without saying what by. |
Equip | Equipment slot changed | No | Equipping and unequipping both arrive here. |
Stored | Item went into an inventory | No | Into the hero’s or another creature’s inventory. |
An event that no class covers yet arrives as Unknown, with its wire name and raw fields, so a tracer subscribed to Event still sees it. The full contract is in coderpack’s EVENTS.md.
For anything that isn’t an event, getContext().getGame() acts on the game directly. getWorld().getEntityRegistry().getPlayer() returns the hero, and getConsole().print("...") writes a line to the in-game console.
Working on the platform
The loader has three parts: a Frida agent that hooks instructions inside the 32-bit game, a Rust host that carries frames between the game and the JVM, and the JVM that dispatches events to mods. Nothing patches the game on disk. How an event crosses that boundary and back is on the How it works page.
Every hook and address needs evidence before it ships. Research on pureHD.exe relies on three tools: Cheat Engine to watch memory live and find candidate writers, a PE analysis kit (pefile and Capstone) for offline disassembly and string catalogues, and Ghidra for deeper static analysis and call graphs.
A confirmed result moves from research into mappings as a VA, an RVA, and a confidence level. Shipping code never hard-codes an address.
Why pureHD.exe 2.0.2.118 and not the stock Sacred.exe? The community pureHD mod fixes a long list of the original’s bugs, keeps Sacred’s balance and feel, and adds only what’s genuinely useful. That makes it the one build worth pinning every address to.
If your folder has another build, the launcher tells you. It can fetch pureHD from ancaria.dev/files/sacred.purehd.zip and place pureHD.exe and pHD.dll next to the game, leaving the original executable alone.
