Skip to content

Support Minecraft 26.1 through 26.2, and fix the Base64Coder crash they introduce - #178

Open
Aelshi-nui wants to merge 4 commits into
wargamer:masterfrom
Aelshi-nui:master
Open

Support Minecraft 26.1 through 26.2, and fix the Base64Coder crash they introduce#178
Aelshi-nui wants to merge 4 commits into
wargamer:masterfrom
Aelshi-nui:master

Conversation

@Aelshi-nui

Copy link
Copy Markdown
Contributor

Minecraft changed how it numbers releases in 2026. The drops after 1.21.11 are 26.1, 26.1.1, 26.1.2 and 26.2, with the leading "1." gone for good, so these are newer than 1.21.11 even though the numbers look smaller. This branch gets SignShop running on all four of them while still loading on 1.21 servers, and bumps the version to 5.2.3.

Two things genuinely broke on the new drops, and neither was visible at build time.

The first one crashes shops outright. Every 26.x Spigot ships SnakeYAML 2.6, and 2.6 deleted the Base64Coder class that 2.2 and earlier provided. BukkitSerialization imported that class straight from the server's copy of SnakeYAML, so the moment anything touched the legacy item paths, it died with ClassNotFoundException: Base64Coder. Since it only happens at runtime, and only when reading or converting older shop data, it looks like a data problem rather than a library one. It now encodes with java.util.Base64 instead. The output is deliberately identical to what SnakeYAML produced, right down to the 76 character lines and the trailing line break, so re-saving a shop leaves sellers.yml byte for byte the same as before, and the decoder accepts LF, CRLF and completely unwrapped input so nothing written by an older build is rejected.

The second one is quieter and worse for it. PlayerHeadIncompatibilityDetector decided whether it applied by matching on the version string, with checks like version.contains("1.21.1") and a couple of guesses at future versions. On a 26.x server none of those match, so the detector concluded it was irrelevant and switched itself off, which let the empty player head NPE back in on exactly the versions that still have the bug. Version handling now lives in versionUtil, which parses versions into numbers and exposes isAtLeast. Because 26 is greater than 1, both numbering schemes order correctly against each other without any special cases, and the detector simply asks whether the server is at least 1.21.10.

The build now targets spigot-api 26.2, and the source is verified to compile against 1.21, 1.21.11, 26.1, 26.1.1, 26.1.2 and 26.2, so nothing here depends on API that only exists in the newest drop. api-version in plugin.yml deliberately stays at 1.21, because that field is a minimum rather than a target: a server refuses a plugin that asks for something newer than itself, so leaving it alone is what lets one jar cover 1.21 through 26.2. There is a spigot.version property if you want to build against an older drop.

Dependencies moved up to releases that work on the new drops: WorldGuard 7.0.18, which itself declares api-version 26.1, WorldEdit 7.4.5, Towny 0.103.2.0, BentoBox 2.7.0, GriefPrevention 18.0.0, Lands 7.25.4, BlockLocker 1.13, Dynmap 3.8, Vault 1.7.1, bStats 3.2.1 and sqlite-jdbc 3.53.2.1. gson 2.14.0 and annotations 24.1.0 were picked to match exactly what spigot-api 26.2 hands the plugin at runtime. Towny is built against paper-api these days and its Resident and Town classes now extend Adventure audiences, so javac needs those interfaces to resolve the types; adventure-api is on the compile classpath for that reason alone and nothing calls into it, which keeps the plugin working on plain Spigot.

While updating dependencies it turned out the CMI, Essentials, Residence and BlockLocker integrations had been excluded from compilation because their jars were unavailable, which left the worth handlers and the Essentials sign conflict check disabled. They now point at real artifacts, CMI-API 9.8.6.4, EssentialsX 2.21.2, Residence 6.0.2.3 and BlockLocker 1.13, and the wiring is enabled again behind the usual plugin presence guards. LWCX is the one exception, since nothing publishes an artifact for it that Maven can resolve, so its hook stays out of the build with a comment explaining why.

The last two commits stop tracking target/ and dependency-reduced-pom.xml, which were committed build output. They do not affect behaviour, they just stop every local build from showing up as a diff.

On verification: mvn clean package succeeds, the compile matrix across the six versions above passes, the replacement Base64 encoder was checked against SnakeYAML 2.2 and produces identical output for eleven payload sizes from empty up to 64 KB, and the version comparison has twenty assertions covering the ordering across the scheme change. What has not happened yet is a run on a live 26.x server, so the runtime side rests on the API and library evidence rather than on play testing.

One thing worth knowing before reading the file list: most of the changed file count is file permission differences the fork carries, not content. The files with actual changes are the pom, .gitignore, README, versionUtil, BukkitSerialization, PlayerHeadIncompatibilityDetector, IncompatibilityDetector, SignShop, SignShopServerListener and the Folia scheduling classes.

- add FoliaDetector and SchedulerAdapter
- switch schedulers to Folia-safe Consumer<ScheduledTask> API
- fix region-based thread-safety issues
- exclude optional integrations via stub JARs
- tested on Folia 1.21.11 and Paper 1.21
Minecraft switched to year based version numbers after 1.21.11, so the
drops are 26.1 and up rather than 1.26.x. Build against spigot-api 26.2;
the source is verified to compile against 1.21, 1.21.11, 26.1, 26.1.1,
26.1.2 and 26.2. api-version stays at 1.21 because it is a minimum, so
one jar still loads on 1.21 servers.

Two things actually broke on 26.x:

