Skip to content

Fix and improve version normalization for old Minecraft versions #1041

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jul 27, 2025

Conversation

SpaceWalkerRS
Copy link
Contributor

@SpaceWalkerRS SpaceWalkerRS commented Apr 19, 2025

I noticed several issues with version normalization for old Minecraft versions, which I'll detail below. This PR fixes those problems, and also adds handling for Omniarchive version ids.

Classic

The current handling of classic merely strips the c prefix and doing basic normalization of the remaining string (separator digits and non-digits by ., replacing _ with .). This gives invalid semver like 0.0.11.a.

I chose to revamp this entirely for two reasons. The alphabetic chars do not denote versioning (st is for survival test versions, and a is used in all early classic versions), and early classic has an extra leading 0., and leaving that out makes normalizing those versions easier.

The updated handling gives these results:

  • c0.0.11a -> 0.11
  • c0.0.23a_01 -> 0.23.1
  • c0.24_st_03 -> 0.24.3
  • c0.25_05_st -> 0.25.5
  • c0.29 -> 0.29
  • c0.30-c -> 0.30-c
  • c0.30-s -> 0.30-s
  • c0.30-c-renew -> 0.30-c.renew

Alpha and Beta

There was special handling for trailing alphabetic chars for Alpha and Beta versions, which wasn't needed at all, since that was only used in Classic. I removed this without any change in output.

Additionally, there are some test builds and pre-releases in Beta, which Loader had no handling for. After discussion on Discord, I opted to handle those the following way.

  • Add the test build number or pre-release number as a patch version at the end.
  • For the release target of these test builds and pre-releases, use r as a patch version at the end ('r' for 'release').
  • If the release target does not have a minor version, use 0 to ensure it and its pre-releases are sorted before subsequent minor updates.

The use of an alphabetic char for the release ensures it is sorted after the test builds and pre-releases. The updated handling gives the following results:

  • b1.6-tb3 -> 1.0.0-beta.6.0.3
  • b1.6 -> 1.0.0-beta.6.0.r
  • b1.6.1 -> 1.0.0-beta.6.1
  • b1.8-pre2 -> 1.0.0-beta.8.0.2
  • b1.8 -> 1.0.0-beta.8.0.r
  • b1.8.1 -> 1.0.0-beta.8.1

1.0.0

Added a special case for parsing release candidates for 1.0.0 from the jar, where they are simply called Minecraft RC1 and Minecraft RC2. The semver follows the convention for release candidates for later versions:

  • Minecraft RC1 -> 1.0.0-rc.1
  • Minecraft RC2 -> 1.0.0-rc.2

The downside is that this does conflict with pre-releases for old versions (which also use the rc identifier) but since 1.0.0 does not have any pre-releases I do not see this as a problem.

2.0 April Fools update

There were special cases for normalizing 2point0_purple to 1.5.2-purple and similar to the red and blue versions. But those versions are known as 2.0 in the jar, and there was no special case for that, meaning Loader would simply normalize that version to 2.0.

To reconcile this I updated the special cases as follows:

  • 2.0 -> 1.5.2-2.0
  • 2point0_purple -> 1.5.2-2.0+purple
  • 2point0_red -> 1.5.2-2.0+red
  • 2point0_blue -> 1.5.2-2.0+blue

22w13oneBlockAtATime (2022 April Fools update)

There was no special handling for this version which led to inconsistent normalization. I added a special case for it.

Release Versions

I made the pattern matching for release versions a little stricter. It now only matches 1.x.y instead of x.y.z. This way the Release version pattern does not match Classic versions.

Experimental Snapshots

Pattern matching for experimental snapshots was already implemented, but the special cases for it in getRelease were missing so it was never used. I added these special cases to fix that.

Omniarchive versions

Omniarchive deviate from Mojang's manifest ids in a few ways.

  • Early april fools version ids are of the form af-yyyy where yyyy is the year in which the version was released. I added special cases for these.
  • Between 1.2 and 1.7, regular release version ids were used for pre-releases. Omniarchive marks these versions with a -pre suffix. I added handling to remove those suffixes to be consistent with early Loader behavior.
  • Since Omniarchive tracks re-uploads of versions, it has to distinguish between them. It does this by added timestamp suffixes to the ids of all uploads of a version. Handling for this was added so Loader still recognizes these versions, and the timestamp is added as extra build information (through the + separator).

@SpaceWalkerRS SpaceWalkerRS force-pushed the normalize-version-classic branch from f759e9f to a6715c7 Compare July 5, 2025 10:09
@SpaceWalkerRS SpaceWalkerRS changed the title Fix version normalization for Minecraft classic Fix and improve version normalization for old Minecraft versions Jul 5, 2025
@SpaceWalkerRS SpaceWalkerRS force-pushed the normalize-version-classic branch 5 times, most recently from 11b0ab2 to e6c1728 Compare July 5, 2025 11:17
@SpaceWalkerRS SpaceWalkerRS marked this pull request as draft July 5, 2025 11:52
@SpaceWalkerRS SpaceWalkerRS force-pushed the normalize-version-classic branch 2 times, most recently from 4fd11b9 to 1221edc Compare July 6, 2025 16:52
@SpaceWalkerRS SpaceWalkerRS marked this pull request as ready for review July 6, 2025 17:00
@SpaceWalkerRS SpaceWalkerRS force-pushed the normalize-version-classic branch from 1221edc to db32fcd Compare July 6, 2025 17:46
- proper handling of classic versions
- fix handling of multiple indev/infdev versions that occur on the same day
- fix handling of experimental snapshots
- fix 2.0 april fools version handling
- add handling for test builds and pre-releases in beta
- add support for timestamp suffixes used in omniarchive version ids
@SpaceWalkerRS SpaceWalkerRS force-pushed the normalize-version-classic branch from db32fcd to fa18e6b Compare July 6, 2025 18:00
@Player3324
Copy link
Contributor

