Skip to content

About

LAN Finder Fix fixes the LAN Worlds discovery on macOS (probably other platforms too)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

LAN Finder Fix

A client mod that fixes LAN world discovery on machines with multiple network interfaces (VMs, VPNs, virtual adapters, Docker/WSL bridges, etc.). Built for Fabric, NeoForge, and Forge, across Minecraft 1.14.4 through 1.21.11 plus the newer 26.x releases.

This is a known, long-standing Mojang bug - see MC-98598 ("LAN List does not populate reliably", confirmed, Category: Networking, open since 2016 with ~35 duplicate reports going back to 1.9). This mod works around it client-side until Mojang fixes it upstream.

The problem

Minecraft's client-side LAN scanner (net.minecraft.client.server.LanServerDetection$LanServerDetector) joins its multicast group with the legacy MulticastSocket#joinGroup(InetAddress) overload, which does not let you choose a network interface — it silently uses whatever the OS/JVM picks as "default". On machines with many virtual adapters (VMware/VirtualBox host-only networks, VPN tunnels, Docker bridges, WSL, etc.) that is frequently not the real LAN adapter, so "Share to LAN" worlds hosted on other machines never show up in the Multiplayer screen. This class and its fields have been structurally unchanged since at least 1.14.4, so a single fix works across every loader and version listed below.

This matches the pattern across MC-98598's duplicates almost exactly: it works on some machines and not others on the same network, it comes and goes with OS reinstalls or adapter changes, and direct-connect-by-IP always works even when the LAN list stays empty (direct connect is ordinary unicast TCP, unaffected by multicast group membership). That's the signature of an interface-selection bug, not a protocol regression — the sending side (LanServerPinger) just sends plain UDP broadcast and isn't affected the same way, which is also why this has stayed hard to pin down: it only reproduces on machines with more than one active network interface.

The fix

A single Mixin (LanServerDetectorMixin) redirects that joinGroup call. Instead of relying on the default interface, it enumerates all network interfaces, filters out ones that clearly aren't usable (down, loopback, point-to-point, no multicast support, no IPv4 address), and explicitly joins the LAN discovery multicast group via joinGroup(SocketAddress, NetworkInterface) on every interface that's left — not just one "best" pick, and deliberately not filtered by adapter name or type beyond that: a VM host-only network, VPN, or container bridge can be a legitimate LAN too if that's genuinely where the player is hosting or joining from. If no interface passes the basic checks, it falls back to the original vanilla behavior.

See LanServerDetectorMixin and LanInterfaceSelector.

Supported loaders and versions

Minecraft Fabric NeoForge Forge
1.14.4 ✅ n/a (NeoForge starts at 1.20.2) ❌ blocked - see below
1.16.5 ✅ n/a ❌ blocked - see below
1.18.2 ✅ n/a ✅
1.19.4 ✅ n/a ✅
1.20.4 ✅ ✅ ✅
1.21.1 ✅ ✅ ✅
1.21.11 ✅ ✅ ✅
26.2 (whole 26.x line) ✅ ✅ (beta NeoForge build) not attempted

Quilt: not built separately. Quilt Loader has a built-in Fabric-compatibility layer (QSL) and can load Fabric mod jars directly; this mod only uses a Mixin and a basic client init hook, well within what that layer supports. Use the Fabric jar for the matching Minecraft version.

Forge 1.14.4 and 1.16.5 are currently blocked, unlike every other target: the Mixin annotation processor reports Mixin target ... could not be found for LanServerDetection$LanServerDetector specifically on these two versions' ForgeGradle-6 compile classpath, even though the exact same class resolves fine on 1.18.2-1.20.4 (same FG6 generation) and the class is confirmed present in both versions' official Mojang mappings. Forcing the Java toolchain to 17 instead of 8 didn't change the outcome, so it isn't a Java-8-toolchain issue - the cause is still open. 1.12.2 (ForgeGradle 3, a different legacy apply plugin build style entirely) hasn't been attempted at all. 26.2 hasn't been checked for Forge (unclear whether a Forge build for the 26.x line exists yet upstream).

Fabric and NeoForge don't support Minecraft versions before 1.14/1.20.2 respectively - no mod-loading hooks exist that early. Forge itself goes back to Minecraft 1.1, but versions before roughly 1.12.2 predate Mixin support in Forge entirely (would need a hand-written ASM coremod) and rely on MCP/SRG mapping infrastructure that mostly no longer exists online - those are out of scope here.

ForgeGradle quirks discovered while building this

Getting any Forge target working required working around several real bugs/limitations in the current ForgeGradle releases, worth knowing if you touch these builds:

  • ForgeGradle 7 (1.21+) rejects Gradle 9.5 outright ("Versions Gradle 9.0 and newer are not supported yet"), but its own plugin-entry gate separately requires Gradle ≥9.3.0. The only version that satisfies both of its own contradictory checks is the 9.3.x line - that's why forge/ pins Gradle 9.3.0 specifically, not the newer 9.5.1 the root build uses.
  • ForgeGradle 6 (1.12.2-1.20.6) unconditionally refuses any Gradle ≥9.0, so it needs its own separate Gradle 8.x build entirely - see forge-legacy-*/.
  • ForgeGradle 6 on Gradle 8.14.5 crashes (ObjectInstantiationException in DefaultToolchainSpec) if the Gradle daemon itself runs under a JDK newer than 21 (e.g. JDK 25) - unrelated to which JDK your build actually targets. JAVA_HOME must point at a JDK 21 (or older, back to whatever Gradle 8.14.5 supports) when invoking ./gradlew in any forge-legacy-* directory, even though the compiled mod itself may target Java 8.
  • ForgeGradle 6's Minecraft-artifact generation is not safe to share across projects: if two ForgeGradle-6-configured projects are evaluated in the same Gradle daemon (e.g. as subprojects of one multi-project build), the artifact generation for one version silently fails to produce a resolvable dependency for another. Each FG6 version needs its own fully separate Gradle root (own settings.gradle, not just its own subproject) - that's why there's a forge-legacy-<version>/ directory per version instead of one shared forge-legacy/ multi-project build.
  • Related to the above: ForgeGradle 6's global artifact cache under ~/.gradle/caches/forge_gradle can end up in a state where a different Minecraft version's build leaves it unable to resolve/regenerate an already-built version's artifact. If a forge-legacy-* build fails with Could not find net.minecraftforge:forge:...-...-...-_mapped_official_..., delete ~/.gradle/caches/forge_gradle and retry - this was reliable in testing, but means building multiple FG6 versions back-to-back isn't fully safe without clearing that cache between them.