- Every 26.x Spigot ships snakeyaml 2.6, which removed
  Base64Coder, so the legacy item paths died with
  ClassNotFoundException: Base64Coder. BukkitSerialization now uses
  java.util.Base64 with the same 76 character lines and trailing break,
  so saved shops stay byte for byte identical, and the decoder accepts
  LF, CRLF and unwrapped data.

- PlayerHeadIncompatibilityDetector decided whether it applied with
  version.contains("1.21.1") and friends, which match nothing on 26.x,
  so it quietly disabled itself and let the empty player head NPE back
  in. versionUtil now parses versions into numbers and offers
  isAtLeast(), which orders both schemes correctly since 26 > 1.

Dependency updates: worldguard 7.0.18, worldedit 7.4.5, towny 0.103.2.0,
bentobox 2.7.0, GriefPrevention 18.0.0, LandsAPI 7.25.4, blocklocker
1.13, DynmapCoreAPI 3.8, VaultAPI 1.7.1, bstats 3.2.1, sqlite-jdbc
3.53.2.1, gson 2.14.0 and annotations 24.1.0 (the last two match what
spigot-api 26.2 provides). Towny leaks Adventure types through Resident
and Town now, so adventure-api is on the compile classpath only.

The CMI, Essentials, Residence and BlockLocker integrations were
excluded from the build because their jars were unavailable. They now
point at real artifacts (CMI-API 9.8.6.4, EssentialsX 2.21.2, Residence
6.0.2.3, blocklocker 1.13) and the worth handler and Essentials sign
conflict wiring is enabled again. Only LWCX stays out, since nothing
publishes a resolvable artifact for it.

Version bumped to 5.2.3.
target/ was committed, so every build showed up as a diff and the
repository carried ~389k lines of generated files. It is ignored now,
along with FOLIA_COMPATIBILITY.md, which stays a local note. The README
covered the same Folia information, so the link to it is gone and the
short version lives in the README instead.
The shade plugin rewrites it on every package, so it only ever showed up
as noise in diffs. Same reasoning as target/.
@weaves7

weaves7 commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

Thanks for this — the version-detection rework (versionUtil.isAtLeast()) and the Base64Coder replacement are solid, both root-caused and correctly handled for backward compat. I checked all the bumped dependencies (Towny,
BentoBox, GriefPrevention, LandsAPI, Residence, EssentialsX, CMI-API, Dynmap, BlockLocker, adventure-api) and they all resolve and compile cleanly.

One thing I need clarified before anything else:

  1. Did you actually verify a clean build across the full version spread you're claiming? The README says this builds against spigot-api 26.2 and Java 21, and compiles for everything from 1.13.2 up through 26.2. But
    worldguard-bukkit:7.0.18 and worldedit-bukkit:7.4.5 don't compile for me against maven.compiler.source/target=21 as currently set — something in that dependency's own toolchain requirements is out ahead of the Java version
    this project targets. Did you build and test this end-to-end on a clean environment across that whole matrix, or mainly on whatever JDK/setup you had locally? If it's the latter, I'd like to see it actually verified
    across the stated range before merging — happy to help narrow down which combination breaks if useful.

Other things I'd like addressed before merging:

  1. File mode churn: ~326 unrelated files flipped 100644→100755 with zero content change (looks like a WSL/Docker-on-Windows mount artifact). Run git config core.fileMode false, normalize back to 644, force-push.
  2. SchedulerAdapter.initializeFoliaReflection(): unconditional logger.info() spam with emoji, fires once at startup on Folia servers specifically (properly gated behind FoliaDetector.isFolia(), non-Folia users unaffected).
    Still worth gating behind Debugging:/debugMessage() for consistency.
  3. true on CMI/EssentialsX/Residence/Dynmap/LWCX/BlockLocker in pom.xml: no-op for a shaded plugin jar (already provided scope), inconsistently applied. Drop it.
  4. Storage.java/itemUtil.clickedSign(): swallow validation failures under a guessed "might be Folia" comment instead of a confirmed cause. If you hit a real stack trace, note it; otherwise this risks masking a genuinely
    broken shop.
  5. This silently drops LWC support. LWCHook.java is excluded from compilation, but plugin.yml still lists LWC in softdepend and HookManager swallows the resulting ClassNotFoundException at runtime — no error, the protection
    check just stops working. LWCX 2.2.5 resolves fine here already; if you can't resolve it locally, install it into your local repo manually (mvn install:install-file -Dfile=LWCX-2.2.5.jar -DgroupId=com.griefcraft.lwc
    -DartifactId=LWCX -Dversion=2.2.5 -Dpackaging=jar) rather than excluding the hook.

Heads up — I've already got my own overlapping fixes queued (Base64Coder in particular, and a few of the others touch the same files), so I'll be PRing separately and merging whichever lands cleaner/first rather than blocking
on full overlap resolution here.

@Aelshi-nui

Copy link
Copy Markdown
Contributor Author

I went through everything and checked each one, and you're right on all of them.

The main thing I missed was the build. My matrix runs were on JDK 25, so I didn't catch that WorldGuard 7.0.18 and WorldEdit 7.4.3+ require Java 25. Pinning WorldGuard 7.0.17 and WorldEdit 7.4.2 works cleanly on both JDK 21 and 25.

The README's 1.13.2 support was also wrong since the plugin uses "api-version: 1.21".

Everything else checks out too, including the file modes, Folia logging, optional flags, and sign validation.

One note: LWCX 2.2.5 doesn't seem to exist in any public repo I checked, so it may only be resolving from your local ".m2". That should either use a real repo or fail loudly instead of silently throwing "ClassNotFoundException".

I won't have time to work through these this week, so go ahead and merge yours. It'll be cleaner. I'm happy to close this one or rebase anything left afterward.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants