Skip to content

Releases: luibara2/endlink

Endlink v0.3.0

Choose a tag to compare

@luibara2 luibara2 released this 17 Aug 12:52

A backend can now be a Minecraft: Java server, reached through a
Geyser instance the proxy treats as an ordinary Bedrock backend. Bedrock
players are sent to it alongside the Bedrock ones and keep their identity, their permissions and
their player data. Install
EndlinkGuard's geyser/ build into that Geyser's
extensions/ folder; nothing in Endlink's config needs setting.

Moving to or from a Java backend reconnects the player to the proxy rather than handing them
over, and that cannot be worked around. A Bedrock client reads its block-id scheme from the
StartGame it logs in with and can never be told otherwise while it is playing: Bedrock servers hash
block ids from the block state, Geyser numbers them by palette position. A seamless handoff across
that boundary delivers chunks the client cannot decode, and the player stands in an empty or
scrambled world with nothing in any log to explain it.

So Endlink learns each backend's scheme from its first StartGame, remembers it, and moves players
across that boundary by transferring them to its own address, putting them where they asked.
They never leave the proxy - same listener, same verification, same permissions, and backends stay
unreachable from outside. They see a loading screen. Switches between backends of the same kind are
untouched and stay seamless.

Also fixed

  • A palette-indexed backend's block registry is left alone. The cross-backend palette was
    merging every other backend's custom blocks into it and zeroing its checksum. Where block ids are
    palette indices that renumbers the world out from under the client - wrong blocks everywhere,
    unknown blocks past the end - and clearing the checksum removed the client's own mismatch check,
    so it corrupted the world silently instead of failing cleanly. It was also pushing such a
    backend's blocks into every other backend's registry.
  • Sub-chunk requests are withheld from a backend that does not implement them. A client taught
    to request terrain that way by a Bedrock backend goes on doing it after a switch, and Geyser
    treats those requests as a protocol violation and drops the player mid-game. Inferred
    automatically; backend.<name>.dropSubChunkRequests remains for a backend nobody has visited yet.

New setting

publicAddress - the address players are sent back to when a move needs a reconnect. Empty uses
the address each player connected with, which is right for almost every install.

Tested live: a network of two Bedrock backends and two Java ones, switching in every direction,
with the player's real IP reaching the Java server.

Endlink v0.2.0

Choose a tag to compare

@luibara2 luibara2 released this 17 Aug 02:27

Custom content — items, entities, blocks and the resource packs behind them — now survives a backend
switch. Before this release it did not, and no amount of correct pack configuration fixed it.

Bedrock reads a client's resource pack stack, item registry, entity identifier list and custom block
definitions once, at level init. A seamless switch deliberately does not re-run level init, so a
client kept whatever the first backend it joined had told it: another backend's custom items drew
arbitrary vanilla textures, and its custom entities and blocks were invisible — solid and clickable,
but not drawn.

Cross-backend registries

Every client is now given the combined item and entity registries of all backends at login, and each
backend's own network ids are translated to and from that union as packets pass through. The
translation is free per packet: it rides on the codec's existing decode step rather than rewriting
item ids packet by packet.

Custom blocks need no id translation — a modern backend hashes a block's runtime id from its state,
so the number is the same everywhere — but they do need their definitions, which are merged in the
same way. A backend that numbers blocks by palette order instead of hashing them is reported, since
that case cannot be corrected by a proxy.

Each backend's registries are learned the first time anyone visits it and cached in
cache/backend-palettes.nbt, so later logins account for backends nobody has been to this run. After
a backend's addons change, the first player to go there sees the old registry until they rejoin; that
is logged, not silent.

Controlled by crossBackendPalette, on by default.

Resource packs

  • Unpacked pack folders are loaded, not only .mcpack files. A folder holding manifest.json is
    zipped in memory at startup, reproducibly, so an unchanged folder keeps its hash across restarts
    and clients do not redownload it.
  • Backend packs are cached and served by the proxy (resourcePacks.cacheBackendPacks). The proxy
    keeps a copy of the bytes it sees a client download, and downloads a switched-to backend's packs
    itself — nobody else ever asks that backend for them. Later logins include them, which is what
    makes a switched-to backend's content render. Newest version wins; a pack placed in
    resourcePacks.dir by hand wins a tie. Encrypted packs cannot be cached and are named in the log.

Joining

security.rateLimit.packetLimit now defaults to 500 rather than RakNet's 120. A single Bedrock
login is a burst of MTU-sized fragments carrying the identity chain and skin; over loopback or a LAN
they land inside one 10ms tick and exceed 120. That blocked the address for ten seconds, stalled the
handshake into disconnect.timeout, and repeated on every retry — a player in that state could not
join at all. A blocked address now also names itself and the setting that blocked it in the log.

Resource pack chunks are 100KB rather than 1MB, so the acknowledgements a client sends back while
downloading stay inside that budget.

Upgrading

Drop in the new jar. No config changes are required — the new settings default to on, and an existing
config.properties keeps working. Two things worth doing:

  • If your config.properties sets security.rateLimit.packetLimit=120, raise it; the new default
    only applies to newly generated configs.
  • The caches start empty, so the first visit to each backend is what teaches the proxy. Visit each
    backend once and rejoin, and everything is in place from then on.

Endlink v0.1.1

Choose a tag to compare

@luibara2 luibara2 released this 12 Aug 18:08

Security fix release — updating from v0.1.0 is recommended.

Security

  • Fixed a pre-auth denial-of-service (zlib decompression bomb). The zlib decompression
    size cap was not being enforced, so an unauthenticated client could send a small compressed
    batch that inflated without bound and exhausted the proxy's memory before logging in. The
    cap (bedrock.maxDecompressedBytes, default 10 MB) is now enforced on the zlib path, matching
    the Snappy path, and is covered by a regression test. Reported through responsible disclosure.

Other changes since v0.1.0

  • ViaEndlink (the Java Edition addon) is now linked from the sibling projects, marked beta.
  • Fuller packet diagnostics: resource-pack response status and item-stack requests/responses
    are now traceable.

Endlink.jar is a drop-in replacement for v0.1.0.

Endlink v0.1.0

Choose a tag to compare

@luibara2 luibara2 released this 10 Aug 15:01

First tagged build of Endlink.

Work in progress. Tested and working for a Minecraft 1.26.40 client against a 1.26.40 backend — a player joins, plays, and switches between backends. Other version pairings are less certain, and the older-version translation chain is incomplete.

Install

  1. Drop Endlink.jar on the machine that should face players and run it:

    java -jar Endlink.jar
    

    It writes a documented config.properties and a plugins/ folder on first start, then exits ready to configure.

  2. Set your backends and backendVerification.sharedSecret in config.properties.

  3. Install EndlinkGuard on every backend and give it the same shared secret:
    https://github.com/luibara2/endlinkguard

    Backends sit in offline mode behind the proxy so it can own authentication. Without EndlinkGuard they are open to anyone who learns their address.

Requirements

  • Java 21 or newer
  • Endstone backends (recommended), each running EndlinkGuard

Apache 2.0. Built from source in this tag; gradle build reproduces it.