ServerModSync
The client pulls the server's mods itself, by file hash.
Problem
Joining a modded server starts with matching its pack by hand: a link somewhere on Discord, versions drifting apart, and one file different from the server's is a refusal at the door.
Solution
On start-up the server takes a SHA-256 of each of its own mods and serves the list over HTTP alongside the files. The client draws an icon on every entry in the server list; clicking it compares the server's list against what is on disk and downloads only what is missing or hashes differently. A file reaches the mods folder only after its hash checks out.
What it does
- An icon on every entry of the multiplayer server list, injected into the game screen by a mixin
- The server's mod list as JSON, the files under a separate route
- Comparison by mod id and SHA-256 rather than by file name
- A progress bar on the file being fetched, refreshed every 48 kilobytes
- The hash is checked after the download; a file that does not match is deleted, not installed
- Three attempts per file, a second apart, before a download counts as failed
- A list of mods to skip during a sync, in the config file
How it's built
- The shared module knows nothing about any loader; Fabric and NeoForge are four thin layers over it
- An HTTP server from Java's standard library, on two routes: the mod list and the files
- The list is built at server start from what the loader can see, hashing every JAR
- The client streams through Java 21's HttpClient, never holding a file in memory
- The file goes to .tmp, then the hash, and only then an atomic rename into place
- A watchdog on silence in the stream: no bytes for the configured span closes the connection
- The client screen is a state machine: fetching, list, downloading, error, done
What it changed
- Matching a mod pack by hand goes away: the client compares the list by SHA-256 and pulls only what is missing or different
- Twenty people are using it; a CurseForge and Modrinth release is being prepared
- A fix is written once instead of four times: one core covers Fabric and NeoForge, server side and client side
- 29 tests and 40 assertions, all of which run without starting Minecraft
- Transport with no external dependency at all: HTTP server and client from Java's standard library
Stack
- Java 21
- Fabric
- NeoForge
- Mixin
- Gradle
- Gson
Story
Escaping the folder is blocked twice, independently, at both ends. The server rejects names carrying directory traversal and serves only files on its own list. The client separately rejects an absolute URL and any path that, once resolved, falls outside the mods folder. Either check alone would do against an honest other end. Both are there so that the other end's honesty is not an assumption. Beyond the number of people using it, this entry carries no measurements from a real server; what is here is what the code and the tests can be checked against.