Compare commits

..
8 Commits
28 changed files with 750 additions and 291 deletions

No files matched your search

-21
View File
@@ -1,21 +0,0 @@
name: Sync to Gitee
on:
push
jobs:
run:
if: ${{ github.repository_owner == 'HMCL-dev' }}
runs-on: ubuntu-latest
steps:
- name: Mirror GitHub to Gitee
uses: Yikun/hub-mirror-action@v1.4
with:
src: github/HMCL-dev
dst: gitee/huanghongxun
static_list: 'HMCL'
force_update: true
debug: true
dst_key: ${{ secrets.GITEE_SYNC_BOT_PRIVATE_KEY }}
dst_token: ${{ secrets.GITEE_SYNC_BOT_TOKEN }}
cache_path: /github/workspace/hub-mirror-cache
+32
View File
@@ -0,0 +1,32 @@
name: Mirror Repository
on:
workflow_dispatch:
push:
concurrency:
group: mirror-repository
cancel-in-progress: true
jobs:
mirror:
strategy:
matrix:
target:
- name: Gitee
repo: gitee.com/huanghongxun/HMCL
user: 'hmcl-sync'
token: 'GITEE_SYNC_TOKEN'
- name: CNB
repo: cnb.cool/HMCL-dev/HMCL
user: 'cnb'
token: 'CNB_SYNC_TOKEN'
name: Mirror to ${{ matrix.target.name }}
if: ${{ github.repository == 'HMCL-dev/HMCL' }}
runs-on: ubuntu-latest
steps:
- name: Mirror GitHub to ${{ matrix.target.name }}
run: |
git clone --mirror "https://github.com/$GITHUB_REPOSITORY.git" -- repo
cd repo
git push -f --prune "https://${{ matrix.target.user }}:${{ secrets[matrix.target.token] }}@${{ matrix.target.repo }}.git" "refs/heads/*:refs/heads/*" "refs/tags/*:refs/tags/*"
@@ -338,7 +338,7 @@ public final class HMCLGameRepository extends DefaultGameRepository {
GameVersionNumber versionNumber = GameVersionNumber.asGameVersion(gameVersion);
if (versionNumber.isAprilFools()) {
return VersionIconType.APRIL_FOOLS.getIcon();
} else if (versionNumber instanceof GameVersionNumber.Snapshot) {
} else if (versionNumber instanceof GameVersionNumber.LegacySnapshot) {
return VersionIconType.COMMAND.getIcon();
} else if (versionNumber instanceof GameVersionNumber.Old) {
return VersionIconType.CRAFT_TABLE.getIcon();
@@ -233,7 +233,7 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
}
setGraphic(pane);
twoLineListItem.setTitle(I18n.getDisplaySelfVersion(remoteVersion));
twoLineListItem.setTitle(I18n.getDisplayVersion(remoteVersion));
if (remoteVersion.getReleaseDate() != null) {
twoLineListItem.setSubtitle(I18n.formatDateTime(remoteVersion.getReleaseDate()));
} else {
@@ -40,6 +40,7 @@ import org.jackhuang.hmcl.ui.construct.RipplerContainer;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jetbrains.annotations.NotNull;
import java.nio.file.*;
@@ -246,7 +247,7 @@ public final class WorldBackupsPage extends ListPageBase<WorldBackupsPage.Backup
item.setSubtitle(formatDateTime(skinnable.getBackupTime()) + (skinnable.count == 0 ? "" : " (" + skinnable.count + ")"));
if (world.getGameVersion() != null)
item.addTag(world.getGameVersion());
item.addTag(I18n.getDisplayVersion(world.getGameVersion()));
}
{
@@ -23,6 +23,7 @@ import javafx.scene.control.Skin;
import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.wizard.WizardSinglePage;
import org.jackhuang.hmcl.util.i18n.I18n;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -41,7 +42,8 @@ public class WorldExportPage extends WizardSinglePage {
this.world = world;
path.set(export.toString());
gameVersion.set(world.getGameVersion());
if (world.getGameVersion() != null)
gameVersion.set(I18n.getDisplayVersion(world.getGameVersion()));
worldName.set(world.getWorldName());
}
@@ -123,7 +123,8 @@ public final class WorldInfoPage extends SpinnerPane {
Label gameVersionLabel = new Label();
FXUtils.copyOnDoubleClick(gameVersionLabel);
gameVersionLabel.setText(world.getGameVersion());
if (world.getGameVersion() != null)
gameVersionLabel.setText(world.getGameVersion().toNormalizedString());
BorderPane.setAlignment(gameVersionLabel, Pos.CENTER_RIGHT);
gameVersionPane.setRight(gameVersionLabel);
}
@@ -33,7 +33,7 @@ import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.util.ChunkBaseApp;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import org.jackhuang.hmcl.util.i18n.I18n;
import java.time.Instant;
@@ -73,10 +73,10 @@ public final class WorldListItemSkin extends SkinBase<WorldListItem> {
item.setMouseTransparent(true);
if (world.getWorldName() != null)
item.setTitle(parseColorEscapes(world.getWorldName()));
item.setSubtitle(i18n("world.datetime", formatDateTime(Instant.ofEpochMilli(world.getLastPlayed())), world.getGameVersion() == null ? i18n("message.unknown") : world.getGameVersion()));
item.setSubtitle(i18n("world.datetime", formatDateTime(Instant.ofEpochMilli(world.getLastPlayed()))));
if (world.getGameVersion() != null)
item.addTag(world.getGameVersion());
item.addTag(I18n.getDisplayVersion(world.getGameVersion()));
if (world.isLocked())
item.addTag(i18n("world.locked"));
}
@@ -127,7 +127,7 @@ public final class WorldListItemSkin extends SkinBase<WorldListItem> {
new IconedMenuItem(SVG.FORT, i18n("world.chunkbase.nether_fortress"), () -> ChunkBaseApp.openNetherFortressFinder(world), popup)
);
if (GameVersionNumber.compare(world.getGameVersion(), "1.13") >= 0) {
if (world.getGameVersion() != null && world.getGameVersion().compareTo("1.13") >= 0) {
popupMenu.getContent().add(new IconedMenuItem(SVG.LOCATION_CITY, i18n("world.chunkbase.end_city"),
() -> ChunkBaseApp.openEndCityFinder(world), popup));
}
@@ -29,6 +29,7 @@ import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.*;
import org.jackhuang.hmcl.ui.animation.TransitionPane;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
@@ -50,7 +51,7 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
private List<World> worlds;
private Profile profile;
private String id;
private String gameVersion;
private GameVersionNumber gameVersion;
public WorldListPage() {
FXUtils.applyDragListener(this, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
@@ -88,7 +89,7 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
return;
setLoading(true);
Task.runAsync(() -> gameVersion = profile.getRepository().getGameVersion(id).orElse(null))
Task.runAsync(() -> gameVersion = profile.getRepository().getGameVersion(id).map(GameVersionNumber::asGameVersion).orElse(null))
.thenApplyAsync(unused -> {
try (Stream<World> stream = World.getWorlds(savesDir)) {
return stream.parallel().collect(Collectors.toList());
@@ -33,7 +33,6 @@ import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.ChunkBaseApp;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.io.IOException;
import java.nio.channels.FileChannel;
@@ -84,7 +83,7 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
.addNavigationDrawerTab(header, worldBackupsTab, i18n("world.backup"), SVG.ARCHIVE, SVG.ARCHIVE_FILL);
if (world.getGameVersion() != null && // old game will not write game version to level.dat
GameVersionNumber.asGameVersion(world.getGameVersion()).isAtLeast("1.13", "17w43a")) {
world.getGameVersion().isAtLeast("1.13", "17w43a")) {
header.getTabs().add(datapackTab);
sideBar.addNavigationDrawerTab(header, datapackTab, i18n("world.datapack"), SVG.EXTENSION, SVG.EXTENSION_FILL);
}
@@ -102,7 +101,7 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
new IconedMenuItem(SVG.FORT, i18n("world.chunkbase.nether_fortress"), () -> ChunkBaseApp.openNetherFortressFinder(world), popup)
);
if (GameVersionNumber.compare(world.getGameVersion(), "1.13") >= 0) {
if (world.getGameVersion() != null && world.getGameVersion().compareTo("1.13") >= 0) {
popupMenu.getContent().add(
new IconedMenuItem(SVG.LOCATION_CITY, i18n("world.chunkbase.end_city"), () -> ChunkBaseApp.openEndCityFinder(world), popup));
}
@@ -112,7 +112,7 @@ public final class UpdateChecker {
try {
result = checkUpdate(channel, preview);
LOG.info("Latest version (" + channel + ", preview=" + preview + ") is " + result);
} catch (IOException e) {
} catch (Throwable e) {
LOG.warning("Failed to check for update", e);
}
@@ -49,7 +49,7 @@ public final class ChunkBaseApp {
public static boolean isSupported(@NotNull World world) {
return world.getSeed() != null && world.getGameVersion() != null &&
GameVersionNumber.asGameVersion(world.getGameVersion()).compareTo(MIN_GAME_VERSION) >= 0;
world.getGameVersion().compareTo(MIN_GAME_VERSION) >= 0;
}
public static ChunkBaseApp newBuilder(String app, long seed) {
@@ -60,7 +60,7 @@ public final class ChunkBaseApp {
assert isSupported(world);
newBuilder("seed-map", Objects.requireNonNull(world.getSeed()))
.addPlatform(GameVersionNumber.asGameVersion(world.getGameVersion()), world.isLargeBiomes(), SEED_MAP_GAME_VERSIONS)
.addPlatform(world.getGameVersion(), world.isLargeBiomes(), SEED_MAP_GAME_VERSIONS)
.open();
}
@@ -68,7 +68,7 @@ public final class ChunkBaseApp {
assert isSupported(world);
newBuilder("stronghold-finder", Objects.requireNonNull(world.getSeed()))
.addPlatform(GameVersionNumber.asGameVersion(world.getGameVersion()), world.isLargeBiomes(), STRONGHOLD_FINDER_GAME_VERSIONS)
.addPlatform(world.getGameVersion(), world.isLargeBiomes(), STRONGHOLD_FINDER_GAME_VERSIONS)
.open();
}
@@ -76,7 +76,7 @@ public final class ChunkBaseApp {
assert isSupported(world);
newBuilder("nether-fortress-finder", Objects.requireNonNull(world.getSeed()))
.addPlatform(GameVersionNumber.asGameVersion(world.getGameVersion()), false, NETHER_FORTRESS_GAME_VERSIONS)
.addPlatform(world.getGameVersion(), false, NETHER_FORTRESS_GAME_VERSIONS)
.open();
}
@@ -84,7 +84,7 @@ public final class ChunkBaseApp {
assert isSupported(world);
newBuilder("endcity-finder", Objects.requireNonNull(world.getSeed()))
.addPlatform(GameVersionNumber.asGameVersion(world.getGameVersion()), false, END_CITY_GAME_VERSIONS)
.addPlatform(world.getGameVersion(), false, END_CITY_GAME_VERSIONS)
.open();
}
@@ -249,7 +249,7 @@ public final class NativePatcher {
if (minVersion != null) {
if (gameVersion.compareTo(minVersion) >= 0) {
if (maxVersion != null && gameVersion.compareTo(maxVersion) >= 0)
if (maxVersion != null && gameVersion.compareTo(maxVersion) > 0)
return SupportStatus.UNSUPPORTED;
String[] defaultGameVersions = GameVersionNumber.getDefaultGameVersions();
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.util.i18n;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.download.game.GameRemoteVersion;
import org.jackhuang.hmcl.util.i18n.translator.Translator;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
@@ -75,7 +76,11 @@ public final class I18n {
return getTranslator().formatSpeed(bytes);
}
public static String getDisplaySelfVersion(RemoteVersion version) {
public static String getDisplayVersion(RemoteVersion version) {
return getTranslator().getDisplayVersion(version);
}
public static String getDisplayVersion(GameVersionNumber version) {
return getTranslator().getDisplayVersion(version);
}
@@ -72,7 +72,7 @@ public final class MinecraftWiki {
else if (wikiVersion.startsWith("1.0.0-rc2"))
wikiVersion = "1.0.0-rc2";
}
} else if (gameVersion instanceof GameVersionNumber.Snapshot) {
} else if (gameVersion instanceof GameVersionNumber.LegacySnapshot) {
return locale.i18n("wiki.version.game.snapshot", wikiVersion) + variantSuffix;
} else {
if (wikiVersion.length() >= 6 && wikiVersion.charAt(2) == 'w') {
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.util.i18n.translator;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.util.i18n.SupportedLocale;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
@@ -47,6 +48,10 @@ public class Translator {
return remoteVersion.getSelfVersion();
}
public String getDisplayVersion(GameVersionNumber versionNumber) {
return versionNumber.toNormalizedString();
}
/// @see [#formatDateTime(TemporalAccessor)]
protected DateTimeFormatter dateTimeFormatter;
@@ -92,22 +92,38 @@ public class Translator_lzh extends Translator {
appendDigitByDigit(builder, String.valueOf(release.getPatch()));
}
//noinspection StatementWithEmptyBody
if (release.getEaType() == GameVersionNumber.Release.TYPE_GA) {
// do nothing
} else if (release.getEaType() == GameVersionNumber.Release.TYPE_PRE) {
builder.append("之預");
appendDigitByDigit(builder, release.getEaVersion().toString());
} else if (release.getEaType() == GameVersionNumber.Release.TYPE_RC) {
builder.append("之候");
appendDigitByDigit(builder, release.getEaVersion().toString());
} else {
// Unsupported
return gameVersion.toString();
switch (release.getEaType()) {
case GA -> {
// do nothing
}
case PRE_RELEASE -> {
builder.append("之預");
appendDigitByDigit(builder, release.getEaVersion().toString());
}
case RELEASE_CANDIDATE -> {
builder.append("之候");
appendDigitByDigit(builder, release.getEaVersion().toString());
}
default -> {
// Unsupported
return gameVersion.toString();
}
}
switch (release.getAdditional()) {
case NONE -> {
}
case UNOBFUSCATED -> {
builder.append("涇渭");
}
default -> {
// Unsupported
return gameVersion.toString();
}
}
return builder.toString();
} else if (gameVersion instanceof GameVersionNumber.Snapshot snapshot) {
} else if (gameVersion instanceof GameVersionNumber.LegacySnapshot snapshot) {
StringBuilder builder = new StringBuilder();
appendDigitByDigit(builder, String.valueOf(snapshot.getYear()));
@@ -120,6 +136,9 @@ public class Translator_lzh extends Translator {
else
builder.append(suffix);
if (snapshot.isUnobfuscated())
builder.append("涇渭");
return builder.toString();
} else if (gameVersion instanceof GameVersionNumber.Special) {
String version = gameVersion.toString();
@@ -130,6 +149,7 @@ public class Translator_lzh extends Translator {
case "2.0_purple" -> "二點〇紫";
case "1.rv-pre1" -> "一點真視之預一";
case "3d shareware v1.34" -> "躍然享件一點三四";
case "13w12~" -> "一三週一二閏";
case "20w14infinite", "20w14~", "20w14∞" -> "二〇週一四宇";
case "22w13oneblockatatime" -> "二二週一三典";
case "23w13a_or_b" -> "二三週一三暨";
@@ -179,6 +199,11 @@ public class Translator_lzh extends Translator {
return translateGenericVersion(remoteVersion.getSelfVersion());
}
@Override
public String getDisplayVersion(GameVersionNumber versionNumber) {
return translateGameVersion(versionNumber);
}
@Override
public String formatDateTime(TemporalAccessor time) {
LocalDateTime localDateTime;
@@ -37,6 +37,8 @@ case "$(uname -m)" in
_HMCL_ARCH="riscv64";;
loongarch64)
_HMCL_ARCH="loongarch64";;
mips64)
_HMCL_ARCH="mips64el";;
*)
_HMCL_ARCH="unknown";;
esac
@@ -24,6 +24,7 @@ import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import javafx.scene.image.Image;
import org.jackhuang.hmcl.util.io.*;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
@@ -48,7 +49,7 @@ public final class World {
private final Path file;
private String fileName;
private String worldName;
private String gameVersion;
private GameVersionNumber gameVersion;
private long lastPlayed;
private Image icon;
private Long seed;
@@ -108,7 +109,7 @@ public final class World {
return lastPlayed;
}
public String getGameVersion() {
public @Nullable GameVersionNumber getGameVersion() {
return gameVersion;
}
@@ -188,7 +189,7 @@ public final class World {
CompoundTag version = data.get("Version");
if (version.get("Name") instanceof StringTag)
gameVersion = version.<StringTag>get("Name").getValue();
gameVersion = GameVersionNumber.asGameVersion(version.<StringTag>get("Name").getValue());
}
Tag worldGenSettings = data.get("WorldGenSettings");
@@ -187,7 +187,9 @@ public abstract class FetchTask<T> extends Task<T> {
ArrayList<IOException> exceptions = null;
for (int retryTime = 0; retryTime < retry; retryTime++) {
// If loading the cache fails, the cache should not be loaded again.
boolean useCachedResult = true;
for (int retryTime = 0, retryLimit = retry; retryTime < retryLimit; retryTime++) {
if (isCancelled()) {
throw new InterruptedException();
}
@@ -204,12 +206,14 @@ public abstract class FetchTask<T> extends Task<T> {
LinkedHashMap<String, String> headers = new LinkedHashMap<>();
headers.put("accept-encoding", "gzip");
if (checkETag)
if (useCachedResult && checkETag)
headers.putAll(repository.injectConnection(uri));
do {
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(currentURI);
requestBuilder.timeout(Duration.ofMillis(NetworkUtils.TIME_OUT));
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(currentURI)
.timeout(Duration.ofMillis(NetworkUtils.TIME_OUT))
.header("User-Agent", Holder.USER_AGENT);
headers.forEach(requestBuilder::header);
response = Holder.HTTP_CLIENT.send(requestBuilder.build(), BODY_HANDLER);
@@ -248,7 +252,7 @@ public abstract class FetchTask<T> extends Task<T> {
} while (true);
int responseCode = response.statusCode();
if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
if (useCachedResult && responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
// Handle cache
try {
Path cache = repository.getCachedRemoteFile(currentURI, false);
@@ -260,9 +264,10 @@ public abstract class FetchTask<T> extends Task<T> {
} catch (IOException e) {
LOG.warning("Unable to use cached file, redownload " + NetworkUtils.dropQuery(uri), e);
repository.removeRemoteEntry(currentURI);
useCachedResult = false;
// Now we must reconnect the server since 304 may result in empty content,
// if we want to redownload the file, we must reconnect the server without etag settings.
retryTime--;
retryLimit++;
continue;
}
} else if (responseCode / 100 == 4) {
@@ -503,6 +508,7 @@ public abstract class FetchTask<T> extends Task<T> {
/// Ensure that [#HTTP_CLIENT] is initialized after ProxyManager has been initialized.
private static final class Holder {
private static final HttpClient HTTP_CLIENT;
private static final String USER_AGENT = System.getProperty("http.agent", "HMCL");
static {
boolean useHttp2 = !"false".equalsIgnoreCase(System.getProperty("hmcl.http2"));
@@ -193,7 +193,7 @@ public class FileDownloadTask extends FetchTask<Void> {
if (integrityCheck != null) {
algorithm = integrityCheck.getAlgorithm();
checksum = integrityCheck.getChecksum();
} else if (bmclapiHash != null) {
} else if (bmclapiHash != null && DigestUtils.isSha1Digest(bmclapiHash)) {
algorithm = "SHA-1";
checksum = bmclapiHash;
} else {
@@ -113,16 +113,19 @@ public final class ManagedProcess {
*/
public List<String> getLines(Predicate<String> lineFilter) {
lock.lock();
try {
if (lineFilter == null)
return List.copyOf(lines);
if (lineFilter == null)
return List.copyOf(lines);
ArrayList<String> res = new ArrayList<>();
for (String line : this.lines) {
if (lineFilter.test(line))
res.add(line);
ArrayList<String> res = new ArrayList<>();
for (String line : this.lines) {
if (lineFilter.test(line))
res.add(line);
}
return Collections.unmodifiableList(res);
} finally {
lock.unlock();
}
return Collections.unmodifiableList(res);
}
public void addLine(String line) {
@@ -17,7 +17,7 @@
*/
package org.jackhuang.hmcl.util.versioning;
import org.intellij.lang.annotations.MagicConstant;
import org.jackhuang.hmcl.util.ToStringBuilder;
import org.jetbrains.annotations.NotNull;
import java.io.BufferedReader;
@@ -28,6 +28,8 @@ import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
/**
* @author Glavo
*/
@@ -38,6 +40,10 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
}
public static GameVersionNumber asGameVersion(String version) {
GameVersionNumber versionNumber = Versions.SPECIALS.get(version);
if (versionNumber != null)
return versionNumber;
try {
if (!version.isEmpty()) {
char ch = version.charAt(0);
@@ -53,20 +59,15 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
if (version.equals("0.0"))
return Release.ZERO;
if (version.startsWith("1."))
return Release.parse(version);
if (version.length() >= 6 && version.charAt(2) == 'w')
return LegacySnapshot.parse(version);
if (version.length() == 6 && version.charAt(2) == 'w')
return Snapshot.parse(version);
return Release.parse(version);
}
} catch (IllegalArgumentException ignore) {
} catch (Throwable ignore) {
}
Special special = Versions.SPECIALS.get(version);
if (special == null) {
special = new Special(version);
}
return special;
return new Special(version, version);
}
public static GameVersionNumber asGameVersion(Optional<String> version) {
@@ -94,17 +95,22 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
}
final String value;
final String normalized;
GameVersionNumber(String value) {
GameVersionNumber(String value, String normalized) {
this.value = value;
this.normalized = normalized;
}
public boolean isAprilFools() {
if (this instanceof Special && !value.endsWith("_unobfuscated"))
return true;
if (this instanceof Special) {
String normalizedVersion = this.toNormalizedString();
return !normalizedVersion.startsWith("1.") && !normalizedVersion.equals("13w12~")
|| normalizedVersion.equals("1.RV-Pre1");
}
if (this instanceof Snapshot snapshot) {
return snapshot.intValue == Snapshot.toInt(15, 14, 'a');
if (this instanceof LegacySnapshot snapshot) {
return snapshot.intValue == LegacySnapshot.toInt(15, 14, 'a', false);
}
return false;
@@ -144,10 +150,10 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
///
/// ```java
/// GameVersionNumber.asVersion("...").isAtLeast("1.13", "17w43a");
/// ```
///```
///
/// @param strictReleaseVersion When `strictReleaseVersion` is `false`, `releaseVersion` is considered less than
/// its corresponding pre/rc versions.
/// its corresponding pre/rc versions.
public boolean isAtLeast(@NotNull String releaseVersion, @NotNull String snapshotVersion, boolean strictReleaseVersion) {
if (this instanceof Release self) {
Release other;
@@ -159,17 +165,35 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
return self.compareToRelease(other) >= 0;
} else {
return this.compareTo(Snapshot.parse(snapshotVersion)) >= 0;
return this.compareTo(LegacySnapshot.parse(snapshotVersion)) >= 0;
}
}
public String toNormalizedString() {
return normalized;
}
@Override
public String toString() {
return value;
}
protected ToStringBuilder buildDebugString() {
return new ToStringBuilder(this)
.append("value", value)
.append("normalized", normalized)
.append("type", getType());
}
public final String toDebugString() {
return buildDebugString().toString();
}
public static final class Old extends GameVersionNumber {
static Old parse(String value) {
if (value.isEmpty())
throw new IllegalArgumentException("Empty old version number");
Type type;
int prefixLength = 1;
switch (value.charAt(0)) {
@@ -215,7 +239,7 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
final VersionNumber versionNumber;
private Old(String value, Type type, VersionNumber versionNumber) {
super(value);
super(value, value);
this.type = type;
this.versionNumber = versionNumber;
}
@@ -231,83 +255,141 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
return o instanceof Old other && type == other.type && this.versionNumber.compareTo(other.versionNumber) == 0;
public int hashCode() {
return Objects.hash(type, versionNumber);
}
@Override
public int hashCode() {
return Objects.hash(type, versionNumber.hashCode());
public boolean equals(Object o) {
return o instanceof Old that
&& this.type == that.type
&& this.versionNumber.equals(that.versionNumber);
}
}
public static final class Release extends GameVersionNumber {
private static final int MINIMUM_YEAR_MAJOR_VERSION = 25;
private static final Pattern PATTERN = Pattern.compile("1\\.(?<minor>[0-9]+)(\\.(?<patch>[0-9]+))?((?<eaType>(-[a-zA-Z]+| Pre-Release ))(?<eaVersion>.+))?");
public enum ReleaseType {
UNKNOWN(""),
SNAPSHOT("-snapshot-"),
PRE_RELEASE("-pre"),
RELEASE_CANDIDATE("-rc"),
GA("");
private final String infix;
public static final int TYPE_GA = Integer.MAX_VALUE;
ReleaseType(String infix) {
this.infix = infix;
}
}
public static final int TYPE_UNKNOWN = 0;
public static final int TYPE_EXP = 1;
public static final int TYPE_PRE = 2;
public static final int TYPE_RC = 3;
public enum Additional {
NONE(""), UNOBFUSCATED("_unobfuscated");
private final String suffix;
static final Release ZERO = new Release("0.0", 0, 0, 0, TYPE_GA, VersionNumber.ZERO);
Additional(String suffix) {
this.suffix = suffix;
}
}
static final Release ZERO = new Release(
"0.0", "0.0",
0, 0, 0,
ReleaseType.UNKNOWN, VersionNumber.ZERO, Additional.NONE
);
private static final Pattern VERSION_PATTERN = Pattern.compile("(?<prefix>(?<major>1|[1-9]\\d+)\\.(?<minor>\\d+)(\\.(?<patch>[0-9]+))?)(?<suffix>.*)");
static Release parse(String value) {
Matcher matcher = PATTERN.matcher(value);
Matcher matcher = VERSION_PATTERN.matcher(value);
if (!matcher.matches()) {
throw new IllegalArgumentException(value);
}
int major = Integer.parseInt(matcher.group("major"));
if (major != 1 && major < MINIMUM_YEAR_MAJOR_VERSION)
throw new IllegalArgumentException(value);
int minor = Integer.parseInt(matcher.group("minor"));
String patchString = matcher.group("patch");
int patch = patchString != null ? Integer.parseInt(patchString) : 0;
String eaTypeString = matcher.group("eaType");
int eaType;
if (eaTypeString == null) {
eaType = TYPE_GA;
} else if ("-pre".equals(eaTypeString) || " Pre-Release ".equals(eaTypeString)) {
eaType = TYPE_PRE;
} else if ("-rc".equals(eaTypeString)) {
eaType = TYPE_RC;
} else if ("-exp".equals(eaTypeString)) {
eaType = TYPE_EXP;
String suffix = matcher.group("suffix");
ReleaseType releaseType;
VersionNumber eaVersion;
Additional additional = Additional.NONE;
boolean needNormalize = false;
if (suffix.endsWith("_unobfuscated")) {
suffix = suffix.substring(0, suffix.length() - "_unobfuscated".length());
additional = Additional.UNOBFUSCATED;
} else if (suffix.endsWith(" Unobfuscated")) {
needNormalize = true;
suffix = suffix.substring(0, suffix.length() - " Unobfuscated".length());
additional = Additional.UNOBFUSCATED;
}
if (suffix.isEmpty()) {
releaseType = ReleaseType.GA;
eaVersion = VersionNumber.ZERO;
} else if (suffix.startsWith("-snapshot-")) {
releaseType = ReleaseType.SNAPSHOT;
eaVersion = VersionNumber.asVersion(suffix.substring("-snapshot-".length()));
} else if (suffix.startsWith(" Snapshot ")) {
needNormalize = true;
releaseType = ReleaseType.SNAPSHOT;
eaVersion = VersionNumber.asVersion(suffix.substring(" Snapshot ".length()));
} else if (suffix.startsWith("-pre")) {
releaseType = ReleaseType.PRE_RELEASE;
eaVersion = VersionNumber.asVersion(suffix.substring("-pre".length()));
} else if (suffix.startsWith(" Pre-Release ")) {
needNormalize = true;
releaseType = ReleaseType.PRE_RELEASE;
eaVersion = VersionNumber.asVersion(suffix.substring(" Pre-Release ".length()));
} else if (suffix.startsWith("-rc")) {
releaseType = ReleaseType.RELEASE_CANDIDATE;
eaVersion = VersionNumber.asVersion(suffix.substring("-rc".length()));
} else if (suffix.startsWith(" Release Candidate ")) {
needNormalize = true;
releaseType = ReleaseType.RELEASE_CANDIDATE;
eaVersion = VersionNumber.asVersion(suffix.substring(" Release Candidate ".length()));
} else {
eaType = TYPE_UNKNOWN;
throw new IllegalArgumentException(value);
}
String eaVersionString = matcher.group("eaVersion");
VersionNumber eaVersion = eaVersionString != null ? VersionNumber.asVersion(eaVersionString) : VersionNumber.ZERO;
return new Release(value, 1, minor, patch, eaType, eaVersion);
}
private static int getNumberLength(String value, int offset) {
int current = offset;
while (current < value.length()) {
char ch = value.charAt(current);
if (ch < '0' || ch > '9')
break;
current++;
String normalized;
if (needNormalize) {
StringBuilder builder = new StringBuilder(value.length());
builder.append(matcher.group("prefix"));
if (releaseType != ReleaseType.GA) {
builder.append(releaseType.infix);
builder.append(eaVersion);
}
builder.append(additional.suffix);
normalized = builder.toString();
} else {
normalized = value;
}
return current - offset;
return new Release(value, normalized, major, minor, patch, releaseType, eaVersion, additional);
}
/// Quickly parses a simple format (`1\.[0-9]+(\.[0-9]+)?`) release version.
/// The returned [#eaType] will be set to [#TYPE_UNKNOWN], meaning it will be less than all pre/rc and official versions of this version.
/// Quickly parses a simple format (`[1-9][0-9]+\.[0-9]+(\.[0-9]+)?`) release version.
/// The returned [#eaType] will be set to [ReleaseType#UNKNOWN], meaning it will be less than all pre/rc and official versions of this version.
///
/// @see GameVersionNumber#isAtLeast(String, String)
static Release parseSimple(String value) {
if (!value.startsWith("1."))
int majorLength = getNumberLength(value, 0);
if (majorLength == 0 || value.length() < majorLength + 2 || value.charAt(majorLength) != '.')
throw new IllegalArgumentException(value);
final int minorOffset = 2;
int major = Integer.parseInt(value.substring(0, majorLength));
if (major != 1 && major < MINIMUM_YEAR_MAJOR_VERSION)
throw new IllegalArgumentException(value);
final int minorOffset = majorLength + 1;
int minorLength = getNumberLength(value, minorOffset);
if (minorLength == 0)
@@ -326,27 +408,41 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
patch = Integer.parseInt(value.substring(patchOffset));
}
return new Release(value, 1, minor, patch, TYPE_UNKNOWN, VersionNumber.ZERO);
return new Release(value, value, major, minor, patch, ReleaseType.UNKNOWN, VersionNumber.ZERO, Additional.NONE);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(value);
}
}
private static int getNumberLength(String value, int offset) {
int current = offset;
while (current < value.length()) {
char ch = value.charAt(current);
if (ch < '0' || ch > '9')
break;
current++;
}
return current - offset;
}
private final int major;
private final int minor;
private final int patch;
@MagicConstant(intValues = {TYPE_GA, TYPE_UNKNOWN, TYPE_EXP, TYPE_PRE, TYPE_RC})
private final int eaType;
private final ReleaseType eaType;
private final VersionNumber eaVersion;
private final Additional additional;
Release(String value, int major, int minor, int patch, int eaType, VersionNumber eaVersion) {
super(value);
Release(String value, String normalized, int major, int minor, int patch, ReleaseType eaType, VersionNumber eaVersion, Additional additional) {
super(value, normalized);
this.major = major;
this.minor = minor;
this.patch = patch;
this.eaType = eaType;
this.eaVersion = eaVersion;
this.additional = additional;
}
@Override
@@ -367,35 +463,45 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
if (c != 0)
return c;
c = Integer.compare(this.eaType, other.eaType);
c = this.eaType.compareTo(other.eaType);
if (c != 0)
return c;
return this.eaVersion.compareTo(other.eaVersion);
c = this.eaVersion.compareTo(other.eaVersion);
if (c != 0)
return c;
return this.additional.compareTo(other.additional);
}
int compareToSnapshot(Snapshot other) {
int idx = Arrays.binarySearch(Versions.SNAPSHOT_INTS, other.intValue);
if (idx >= 0)
return this.compareToRelease(Versions.SNAPSHOT_PREV[idx]) <= 0 ? -1 : 1;
idx = -(idx + 1);
if (idx == Versions.SNAPSHOT_INTS.length)
int compareToSnapshot(LegacySnapshot other) {
if (major == 0) {
return -1;
} else if (major == 1) {
int idx = Arrays.binarySearch(Versions.SNAPSHOT_INTS, other.intValue);
if (idx >= 0)
return this.compareToRelease(Versions.SNAPSHOT_PREV[idx]) <= 0 ? -1 : 1;
return this.compareToRelease(Versions.SNAPSHOT_PREV[idx]) <= 0 ? -1 : 1;
idx = -(idx + 1);
if (idx == Versions.SNAPSHOT_INTS.length)
return -1;
return this.compareToRelease(Versions.SNAPSHOT_PREV[idx]) <= 0 ? -1 : 1;
} else {
return 1;
}
}
@Override
int compareToImpl(@NotNull GameVersionNumber other) {
if (other instanceof Release)
return compareToRelease((Release) other);
if (other instanceof Release release)
return compareToRelease(release);
if (other instanceof Snapshot)
return compareToSnapshot((Snapshot) other);
if (other instanceof LegacySnapshot snapshot)
return compareToSnapshot(snapshot);
if (other instanceof Special)
return -((Special) other).compareToReleaseOrSnapshot(this);
if (other instanceof Special special)
return -special.compareToReleaseOrSnapshot(this);
throw new AssertionError(other.getClass());
}
@@ -412,7 +518,7 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
return patch;
}
public int getEaType() {
public ReleaseType getEaType() {
return eaType;
}
@@ -420,28 +526,65 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
return eaVersion;
}
public Additional getAdditional() {
return additional;
}
@Override
public int hashCode() {
return Objects.hash(major, minor, patch, eaType, eaVersion);
return Objects.hash(major, minor, patch, eaType, eaVersion, additional);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
return o instanceof Release other
&& major == other.major
&& minor == other.minor
&& patch == other.patch
&& eaType == other.eaType
&& eaVersion.equals(other.eaVersion);
return o instanceof Release that
&& this.major == that.major
&& this.minor == that.minor
&& this.patch == that.patch
&& this.eaType == that.eaType
&& this.eaVersion.equals(that.eaVersion)
&& this.additional == that.additional;
}
@Override
protected ToStringBuilder buildDebugString() {
return super.buildDebugString()
.append("major", major)
.append("minor", minor)
.append("patch", patch)
.append("eaType", eaType)
.append("eaVersion", eaVersion)
.append("additional", additional);
}
}
public static final class Snapshot extends GameVersionNumber {
static Snapshot parse(String value) {
if (value.length() != 6 || value.charAt(2) != 'w')
/// Legacy snapshot version numbers like `25w46a`.
public static final class LegacySnapshot extends GameVersionNumber {
static LegacySnapshot parse(String value) {
if (value.length() < 6 || value.charAt(2) != 'w')
throw new IllegalArgumentException(value);
int prefixLength;
boolean unobfuscated;
String normalized;
if (value.endsWith("_unobfuscated")) {
prefixLength = value.length() - "_unobfuscated".length();
unobfuscated = true;
normalized = value;
} else if (value.endsWith(" Unobfuscated")) {
prefixLength = value.length() - " Unobfuscated".length();
unobfuscated = true;
normalized = value.substring(0, prefixLength) + "_unobfuscated";
} else {
prefixLength = value.length();
unobfuscated = false;
normalized = value;
}
if (prefixLength != 6) {
throw new IllegalArgumentException(value);
}
int year;
int week;
try {
@@ -452,21 +595,21 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
}
char suffix = value.charAt(5);
if ((suffix < 'a' || suffix > 'z') && suffix != '~')
if (suffix < 'a' || suffix > 'z')
throw new IllegalArgumentException(value);
return new Snapshot(value, year, week, suffix);
return new LegacySnapshot(value, normalized, year, week, suffix, unobfuscated);
}
static int toInt(int year, int week, char suffix) {
return (year << 16) | (week << 8) | suffix;
static int toInt(int year, int week, char suffix, boolean unobfuscated) {
return (year << 24) | (week << 16) | (suffix << 8) | (unobfuscated ? 1 : 0);
}
final int intValue;
Snapshot(String value, int year, int week, char suffix) {
super(value);
this.intValue = toInt(year, week, suffix);
LegacySnapshot(String value, String normalized, int year, int week, char suffix, boolean unobfuscated) {
super(value, normalized);
this.intValue = toInt(year, week, suffix, unobfuscated);
}
@Override
@@ -476,40 +619,52 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
@Override
int compareToImpl(@NotNull GameVersionNumber other) {
if (other instanceof Release)
return -((Release) other).compareToSnapshot(this);
if (other instanceof Release otherRelease)
return -otherRelease.compareToSnapshot(this);
if (other instanceof Snapshot)
return Integer.compare(this.intValue, ((Snapshot) other).intValue);
if (other instanceof LegacySnapshot otherSnapshot)
return Integer.compare(this.intValue, otherSnapshot.intValue);
if (other instanceof Special)
return -((Special) other).compareToReleaseOrSnapshot(this);
if (other instanceof Special otherSpecial)
return -otherSpecial.compareToReleaseOrSnapshot(this);
throw new AssertionError(other.getClass());
}
public int getYear() {
return (intValue >> 16) & 0xff;
return (intValue >> 24) & 0xff;
}
public int getWeek() {
return (intValue >> 8) & 0xff;
return (intValue >> 16) & 0xff;
}
public char getSuffix() {
return (char) (intValue & 0xff);
return (char) ((intValue >> 8) & 0xff);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
return o instanceof Snapshot other && this.intValue == other.intValue;
public boolean isUnobfuscated() {
return (intValue & 0b00000001) != 0;
}
@Override
public int hashCode() {
return intValue;
}
@Override
public boolean equals(Object o) {
return o instanceof LegacySnapshot that && this.intValue == that.intValue;
}
@Override
protected ToStringBuilder buildDebugString() {
return super.buildDebugString()
.append("year", getYear())
.append("week", getWeek())
.append("suffix", getSuffix())
.append("unobfuscated", isUnobfuscated());
}
}
public static final class Special extends GameVersionNumber {
@@ -517,8 +672,8 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
private GameVersionNumber prev;
Special(String value) {
super(value);
Special(String value, String normalized) {
super(value, normalized);
}
@Override
@@ -534,13 +689,13 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
if (versionNumber != null)
return versionNumber;
return versionNumber = VersionNumber.asVersion(value);
return versionNumber = VersionNumber.asVersion(normalized);
}
GameVersionNumber getPrevNormalVersion() {
GameVersionNumber v = prev;
while (v instanceof Special) {
v = ((Special) v).prev;
while (v instanceof Special special) {
v = special.prev;
}
if (v == null) throw new AssertionError("version: " + value);
@@ -567,7 +722,7 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
if (other.isUnknown())
return -1;
if (this.value.equals(other.value))
if (this.normalized.equals(other.normalized))
return 0;
int c = this.getPrevNormalVersion().compareTo(other.getPrevNormalVersion());
@@ -575,11 +730,11 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
return c;
GameVersionNumber v = prev;
while (v instanceof Special) {
while (v instanceof Special special) {
if (v == other)
return 1;
v = ((Special) v).prev;
v = special.prev;
}
return -1;
@@ -587,27 +742,23 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
@Override
int compareToImpl(@NotNull GameVersionNumber o) {
if (o instanceof Release)
if (o instanceof Release || o instanceof LegacySnapshot)
return compareToReleaseOrSnapshot(o);
if (o instanceof Snapshot)
return compareToReleaseOrSnapshot(o);
if (o instanceof Special)
return compareToSpecial((Special) o);
if (o instanceof Special special)
return compareToSpecial(special);
throw new AssertionError(o.getClass());
}
@Override
public int hashCode() {
return value.hashCode();
public boolean equals(Object o) {
return o instanceof Special that && this.normalized.equals(that.normalized);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
return o instanceof Special other && this.value.equals(other.value);
public int hashCode() {
return normalized.hashCode();
}
}
@@ -621,10 +772,11 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
static {
ArrayDeque<String> defaultGameVersions = new ArrayDeque<>(64);
List<Snapshot> snapshots = new ArrayList<>(1024);
List<LegacySnapshot> snapshots = new ArrayList<>(1024);
List<Release> snapshotPrev = new ArrayList<>(1024);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(GameVersionNumber.class.getResourceAsStream("/assets/game/versions.txt"), StandardCharsets.US_ASCII))) {
//noinspection DataFlowIssue
try (var reader = new BufferedReader(new InputStreamReader(GameVersionNumber.class.getResourceAsStream("/assets/game/versions.txt"), StandardCharsets.US_ASCII))) {
Release currentRelease = null;
GameVersionNumber prev = null;
@@ -637,13 +789,13 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
if (currentRelease == null)
currentRelease = (Release) version;
if (version instanceof Snapshot snapshot) {
if (version instanceof LegacySnapshot snapshot) {
snapshots.add(snapshot);
snapshotPrev.add(currentRelease);
} else if (version instanceof Release) {
currentRelease = (Release) version;
} else if (version instanceof Release release) {
currentRelease = release;
if (currentRelease.eaType == Release.TYPE_GA) {
if (currentRelease.eaType == Release.ReleaseType.GA) {
defaultGameVersions.addFirst(currentRelease.value);
}
} else if (version instanceof Special special) {
@@ -658,6 +810,36 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
throw new AssertionError(e);
}
//noinspection DataFlowIssue
try (var reader = new BufferedReader(new InputStreamReader(GameVersionNumber.class.getResourceAsStream("/assets/game/version-alias.csv"), StandardCharsets.US_ASCII))) {
for (String line; (line = reader.readLine()) != null; ) {
if (line.isEmpty())
continue;
String[] parts = line.split(",");
if (parts.length < 2) {
LOG.warning("Invalid line: " + line);
continue;
}
String normalized = parts[0];
Special normalizedVersion = SPECIALS.get(normalized);
if (normalizedVersion == null) {
LOG.warning("Unknown special version: " + normalized);
continue;
}
for (int i = 1; i < parts.length; i++) {
String version = parts[i];
Special versionNumber = new Special(version, normalized);
versionNumber.prev = normalizedVersion.prev;
SPECIALS.put(version, versionNumber);
}
}
} catch (IOException e) {
throw new AssertionError(e);
}
DEFAULT_GAME_VERSIONS = defaultGameVersions.toArray(new String[0]);
SNAPSHOT_INTS = new int[snapshots.size()];
@@ -85,63 +85,63 @@
"releaseTime": "2021-07-13T12:54:19+00:00"
},
{
"id": "1_16_combat-6",
"id": "1.16_combat-6",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-6/1_16_combat-6.json",
"time": "2020-08-26T06:24:28+00:00",
"releaseTime": "2020-08-26T06:24:28+00:00"
},
{
"id": "1_16_combat-5",
"id": "1.16_combat-5",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-5/1_16_combat-5.json",
"time": "2020-08-21T09:23:13+00:00",
"releaseTime": "2020-08-21T09:23:13+00:00"
},
{
"id": "1_16_combat-4",
"id": "1.16_combat-4",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-4/1_16_combat-4.json",
"time": "2020-08-19T11:14:58+00:00",
"releaseTime": "2020-08-19T11:14:58+00:00"
},
{
"id": "1_16_combat-3",
"id": "1.16_combat-3",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-3/1_16_combat-3.json",
"time": "2020-08-14T09:02:15+00:00",
"releaseTime": "2020-08-14T09:02:15+00:00"
},
{
"id": "1_16_combat-2",
"id": "1.16_combat-2",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-2/1_16_combat-2.json",
"time": "2020-08-13T11:56:30+00:00",
"releaseTime": "2020-08-13T11:56:30+00:00"
},
{
"id": "1_16_combat-1",
"id": "1.16_combat-1",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-1/1_16_combat-1.json",
"time": "2020-08-12T14:07:25+00:00",
"releaseTime": "2020-08-12T14:07:25+00:00"
},
{
"id": "1_16_combat-0",
"id": "1.16_combat-0",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-0/1_16_combat-0.json",
"time": "2020-08-07T10:44:47+00:00",
"releaseTime": "2020-08-07T10:44:47+00:00"
},
{
"id": "1_15_combat-6",
"id": "1.15_combat-6",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_15_combat-6/1_15_combat-6.json",
"time": "2020-01-15T09:46:35+00:00",
"releaseTime": "2020-01-15T09:46:35+00:00"
},
{
"id": "1_15_combat-1",
"id": "1.15_combat-1",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_15_combat-1/1_15_combat-1.json",
"time": "2019-11-29T15:41:39+00:00",
@@ -0,0 +1,20 @@
1.14_combat-212796,1.14.3 - Combat Test
1.14_combat-0,Combat Test 2
1.14_combat-3,Combat Test 3
1.15_combat-1,Combat Test 4
1.15_combat-6,Combat Test 5
1.16_combat-0,Combat Test 6
1.16_combat-1,Combat Test 7
1.16_combat-2,Combat Test 7b
1.16_combat-3,Combat Test 7c
1.16_combat-4,Combat Test 8
1.16_combat-5,Combat Test 8b
1.16_combat-6,Combat Test 8c
1.18_experimental-snapshot-1,1.18 Experimental Snapshot 1
1.18_experimental-snapshot-2,1.18 Experimental Snapshot 2
1.18_experimental-snapshot-3,1.18 Experimental Snapshot 3
1.18_experimental-snapshot-4,1.18 Experimental Snapshot 4
1.18_experimental-snapshot-5,1.18 Experimental Snapshot 5
1.18_experimental-snapshot-6,1.18 Experimental Snapshot 6
1.19_deep_dark_experimental_snapshot-1,Deep Dark Experimental Snapshot 1
20w14infinite,20w14~
1 1.14_combat-212796 1.14.3 - Combat Test
2 1.14_combat-0 Combat Test 2
3 1.14_combat-3 Combat Test 3
4 1.15_combat-1 Combat Test 4
5 1.15_combat-6 Combat Test 5
6 1.16_combat-0 Combat Test 6
7 1.16_combat-1 Combat Test 7
8 1.16_combat-2 Combat Test 7b
9 1.16_combat-3 Combat Test 7c
10 1.16_combat-4 Combat Test 8
11 1.16_combat-5 Combat Test 8b
12 1.16_combat-6 Combat Test 8c
13 1.18_experimental-snapshot-1 1.18 Experimental Snapshot 1
14 1.18_experimental-snapshot-2 1.18 Experimental Snapshot 2
15 1.18_experimental-snapshot-3 1.18 Experimental Snapshot 3
16 1.18_experimental-snapshot-4 1.18 Experimental Snapshot 4
17 1.18_experimental-snapshot-5 1.18 Experimental Snapshot 5
18 1.18_experimental-snapshot-6 1.18 Experimental Snapshot 6
19 1.19_deep_dark_experimental_snapshot-1 Deep Dark Experimental Snapshot 1
20 20w14infinite 20w14~
@@ -452,24 +452,25 @@
3D Shareware v1.34
19w14a
19w14b
1.14 Pre-Release 1
1.14 Pre-Release 2
1.14 Pre-Release 3
1.14 Pre-Release 4
1.14 Pre-Release 5
1.14-pre1
1.14-pre2
1.14-pre3
1.14-pre4
1.14-pre5
1.14
1.14.1 Pre-Release 1
1.14.1 Pre-Release 2
1.14.1-pre1
1.14.1-pre2
1.14.1
1.14.2 Pre-Release 1
1.14.2 Pre-Release 2
1.14.2 Pre-Release 3
1.14.2 Pre-Release 4
1.14.2-pre1
1.14.2-pre2
1.14.2-pre3
1.14.2-pre4
1.14.2
1.14.3-pre1
1.14.3-pre2
1.14.3-pre3
1.14.3-pre4
1.14_combat-212796
1.14.3
1.14.4-pre1
1.14.4-pre2
@@ -479,6 +480,8 @@
1.14.4-pre6
1.14.4-pre7
1.14.4
1.14_combat-0
1.14_combat-3
19w34a
19w35a
19w36a
@@ -497,6 +500,7 @@
1.15-pre1
1.15-pre2
1.15-pre3
1.15_combat-1
1.15-pre4
1.15-pre5
1.15-pre6
@@ -506,6 +510,7 @@
1.15.1
1.15.2-pre1
1.15.2-pre2
1.15_combat-6
1.15.2
20w06a
20w07a
@@ -545,9 +550,16 @@
1.16.2-pre1
1.16.2-pre2
1.16.2-pre3
1.16_combat-0
1.16.2-rc1
1.16.2-rc2
1.16.2
1.16_combat-1
1.16_combat-2
1.16_combat-3
1.16_combat-4
1.16_combat-5
1.16_combat-6
1.16.3-rc1
1.16.3
1.16.4-pre1
@@ -592,6 +604,13 @@
1.17.1-rc1
1.17.1-rc2
1.17.1
1.18_experimental-snapshot-1
1.18_experimental-snapshot-2
1.18_experimental-snapshot-3
1.18_experimental-snapshot-4
1.18_experimental-snapshot-5
1.18_experimental-snapshot-6
1.18_experimental-snapshot-7
21w37a
21w38a
21w39a
@@ -622,6 +641,7 @@
22w05a
22w06a
22w07a
1.19_deep_dark_experimental_snapshot-1
1.18.2-pre1
1.18.2-pre2
1.18.2-pre3
@@ -1,4 +1,4 @@
# https://gitlab.freedesktop.org/mesa/libdrm/-/blob/7d43d9b6fe5df4b234937fce9e51adb1b327ded1/data/amdgpu.ids
# https://gitlab.freedesktop.org/mesa/libdrm/-/blob/35a21916c8f621b35a67f3c5994280ae59a4981a/data/amdgpu.ids
#
# List of AMDGPU IDs
#
@@ -559,6 +559,7 @@
7448, 00, AMD Radeon Pro W7900
7449, 00, AMD Radeon Pro W7800 48GB
744A, 00, AMD Radeon Pro W7900 Dual Slot
744B, 00, AMD Radeon Pro W7900D
744C, C8, AMD Radeon RX 7900 XTX
744C, CC, AMD Radeon RX 7900 XT
744C, CE, AMD Radeon RX 7900 GRE
@@ -569,6 +570,7 @@
7470, 00, AMD Radeon Pro W7700
747E, C8, AMD Radeon RX 7800 XT
747E, D8, AMD Radeon RX 7800M
747E, DB, AMD Radeon RX 7700
747E, FF, AMD Radeon RX 7700 XT
7480, 00, AMD Radeon Pro W7600
7480, C0, AMD Radeon RX 7600 XT
@@ -586,6 +588,7 @@
74A1, 00, AMD Instinct MI300X
74A2, 00, AMD Instinct MI308X
74A5, 00, AMD Instinct MI325X
74A8, 00, AMD Instinct MI308X HF
74A9, 00, AMD Instinct MI300X HF
74B5, 00, AMD Instinct MI300X VF
74B6, 00, AMD Instinct MI308X
@@ -594,6 +597,12 @@
7550, C2, AMD Radeon RX 9070 GRE
7550, C3, AMD Radeon RX 9070
7551, C0, AMD Radeon AI PRO R9700
7590, C0, AMD Radeon RX 9060 XT
7590, C7, AMD Radeon RX 9060
75A0, C0, AMD Instinct MI350X
75A3, C0, AMD Instinct MI355X
75B0, C0, AMD Instinct MI350X VF
75B3, C0, AMD Instinct MI355X VF
9830, 00, AMD Radeon HD 8400 / R3 Series
9831, 00, AMD Radeon HD 8400E
9832, 00, AMD Radeon HD 8330
@@ -689,4 +698,4 @@
98E4, E9, AMD Radeon R4 Graphics
98E4, EA, AMD Radeon R4 Graphics
98E4, EB, AMD Radeon R3 Graphics
98E4, EB, AMD Radeon R4 Graphics
98E4, EB, AMD Radeon R4 Graphics
@@ -25,6 +25,7 @@ import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Supplier;
import static org.jackhuang.hmcl.util.versioning.GameVersionNumber.asGameVersion;
import static org.junit.jupiter.api.Assertions.*;
@@ -34,6 +35,8 @@ import static org.junit.jupiter.api.Assertions.*;
*/
public final class GameVersionNumberTest {
//region Helpers
private static List<String> readVersions() {
List<String> versions = new ArrayList<>();
@@ -48,18 +51,8 @@ public final class GameVersionNumberTest {
return versions;
}
@Test
public void testSortVersions() {
List<String> versions = readVersions();
List<String> copied = new ArrayList<>(versions);
Collections.shuffle(copied, new Random(0));
copied.sort(Comparator.comparing(GameVersionNumber::asGameVersion));
assertIterableEquals(versions, copied);
}
private static String errorMessage(String version1, String version2) {
return String.format("version1=%s, version2=%s", version1, version2);
private static Supplier<String> errorMessage(GameVersionNumber version1, GameVersionNumber version2) {
return () -> "version1=%s, version2=%s".formatted(version1.toDebugString(), version2.toDebugString());
}
private static void assertGameVersionEquals(String version) {
@@ -67,25 +60,42 @@ public final class GameVersionNumberTest {
}
private static void assertGameVersionEquals(String version1, String version2) {
assertEquals(0, asGameVersion(version1).compareTo(version2), errorMessage(version1, version2));
assertEquals(asGameVersion(version1), asGameVersion(version2), errorMessage(version1, version2));
}
private static String toString(GameVersionNumber gameVersionNumber) {
return gameVersionNumber.getClass().getSimpleName();
GameVersionNumber gameVersion1 = asGameVersion(version1);
GameVersionNumber gameVersion2 = asGameVersion(version2);
assertEquals(0, gameVersion1.compareTo(gameVersion2), errorMessage(gameVersion1, gameVersion2));
assertEquals(0, gameVersion2.compareTo(gameVersion1), errorMessage(gameVersion1, gameVersion2));
assertEquals(gameVersion1, gameVersion2, errorMessage(gameVersion1, gameVersion2));
assertEquals(gameVersion2, gameVersion1, errorMessage(gameVersion1, gameVersion2));
assertEquals(gameVersion1.hashCode(), gameVersion2.hashCode(), errorMessage(gameVersion1, gameVersion2));
}
private static void assertOrder(String... versions) {
var gameVersionNumbers = new GameVersionNumber[versions.length];
for (int i = 0; i < versions.length; i++) {
gameVersionNumbers[i] = asGameVersion(versions[i]);
}
for (int i = 0; i < versions.length - 1; i++) {
GameVersionNumber version1 = asGameVersion(versions[i]);
GameVersionNumber version1 = gameVersionNumbers[i];
for (int j = 0; j < i; j++) {
GameVersionNumber version2 = gameVersionNumbers[j];
assertTrue(version1.compareTo(version2) > 0, errorMessage(version1, version2));
assertTrue(version2.compareTo(version1) < 0, errorMessage(version1, version2));
assertNotEquals(version1, version2, errorMessage(version1, version2));
assertNotEquals(version2, version1, errorMessage(version1, version2));
}
assertGameVersionEquals(versions[i]);
for (int j = i + 1; j < versions.length; j++) {
GameVersionNumber version2 = asGameVersion(versions[j]);
GameVersionNumber version2 = gameVersionNumbers[j];
assertEquals(-1, version1.compareTo(version2), String.format("version1=%s (%s), version2=%s (%s)", versions[i], toString(version1), versions[j], toString(version2)));
assertEquals(1, version2.compareTo(version1), String.format("version1=%s (%s), version2=%s (%s)", versions[i], toString(version1), versions[j], toString(version2)));
assertTrue(version1.compareTo(version2) < 0, errorMessage(version1, version2));
assertTrue(version2.compareTo(version1) > 0, errorMessage(version1, version2));
assertNotEquals(version1, version2, errorMessage(version1, version2));
assertNotEquals(version2, version1, errorMessage(version1, version2));
}
}
@@ -100,6 +110,8 @@ public final class GameVersionNumberTest {
assertEquals(VersionNumber.asVersion(versionNumber), old.versionNumber);
}
//endregion Helpers
private static boolean isAprilFools(String version) {
return asGameVersion(version).isAprilFools();
}
@@ -124,6 +136,31 @@ public final class GameVersionNumberTest {
assertFalse(isAprilFools("25w45a_unobfuscated"));
}
@Test
public void testSortVersions() {
List<String> versions = readVersions();
{
List<String> copied = new ArrayList<>(versions);
copied.sort(Comparator.comparing(GameVersionNumber::asGameVersion));
assertIterableEquals(versions, copied);
}
{
List<String> copied = new ArrayList<>(versions);
Collections.reverse(copied);
copied.sort(Comparator.comparing(GameVersionNumber::asGameVersion));
assertIterableEquals(versions, copied);
}
for (int randomSeed = 0; randomSeed < 5; randomSeed++) {
List<String> copied = new ArrayList<>(versions);
Collections.shuffle(copied, new Random(randomSeed));
copied.sort(Comparator.comparing(GameVersionNumber::asGameVersion));
assertIterableEquals(versions, copied);
}
}
@Test
public void testParseOld() {
assertOldVersion("rd-132211", GameVersionNumber.Type.PRE_CLASSIC, "132211");
@@ -136,36 +173,96 @@ public final class GameVersionNumberTest {
assertOldVersion("a1.0.13_01-1", GameVersionNumber.Type.ALPHA, "1.0.13_01-1");
assertOldVersion("b1.0", GameVersionNumber.Type.BETA, "1.0");
assertOldVersion("b1.0_01", GameVersionNumber.Type.BETA, "1.0_01");
assertOldVersion("b1.6-tb3", GameVersionNumber.Type.BETA, "1.6-tb3");
assertOldVersion("b1.8-pre1-2", GameVersionNumber.Type.BETA, "1.8-pre1-2");
assertOldVersion("b1.9-pre1", GameVersionNumber.Type.BETA, "1.9-pre1");
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse(""));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("1.21"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("r-132211"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("rd-"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("rd-a"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("i-20100223"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("in-"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("in-a"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("inf-"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("inf-a"));
}
private static void testParseLegacySnapshot(int year, int week, char suffix) {
String raw = "%02dw%02d%s".formatted(year, week, suffix);
var rawVersion = (GameVersionNumber.LegacySnapshot) asGameVersion(raw);
assertInstanceOf(GameVersionNumber.LegacySnapshot.class, rawVersion);
assertEquals(raw, rawVersion.toString());
assertEquals(raw, rawVersion.toNormalizedString());
assertEquals(year, rawVersion.getYear());
assertEquals(week, rawVersion.getWeek());
assertEquals(suffix, rawVersion.getSuffix());
assertFalse(rawVersion.isUnobfuscated());
var unobfuscated = raw + "_unobfuscated";
var unobfuscatedVersion = (GameVersionNumber.LegacySnapshot) asGameVersion(unobfuscated);
assertInstanceOf(GameVersionNumber.LegacySnapshot.class, rawVersion);
assertEquals(unobfuscated, unobfuscatedVersion.toString());
assertEquals(unobfuscated, unobfuscatedVersion.toNormalizedString());
assertEquals(year, unobfuscatedVersion.getYear());
assertEquals(week, unobfuscatedVersion.getWeek());
assertEquals(suffix, unobfuscatedVersion.getSuffix());
assertTrue(unobfuscatedVersion.isUnobfuscated());
var unobfuscated2 = raw + " Unobfuscated";
var unobfuscatedVersion2 = (GameVersionNumber.LegacySnapshot) asGameVersion(unobfuscated2);
assertInstanceOf(GameVersionNumber.LegacySnapshot.class, rawVersion);
assertEquals(unobfuscated2, unobfuscatedVersion2.toString());
assertEquals(unobfuscated, unobfuscatedVersion2.toNormalizedString());
assertEquals(year, unobfuscatedVersion2.getYear());
assertEquals(week, unobfuscatedVersion2.getWeek());
assertEquals(suffix, unobfuscatedVersion2.getSuffix());
assertTrue(unobfuscatedVersion2.isUnobfuscated());
}
@Test
public void testParseNew() {
List<String> versions = readVersions();
for (String version : versions) {
assertFalse(asGameVersion(version) instanceof GameVersionNumber.Old, "version=" + version);
GameVersionNumber gameVersion = asGameVersion(version);
assertFalse(gameVersion instanceof GameVersionNumber.Old, "version=" + gameVersion.toDebugString());
}
testParseLegacySnapshot(25, 46, 'a');
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parse("2.1"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.LegacySnapshot.parse("1.0"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.LegacySnapshot.parse("1.100.1"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.LegacySnapshot.parse("aawbba"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.LegacySnapshot.parse("13w12A"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.LegacySnapshot.parse("13w12~"));
}
private static void assertSimpleReleaseVersion(String simpleReleaseVersion, int minor, int patch) {
private static void assertSimpleReleaseVersion(String simpleReleaseVersion, int major, int minor, int patch) {
GameVersionNumber.Release release = GameVersionNumber.Release.parseSimple(simpleReleaseVersion);
assertAll("Assert Simple Release Version " + simpleReleaseVersion,
() -> assertEquals(1, release.getMajor()),
() -> assertEquals(major, release.getMajor()),
() -> assertEquals(minor, release.getMinor()),
() -> assertEquals(patch, release.getPatch()),
() -> assertEquals(GameVersionNumber.Release.TYPE_UNKNOWN, release.getEaType()),
() -> assertEquals(GameVersionNumber.Release.ReleaseType.UNKNOWN, release.getEaType()),
() -> assertEquals(VersionNumber.ZERO, release.getEaVersion())
);
}
@Test
public void testParseSimpleRelease() {
assertSimpleReleaseVersion("1.0", 0, 0);
assertSimpleReleaseVersion("1.13", 13, 0);
assertSimpleReleaseVersion("1.21.8", 21, 8);
assertSimpleReleaseVersion("1.0", 1, 0, 0);
assertSimpleReleaseVersion("1.13", 1, 13, 0);
assertSimpleReleaseVersion("1.21.8", 1, 21, 8);
assertSimpleReleaseVersion("26.1", 26, 1, 0);
assertSimpleReleaseVersion("26.1.1", 26, 1, 1);
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("26"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("24.0.0"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("24.0"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("2.0"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("1"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("1..0"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("1.0."));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("1.a"));
@@ -186,13 +283,13 @@ public final class GameVersionNumberTest {
"0.0",
"1.0",
"1.99",
"1.99.1-unknown1",
"1.99.1-pre1",
"1.99.1 Pre-Release 2",
"1.99.1-rc1",
"1.99.1",
"1.100",
"1.100.1"
"1.100.1",
"26.1"
);
}
@@ -202,7 +299,6 @@ public final class GameVersionNumberTest {
"90w01a",
"90w01b",
"90w01e",
"90w01~",
"90w02a"
);
}
@@ -257,6 +353,7 @@ public final class GameVersionNumberTest {
"1.14",
"1.15.2",
"20w06a",
"20w13b",
"20w14infinite",
"20w22a",
"1.16-pre1",
@@ -273,10 +370,21 @@ public final class GameVersionNumberTest {
"24w13a",
"24w14potato",
"24w14a",
"25w45a",
"25w45a_unobfuscated",
"Unknown",
"100.0"
"25w46a",
"25w46a_unobfuscated",
"1.21.11-pre1",
"1.21.11-pre1_unobfuscated",
"1.21.11-pre2",
"1.21.11-pre2_unobfuscated",
"99w99a",
"26.1-snapshot-1",
"26.1-snapshot-2",
"26.1",
"26.2-snapshot-1",
"26.2-snapshot-2",
"26.2",
"100.0",
"Unknown"
);
}
@@ -312,26 +420,84 @@ public final class GameVersionNumberTest {
);
}
private static void assertNormalized(String normalized, String version) {
assertGameVersionEquals(version);
assertGameVersionEquals(normalized, version);
assertEquals(normalized, asGameVersion(version).toNormalizedString());
}
@Test
public void testToNormalizedString() {
for (String version : readVersions()) {
assertNormalized(version, version);
}
assertNormalized("26.1-snapshot-1", "26.1 Snapshot 1");
assertNormalized("1.21.11-pre3", "1.21.11 Pre-Release 3");
assertNormalized("1.21.11-pre3_unobfuscated", "1.21.11 Pre-Release 3 Unobfuscated");
assertNormalized("1.21.11-pre3_unobfuscated", "1.21.11-pre3 Unobfuscated");
assertNormalized("1.21.11-rc1", "1.21.11 Release Candidate 1");
assertNormalized("1.21.11-rc1_unobfuscated", "1.21.11 Release Candidate 1 Unobfuscated");
assertNormalized("1.14_combat-212796", "1.14.3 - Combat Test");
assertNormalized("1.14_combat-0", "Combat Test 2");
assertNormalized("1.14_combat-3", "Combat Test 3");
assertNormalized("1.15_combat-1", "Combat Test 4");
assertNormalized("1.15_combat-6", "Combat Test 5");
assertNormalized("1.16_combat-0", "Combat Test 6");
assertNormalized("1.16_combat-1", "Combat Test 7");
assertNormalized("1.16_combat-2", "Combat Test 7b");
assertNormalized("1.16_combat-3", "Combat Test 7c");
assertNormalized("1.16_combat-4", "Combat Test 8");
assertNormalized("1.16_combat-5", "Combat Test 8b");
assertNormalized("1.16_combat-6", "Combat Test 8c");
assertNormalized("1.18_experimental-snapshot-1", "1.18 Experimental Snapshot 1");
assertNormalized("1.18_experimental-snapshot-2", "1.18 Experimental Snapshot 2");
assertNormalized("1.18_experimental-snapshot-3", "1.18 Experimental Snapshot 3");
assertNormalized("1.18_experimental-snapshot-4", "1.18 Experimental Snapshot 4");
assertNormalized("1.18_experimental-snapshot-5", "1.18 Experimental Snapshot 5");
assertNormalized("1.18_experimental-snapshot-6", "1.18 Experimental Snapshot 6");
assertNormalized("1.19_deep_dark_experimental_snapshot-1", "Deep Dark Experimental Snapshot 1");
assertNormalized("20w14infinite", "20w14~");
}
@Test
public void isAtLeast() {
assertTrue(asGameVersion("1.13").isAtLeast("1.13", "17w43a"));
assertTrue(asGameVersion("1.13.1").isAtLeast("1.13", "17w43a"));
assertTrue(asGameVersion("1.14").isAtLeast("1.13", "17w43a"));
assertTrue(asGameVersion("1.13-rc1").isAtLeast("1.13", "17w43a"));
assertTrue(asGameVersion("1.13-pre1").isAtLeast("1.13", "17w43a"));
assertTrue(asGameVersion("17w43a").isAtLeast("1.13", "17w43a"));
assertTrue(asGameVersion("17w43b").isAtLeast("1.13", "17w43a"));
assertTrue(asGameVersion("17w45a").isAtLeast("1.13", "17w43a"));
assertTrue(asGameVersion("1.13").isAtLeast("1.13", "17w43a", true));
assertTrue(asGameVersion("1.13").isAtLeast("1.13", "17w43a", false));
assertTrue(asGameVersion("1.13.1").isAtLeast("1.13", "17w43a", true));
assertTrue(asGameVersion("1.13.1").isAtLeast("1.13", "17w43a", false));
assertTrue(asGameVersion("1.14").isAtLeast("1.13", "17w43a", true));
assertTrue(asGameVersion("1.14").isAtLeast("1.13", "17w43a", false));
assertTrue(asGameVersion("1.13-rc1").isAtLeast("1.13", "17w43a", false));
assertTrue(asGameVersion("1.13-pre1").isAtLeast("1.13", "17w43a", false));
assertTrue(asGameVersion("17w43a").isAtLeast("1.13", "17w43a", true));
assertTrue(asGameVersion("17w43a").isAtLeast("1.13", "17w43a", false));
assertTrue(asGameVersion("17w43b").isAtLeast("1.13", "17w43a", true));
assertTrue(asGameVersion("17w43b").isAtLeast("1.13", "17w43a", false));
assertTrue(asGameVersion("17w45a").isAtLeast("1.13", "17w43a", true));
assertTrue(asGameVersion("17w45a").isAtLeast("1.13", "17w43a", false));
assertFalse(asGameVersion("17w31a").isAtLeast("1.13", "17w43a"));
assertFalse(asGameVersion("1.12").isAtLeast("1.13", "17w43a"));
assertFalse(asGameVersion("1.12.2").isAtLeast("1.13", "17w43a"));
assertFalse(asGameVersion("1.12.2-pre1").isAtLeast("1.13", "17w43a"));
assertFalse(asGameVersion("rd-132211").isAtLeast("1.13", "17w43a"));
assertFalse(asGameVersion("a1.0.6").isAtLeast("1.13", "17w43a"));
assertThrows(IllegalArgumentException.class, () -> asGameVersion("1.13").isAtLeast("17w43a", "17w43a"));
assertThrows(IllegalArgumentException.class, () -> asGameVersion("17w43a").isAtLeast("1.13", "1.13"));
assertThrows(IllegalArgumentException.class, () -> asGameVersion("17w43a").isAtLeast("1.13", "22w13oneblockatatime"));
assertFalse(asGameVersion("1.13-rc1").isAtLeast("1.13", "17w43a", true));
assertFalse(asGameVersion("1.13-pre1").isAtLeast("1.13", "17w43a", true));
assertFalse(asGameVersion("17w31a").isAtLeast("1.13", "17w43a", true));
assertFalse(asGameVersion("17w31a").isAtLeast("1.13", "17w43a", false));
assertFalse(asGameVersion("1.12").isAtLeast("1.13", "17w43a", true));
assertFalse(asGameVersion("1.12").isAtLeast("1.13", "17w43a", false));
assertFalse(asGameVersion("1.12.2").isAtLeast("1.13", "17w43a", true));
assertFalse(asGameVersion("1.12.2").isAtLeast("1.13", "17w43a", false));
assertFalse(asGameVersion("1.12.2-pre1").isAtLeast("1.13", "17w43a", true));
assertFalse(asGameVersion("1.12.2-pre1").isAtLeast("1.13", "17w43a", false));
assertFalse(asGameVersion("rd-132211").isAtLeast("1.13", "17w43a", true));
assertFalse(asGameVersion("rd-132211").isAtLeast("1.13", "17w43a", false));
assertFalse(asGameVersion("a1.0.6").isAtLeast("1.13", "17w43a", true));
assertFalse(asGameVersion("a1.0.6").isAtLeast("1.13", "17w43a", false));
assertThrows(IllegalArgumentException.class, () -> asGameVersion("1.13").isAtLeast("17w43a", "17w43a", true));
assertThrows(IllegalArgumentException.class, () -> asGameVersion("1.13").isAtLeast("17w43a", "17w43a", false));
assertThrows(IllegalArgumentException.class, () -> asGameVersion("17w43a").isAtLeast("1.13", "1.13", true));
assertThrows(IllegalArgumentException.class, () -> asGameVersion("17w43a").isAtLeast("1.13", "1.13", false));
assertThrows(IllegalArgumentException.class, () -> asGameVersion("17w43a").isAtLeast("1.13", "22w13oneblockatatime", true));
assertThrows(IllegalArgumentException.class, () -> asGameVersion("17w43a").isAtLeast("1.13", "22w13oneblockatatime", false));
}
}