Lodestone — a C# WPF Minecraft Mod Manager
Drag-and-drop .NET 10 WPF app for Minecraft: Java Edition, sitting on a Clean Architecture core, and a compatibility engine that refuses to cry wolf.
Most mod managers are a folder, a prayer, and a config file you're afraid to touch. Lodestone is drag-and-drop on the surface and an over-engineered, fully unit-tested layered architecture underneath. Here's the build, the bits that were actually hard, and the things I haven't finished yet.
Modding Minecraft is one of those things that's simple right up until it isn't. You download a .jar, drop it in a folder, and it works - until you have three game versions, two loaders, a mod that needs a library that needs a specific build of another library, and a launcher that quietly shares a single mods folder across all of it. At that point it stops being a hobby and becomes a spreadsheet, a leap of faith, and a crash log.
Lodestone is my attempt to make that boring again. Drop a file on the window and it works out whether it's a mod, a resource pack or a shader and files it in the right place for the version you've got selected. Browse Modrinth, install in a click, and get a small symbol next to anything that's about to break - a missing dependency, a conflict, the wrong game version. No config files. No profiles to hand-edit.
That's the pitch. The interesting part is underneath it. The application was made with dedication to Windows OS. But versions for MacOS and Linux are planned.
The shape of the thing
Lodestone is a native Windows app - .NET 10, WPF, MIT-licensed - but I built it the way you'd build a backend service, because the alternative is the load-bearing while(true) loop that nobody dares touch in two years. It's a Clean / Onion layering with MVVM only at the very edge, and the one rule that makes the whole thing work: dependencies point inward. The domain knows nothing about WPF, HTTP or the file system. Those are details that plug into interfaces defined further in.
This isn't architecture for the sake of a diagram. It buys two concrete things. The entire decision-making core - the install pipeline, compatibility detection, update resolution - is exercised by unit tests with no UI, no network and no disk. And every external dependency is a swap behind an interface rather than a rewrite: Modrinth versus CurseForge, JSON-on-disk versus a future database, WPF versus a future macOS shell.
The proof that the core is genuinely UI-agnostic is that there's a second front end. The same Application and Infrastructure layers also run headless from a CLI - today it hosts the offline licence tooling, but it exists mostly to keep me honest. If the core ever quietly grows a dependency on WPF, the CLI stops compiling.
The bits that were actually hard
None of the hard parts were the UI. They were all about modelling what Minecraft actually does, which is rarely what you'd design if you started from scratch.
Profiles, when there's only one folder
Here's the constraint that shaped half the app: the vanilla launcher uses one mods folder for every game version. There are no per-version folders to organise. So a Lodestone "profile" (1.21.4 on Fabric, say) can't be a directory - it has to be a metadata concept layered on top of one shared folder.
Switching profile, then, means enabling every mod that belongs to the target version-and-loader pair and disabling the rest - not by deleting anything, but by toggling a .disabled suffix the loaders ignore. Fully reversible; switching back re-enables the previous set in seconds. The fiddly bit was respecting intent:
bool belongs = item.Loader == loader && item.SupportsVersion(version);
// A mod the user deliberately turned off stays off, even inside the
// profile it belongs to - otherwise switching away and back would
// silently re-enable it. Only mods set aside for a different
// loader/version are the switch's to flip.
bool desiredEnabled = belongs && !item.UserDisabled;
That one boolean is the difference between an app that respects your choices and one that quietly undoes them behind your back.
A compatibility engine that doesn't cry wolf
The dependency and conflict checker is a chain of responsibility: a pipeline of small, independent rules, each owning exactly one class of problem and individually unit-tested.
public interface ICompatibilityRule
{
IEnumerable<CompatibilityIssue> Evaluate(
InstalledContent item,
CompatibilityContext context,
CompatibilityIndex index);
}
The design philosophy mattered more than the pattern. Mod metadata is patchy and inconsistent, so the engine is deliberately conservative: missing data is treated as unknown, never as an error. It raises a flag only on explicit declarations it fully understands. A tool that flags everything as broken teaches you to ignore it within a day, so a false positive is treated as a worse bug than a false negative.
Comparing versions that refuse to be compared
Mods version themselves with magnificent inconsistency: 0.5.8, 19.5.0, r5.3, 1.8.1+1.21. The comparer reads the leading numeric components and, crucially, has an opinion about uncertainty:
// Numeric parts equal but the strings differ -> fall back to ordinal
// so the ordering stays stable. The point: when it genuinely can't
// decide, an ambiguous case surfaces as an *available update the user
// can choose to take* - never an unwanted automatic decision.
The same caution runs through the constraint checker, which only evaluates simple comparators against clean numeric versions and returns unknown for anything richer. Better to stay quiet than to be confidently wrong about someone's mod list.
Trusting nothing you download
Three threats, three guards, none optional. Every download is verified against the SHA-512 the source publishes before it's allowed near the game folder - a failed check is discarded. Archives are read entirely in memory to extract their metadata and never unpacked to disk, which closes off zip-slip path traversal from a malicious file. And because mod descriptions are untrusted Markdown-with-embedded-HTML, they render in a sandboxed WebView with JavaScript disabled and a strict Content-Security-Policy, so the worst a hostile description can do is render some text.
A supporter system with no server
Lodestone is free, and the optional supporter perks are cosmetic, but I still wanted a real licence mechanism - without standing up a backend or putting a payment processor inside the app. The answer was offline-verified, signed codes. A code is base64url(payload).base64url(signature), signed with ECDSA P-256; only the public key ships in the binary, and the private key never does. The redeemed code is stored verbatim and its signature re-checked on every load, so editing the saved file can't fabricate status. The one-hour activation window lives in the app, not the code, so a leaked code can't extend its own life. It's the kind of problem where the satisfying solution is mostly about deciding what you don't need to build.
Shipping it
The release story is the one I'm quietly proudest of, because it's the part most side projects skip. Push a v* git tag and GitHub Actions builds, tests, packages and publishes a release; every installed copy picks it up on next launch and applies a small delta in the background. No re-emailing anyone, no "please reinstall". Pre-release tags ship a supporters-first beta on the same feed.
Two deliberate restraints round it off. There is no background daemon - updates are checked on launch and on demand, full stop, so Lodestone costs nothing while it isn't open and your .minecraft only changes when you act. And every store on disk writes atomically (temp file, then swap) with corrupt files quarantined and defaults restored, so a power cut mid-write can't brick your library. Respecting the user's machine is a feature you only notice when it's missing.
To actually ship it, and 'sell it' to audience. I had to make a website. I always had some afinity for JavaScript and therefore I decided to entrust it this time. The whole thing is a single Nuxt 3 app - Vue, Tailwind, a bit of GSAP for the scroll work, with a thin server layer doing the real lifting behind it. I left SSR on from the start, mostly so link previews and search engines see proper HTML instead of an empty shell, but it also meant the no-JS path still renders fine. Felt like the grown-up choice. The one rule I set myself was that the site should never lie. So rather than hard-coding the version number and changelog and then forgetting to update them, everything that can drift is pulled live from the GitHub Releases API at runtime (I hate cashing). The trickier bit was the supporter side. Lodestone is free and always will be, but I wanted to integrate my Patreon somewhow, so patrons get a few cosmetic perks they can unlock in the app. The flow is simple to use and fiddly to build: sign in with Patreon, the server confirms an active paying pledge, then it mints a signed code the desktop app accepts offline.
So.. This is the website, feel free to have a look.
Known gaps, and why
It's pre-1.0 and the rough edges are real. Here's what isn't done, and the reasoning - because what you chose not to finish is as telling as what you did.
| Gap | Status & reasoning |
|---|---|
| Not code-signed | First-run installs trip the "Windows protected your PC" prompt. The plan is a free CA-issued certificate via the SignPath Foundation open-source programme; a self-signed cert is worthless here, as Windows treats it as unsigned. |
| CurseForge source | Wired into the registry and honoured by the fallback setting, but a configured stub until I add an API key. It reports as unconfigured and is skipped safely rather than failing loudly. |
| Forge / NeoForge install | Done by downloading and launching their official Java installers; only Fabric and Quilt are installed directly through their meta APIs. Their installers do real work I'm not about to reimplement. |
| Transitive dependencies | Rules resolve one level deep; the detail view shows the full declared tree where the source provides it. Deep resolution is a known follow-up. |
| Beta gating | Soft, not secret. The repo is public, so a pre-release is directly downloadable from GitHub. Accepted - the perks are cosmetic, not paid content. |
| Platform | Windows-only today. The core and Infrastructure are deliberately free of Windows-only APIs, so a macOS port is mostly a new UI layer rather than a rewrite - which was rather the point of the architecture. |
What I'd take from it
The lesson that stuck wasn't a technique, it was a priority. The hardest engineering in Lodestone went into modelling reality faithfully - one shared folder, versions that won't compare, metadata you can't trust, and into being conservative everywhere it counted. The flashy bit is drag-and-drop. The part I'd actually defend in a review is everything that quietly refuses to guess.
Did I use artifitial inteligence? Sure I did, I would not be able to complete this project without it, did I rely in it? No, not really. I used it mainly to bug-fix, create docs, learn new things, and verify security and architecture.
The source is on GitHub - issues are read, PRs are welcome, and the whole thing is MIT.