This looks promising so far, thanks.

  • in-20100130 - > 0.31.20100130

  • in-20100218-0016 -> 0.31.201002180016

  • inf-20100327 -> 0.31.20100327

  • inf-20100616-1808 -> 0.31.201006161808

These seem out of order now, Semver is supposedly using numeric comparison and 201002180016 is obviously bigger than 20100327? Maybe you can add a few unit tests to ensure that the resulting Comparable SemanticVersion is comparing as expected for all the interesting cases. In particular each version pair should compares the same as the evolution of the underlying code base suggests.

Additionally to testing compareTo we should validate conversions of a variety of versions to make sure no code changes accidentally change them.

At this point it might be worth changing the static Pattern declarations to just Strings and compile the pattern as they are used. Combined with checking modern and frequently used patterns first this should save a bit of overhead for most users.

@SpaceWalkerRS
Copy link
Contributor Author

  • in-20100130 - > 0.31.20100130
  • in-20100218-0016 -> 0.31.201002180016
  • inf-20100327 -> 0.31.20100327
  • inf-20100616-1808 -> 0.31.201006161808

These seem out of order now, Semver is supposedly using numeric comparison and 201002180016 is obviously bigger than 20100327?

Good catch! For now I'll set the time component of 0000 if it isn't given, but I could also revert back to the old approach if you'd prefer that.

only allow 1.x.y instead of x.y.z - this helps in differentiating between classic and release versions
this is because some pre-release 1s are simply known as "Prerelease" in the jar
and use them in the semver for these versions
apparently there was no handling for this april fools version yet??
it would fail when called from McVersion.Builder.setNameAndRelease anyway
@SpaceWalkerRS
Copy link
Contributor Author

I had to revert back to the previous handling of Indev/Infdev versions, as the new approach made the numbers too large (the semver parser can only handle 32bit ints).

I added unit tests for all special cases and oddities for mc version normalization, and tests for the semver comparison as well.

Copy link
Contributor

@Player3324 Player3324 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thanks!

One last request though: It might help future maintenance a lot if the Pattern initializers and their use sites had a comment that lists an existing example version (maybe multiple if there are substantially different options) like this:

private static final Pattern RELEASE_PATTERN = Pattern.compile("\\d+\\.\\d+(\\.\\d+)?"); // 1.3 1.7.10
[...]
if ((matcher = RELEASE_PATTERN.matcher(name)).matches()) { // 1.3 1.7.10

case "22w13oneblockatatime":
case "af-2022":
// Minecraft 22w13oneblockatatime
return "1.19.1-alpha.22.13.oneblockatatime";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is definitely wrong. I think the correct answer is 1.18.3-alpha.22.13.oneblockatatime (if we're fine with nonexistent main version) or 1.19-alpha.22.13.oneblockatatime (if we don't care that oBAAT is not forked from 1.19 snapshot series).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the correct answer is 1.18.3-alpha.22.13.oneblockatatime (if we're fine with nonexistent main version)

I think you're right (see the combat snapshots forked from 1.14.4) - I'll be using this, thanks!

change from 1.19.1-alpha.22.13.oneblockatatime to
1.18.3-alpha.22.13.oneblockatatime
the version is forked from 1.18.2, not the 1.19.1 snapshot series!
@SpaceWalkerRS SpaceWalkerRS force-pushed the normalize-version-classic branch from 8db054b to 80b51d9 Compare July 10, 2025 08:07
private static final Pattern TEST_BUILD_PATTERN = Pattern.compile(".+(?:-tb| Test Build )(\\d+)?(?:-(\\d+))?"); // ... Test Build 1, ...-tb2, ...-tb3-1234
private static final Pattern PRE_RELEASE_PATTERN = Pattern.compile(".+(?:-pre| Pre-?[Rr]elease ?)(?:(\\d+)(?: ;\\))?)?(?:-(\\d+))?"); // ... Prerelease, ... Pre-release 1, ... Pre-Release 2, ...-pre3, ...-pre4-1234
private static final Pattern RELEASE_CANDIDATE_PATTERN = Pattern.compile(".+(?:-rc| RC| [Rr]elease Candidate )(\\d+)(?:-(\\d+))?"); // ... RC1, ... Release Candidate 2, ...-rc3, ...-rc4-1234
private static final Pattern SNAPSHOT_PATTERN = Pattern.compile("(?:Snapshot )?(\\d+)w0?(0|[1-9]\\d*)([a-z])(?:-(\\d+))?"); // Snapshot 16w02a, 20w13b, 22w18c-1234
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't there a snapshot that had a tilde?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes there is 13w12~ and 20w14~ (20w14 infinite) but they're handled by special cases in normalizeSpecialVersion.

@Player3324 Player3324 merged commit ad1a33b into FabricMC:master Jul 27, 2025
3 checks passed
Player3324 pushed a commit that referenced this pull request Aug 5, 2025
* fix regressions in version normalization

* fix comment

* more fixes

* more special cases for combat snapshots

* fix experimental snapshot parsing again

* support server- prefix for alpha servers

* semver handling for classic servers

* improve handling of special semver cases

* fix sorting of classic servers
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.

3 participants