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.

New Project wizard for a Sacred mod
New Project wizard
Gutter icons beside a SacredMod class and an @Subscribe method
Gutter icons on the entrypoint and its listeners
Sacred Mod Development settings page
Settings → Tools → Sacred Mod Development

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.

Run Sacred button in the IDE toolbar
Run Sacred, one click from an open project

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.

terminal
1$ coderpack new gold-rush --language kotlin
2# writes build.gradle.kts, settings.gradle.kts, registry.toml,
3# and src/main/kotlin/mods/goldrush/GoldRush.kt
4
5$ cd gold-rush
6$ ./gradlew assembleSacredMod

Both 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.

EventGame actionMutableDescription
GoldGold about to changeYes: change delta, vetoDecided as a delta, never a total, so the game keeps its own mirrors in step.
ExperienceExperience about to be grantedYes: change total, vetoThe decision is the new total, and the game clamps it itself.
DamageHero about to take damage or be healedYes: change HP, vetoThe value is the HP about to be committed, clamped to the hero’s maximum.
SkillSkill value about to changeYes: change value, vetoSlots are reported by index, since the skill set differs per character.
AttributeAttribute point spentYes: change value, vetoWritten straight after the game’s own grant, before anything reads it.
CombatArtRune invested in a combat artYes: change level, vetoThe decision is the art’s new base level. A veto still spends the rune.
PickupItem about to be picked upYes: veto, replace, retype, reshapeSwap which object is picked up, or edit the one that is.
ConsoleLine typed into the consoleYes: veto (claim as a command)A veto claims the line as the mod’s command, and the game never sees it.
HeroHero found in a worldNoOnce per world load and on a character switch: class, level, HP, gold.
WorldSession phase changedNoAttached, loaded, and the moment the hero pointer stops being valid.
SaveSave file writtenNoThe moment to store per-save mod data next to the save, keyed by slot.
LoadSave file readNoArrives twice per load, as it starts and as it ends.
PositionHero movedNoFires only when the HUD coordinates change, in world and HUD units.
LevelUpHero level changedNoRead-only: the level drives the grant tables and a mirrored anti-cheat field.
DeathHero HP reached zeroNoAlready happened, with the HP before and the size of the blow.
NearDeathHero fell to 15% HP or belowNoFires once on the way down, having been above the threshold.
HealthChangedHero HP writtenNoWhat the game stored after Damage, plus the writes nobody can decide.
MaxHealthChangedMaximum HP recomputedNoOnce per recalculation after gear, a level or an attribute changed.
GoldChangedGold total movedNoThe total after any change, read a moment after the write.
ExperienceChangedExperience storedNoThe total the game actually kept, after its own clamp.
SkillChangedSkill slot holds a new valueNoWhat a Skill decision came to.
AttributeChangedAttribute holds a new valueNoWhat an Attribute decision came to.
CombatArtChangedCombat art holds a new levelNoWhat a CombatArt decision came to.
SkillPointsChangedUnspent skill points changedNoSpent on a skill or granted by a level-up.
AttributePointsChangedUnspent attribute points changedNoSpent on an attribute or granted by a level-up.
RegionHero crossed a regionNoThe game’s own map cell, entered or left. Ids only, no names.
SectorHero entered a sectorNoThe coarse grid the world streams in, once per crossing.
SpawnCreature entered the worldNoA monster spawned or an NPC streamed in. Silent while a world loads.
DespawnCreature left the worldNoA corpse cleared or a sector streamed out. Not a death.
MobHitCreature took a hitNoAny creature but the hero, with its HP before and after.
MobDeathCreature diedNoAny creature but the hero whose HP reached zero, whoever dealt the blow.
KillJournal counted a defeated opponentNoThe game’s own notion of a kill that counts, from the Statistics page.
ResurrectionHero brought back after dyingNoThe game confirms the death and counts it in the journal.
DiscoveryNew area discoveredNoThe “World Discovered” count went up. Which area isn’t known.
QuestQuest started or endedNoIdentified by the number Sacred’s quest files use, without a title.
LootCreature dropped loot or chest openedNoEvery item is already a world object a mod can retype or reshape.
DrinkPotion drunkNoBy the hero or by another creature.
TradeBought from or sold to a merchantNoThe payment follows as a Gold event, where the amount can be decided.
MovedItem dragged between slotsNoGrid indices only: a bag was rearranged, without saying what by.
EquipEquipment slot changedNoEquipping and unequipping both arrive here.
StoredItem went into an inventoryNoInto 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.