Project layout

The mixin/mod source is identical across every loader and version above (the LAN scanner hasn't changed), so it lives once in common/:

common/src/main/java/...      shared mod source: mixin, interface selector, LanFinderFix (logger/constants)
common/src/main/resources/    shared lan-finder-fix.mixins.json (templated per version)

fabric/src/main/java/...      Fabric entrypoint (ClientModInitializer)
fabric/src/main/resources/    fabric.mod.json (templated)
neoforge/src/main/java/...    NeoForge entrypoint (@Mod)
neoforge/src/main/resources/  neoforge.mods.toml (templated)
forge/src/main/java/...       Forge entrypoint (@Mod) - shared by both Forge sub-builds
forge/src/main/resources/     mods.toml (templated)

versions/remap.gradle         shared Fabric build logic, obfuscated releases (Mojang mappings)
versions/noremap.gradle       shared Fabric build logic, unobfuscated releases (26.x, no mappings)
versions/neoforge.gradle      shared NeoForge build logic (ModDevGradle)
versions/<mc>-fabric/         one subproject per Fabric target: build.gradle + gradle.properties
versions/<mc>-neoforge/       one subproject per NeoForge target: build.gradle + gradle.properties

Forge is split into two separate Gradle builds, neither part of the root build - see "ForgeGradle quirks" above for why:

forge/                        ForgeGradle 7 (1.21+), own settings.gradle/wrapper (Gradle 9.3.0)
forge/versions/<mc>/           one subproject per FG7 target

forge-legacy-<mc>/             ForgeGradle 6 (1.18.2-1.20.4), one fully independent Gradle
                                root PER version, own settings.gradle/wrapper (Gradle 8.14.5)
                                - build-logic.gradle and build.gradle live directly in each

Each <version>/build.gradle just declares the right plugin and applies the matching shared script; gradle.properties next to it (or in the same directory, for forge-legacy-*) supplies the version-specific values (Minecraft/loader version, Java target, Mixin compatibility level, dependency version ranges).

Building

./gradlew build                        # Fabric + NeoForge, every version
cd forge && ./gradlew build            # Forge 1.21.1 / 1.21.11 (needs Gradle 9.3.0, auto-fetched by the wrapper)
cd forge-legacy-1.20.4 && JAVA_HOME=<path to a JDK 21 or older> ./gradlew build   # Forge 1.18.2/1.19.4/1.20.4, one at a time

Each target produces its own jar, e.g. versions/1.21.11-fabric/build/libs/lan-finder-fix-1.21.11-fabric-1.0.0.jar, forge/versions/1.21.11/build/libs/lan-finder-fix-1.21.11-forge-1.0.0.jar, or forge-legacy-1.20.4/build/libs/lan-finder-fix-1.20.4-forge-1.0.0.jar. Install whichever one matches your game version and loader. To build just one Fabric/NeoForge target:

./gradlew :v1_21_11_fabric:build
./gradlew :v1_21_11_neoforge:build

(subproject names replace dots with underscores - see the respective settings.gradle)

Requirements

  • One of the Minecraft versions/loaders listed above
  • Fabric Loader >= 0.19.3 / NeoForge / Forge, matching your target
  • Fabric API (Fabric only)
  • Java 8, 17, 21, or 25 depending on target (see the loader-specific gradle.properties files) - but see "ForgeGradle quirks" above: forge-legacy-* builds need JAVA_HOME set to JDK 21 or older regardless of the target's own Java version, due to a ForgeGradle 6 bug

Publishing to CurseForge and Modrinth

.github/workflows/release.yml builds every target above and publishes each jar to both CurseForge and Modrinth whenever a GitHub Release is published (or manually via "Run workflow" - use its dry_run input to build and resolve everything without actually uploading). scripts/publish.py does the actual uploading, driven by scripts/targets.json (one entry per loader/version combination, matching the "Supported loaders and versions" table above - Forge 1.14.4/1.16.5 are deliberately left out since that build is currently broken).

The mod version published is the release's tag name (a leading v is stripped, e.g. tag v1.0.1 publishes as 1.0.1), and the changelog is the release's description text. Both get passed straight through to -Pmod_version=... for every Gradle build and to the CurseForge/Modrinth API calls.

Setup: CURSEFORGE_API_TOKEN, MODRINTH_API_TOKEN, and CURSEFORGE_PROJECT_ID (the numeric ID CurseForge shows on the project's authors page, e.g. authors.curseforge.com/#/projects/<id>/general) all exist as repo secrets (Settings → Secrets and variables → Actions → Secrets) - CurseForge's project ID isn't actually sensitive, but it's kept alongside the tokens for simplicity. The Modrinth project slug is hardcoded in the workflow as lan-finder-fix; change it there if that project ends up under a different slug.

About

LAN Finder Fix fixes the LAN Worlds discovery on macOS (probably other platforms too)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages