Files
Documentation/content/patch-note/2026.01.13-50e69c385-2026.01.15-c04fdfe10-patch-note.md
2026-01-20 20:33:59 +01:00

25 KiB
Raw Blame History

Patch Notes: Hytale Server

Version 2026.01.13-50e69c385 → 2026.01.15-c04fdfe10

Date de release : 15 janvier 2026 Build précédent : 50e69c385 (13 janvier 2026) Build actuel : c04fdfe10 (15 janvier 2026)


Statistiques

Métrique Valeur
Fichiers modifiés 9
Insertions +55
Suppressions -18
Delta net +37 lignes

Table des matières

  1. Authentification & Sécurité JWT
  2. Gestion d'échec d'authentification serveur
  3. Transport QUIC - Optimisations réseau
  4. Timeout Setup Handler
  5. Physique des Items - Collisions latérales
  6. Nouvelle raison d'arrêt MISSING_ASSETS
  7. Validation des Asset Packs
  8. Télémétrie Sentry enrichie
  9. Fix NullPointerException CraftingManager

1. Authentification & Sécurité JWT

Fichier : com/hypixel/hytale/server/core/auth/JWTValidator.java Lignes modifiées : 31, 87-99, 293-309, 355-372

1.1 Nouvelle constante (non utilisée)

private static final long CLOCK_SKEW_SECONDS = 300L;

Une constante a été ajoutée pour centraliser la valeur du clock skew, mais elle n'est pas encore utilisée dans le code - les valeurs 300L sont hardcodées directement dans les méthodes de validation.

1.2 Augmentation du Clock Skew : 60s → 300s

Le clock skew (tolérance de désynchronisation d'horloge entre client et serveur) passe de 1 minute à 5 minutes. Cette modification affecte la validation de trois types de tokens JWT :

Méthode validateToken() (lignes 87-99)

Claim Avant Après Comportement
exp (expires at) now >= exp + 60 now >= exp + 300 Token accepté jusqu'à 5 min après expiration
nbf (not before) now < nbf - 60 now < nbf - 300 Token accepté 5 min avant validité
iat (issued at) Non vérifié iat > now + 300 NOUVEAU : Rejette tokens du futur

Code ajouté (lignes 96-99) :

if (claims.issuedAt != null && claims.issuedAt > nowSeconds + 300L) {
    LOGGER.at(Level.WARNING).log("Token issued in the future (iat: %d, now: %d)",
        (Object)claims.issuedAt, nowSeconds);
    return null;
}

Méthode validateIdentityToken() (lignes 293-309)

Claim Avant Après
exp now >= exp + 60 now >= exp + 300
nbf now < nbf - 60 now < nbf - 300
iat iat > now + 60 iat > now + 300

La validation iat existait déjà pour les identity tokens, seule la valeur du clock skew a changé.

Méthode validateSessionToken() (lignes 355-372)

Claim Avant Après Comportement
exp now >= exp + 60 now >= exp + 300 Token accepté jusqu'à 5 min après expiration
nbf now < nbf - 60 now < nbf - 300 Token accepté 5 min avant validité
iat Non vérifié iat > now + 300 NOUVEAU : Rejette tokens du futur

Code ajouté (lignes 368-371) :

if (claims.issuedAt != null && claims.issuedAt > nowSeconds + 300L) {
    LOGGER.at(Level.WARNING).log("Session token issued in the future (iat: %d, now: %d)",
        (Object)claims.issuedAt, nowSeconds);
    return null;
}

1.3 Impact

Aspect Effet
Compatibilité Meilleure tolérance pour clients avec horloge désynchronisée
Sécurité Protection contre tokens pré-générés pour utilisation future
Stabilité Réduction des erreurs de validation dues au décalage horaire

1.4 Contexte technique

Le JWTValidator gère trois types de tokens avec des rôles distincts :

  1. Auth Token (JWTClaims) : Authentification client, contient username, IP, certificate fingerprint
  2. Identity Token (IdentityTokenClaims) : Identité du joueur, requiert scopes hytale:client ou hytale:editor
  3. Session Token (SessionTokenClaims) : Sessions serveur, validation minimale

Le système utilise un cache JWKS (JSON Web Key Set) avec durée de 1 heure et mécanisme de retry intelligent en cas d'échec de vérification de signature.


2. Gestion d'échec d'authentification serveur

Fichier : com/hypixel/hytale/server/core/io/handlers/login/HandshakeHandler.java Lignes modifiées : 291-298 (dans le callback exceptionally)

2.1 Modification du comportement

Dans la méthode exchangeServerAuthGrant(), le callback .exceptionally() (gestion des erreurs async) a été modifié. Lorsqu'une exception se produit pendant l'échange de server token (AuthState.EXCHANGING_SERVER_TOKEN) :

Avant :

.exceptionally(ex -> {
    LOGGER.at(Level.WARNING).withCause(ex).log("Error exchanging server auth grant");
    this.channel.eventLoop().execute(() -> {
        if (this.authState != AuthState.EXCHANGING_SERVER_TOKEN) {
            return;
        }
        byte[] passwordChallenge = this.generatePasswordChallengeIfNeeded();
        this.completeAuthentication(passwordChallenge);
    });
    return null;
});

Après :

.exceptionally(ex -> {
    LOGGER.at(Level.WARNING).withCause(ex).log("Error exchanging server auth grant");
    this.channel.eventLoop().execute(() -> {
        if (this.authState != AuthState.EXCHANGING_SERVER_TOKEN) {
            return;
        }
        this.disconnect("Server authentication failed - please try again later");
    });
    return null;
});

2.2 Impact sur la sécurité

Aspect Avant Après
Échec server token exchange Authentification continuait silencieusement Déconnexion immédiate
Bypass potentiel Possible dans certains cas de timeout Impossible
Message utilisateur Aucun Message explicite

2.3 Contexte : Flow d'authentification mutuelle

Le HandshakeHandler orchestre un flow d'authentification mutuelle (mTLS) avec les états suivants :

REQUESTING_AUTH_GRANT (30s timeout)
    ↓
AWAITING_AUTH_TOKEN (30s timeout)
    ↓
PROCESSING_AUTH_TOKEN
    ↓
EXCHANGING_SERVER_TOKEN (15s timeout)  ← Changement ici
    ↓
AUTHENTICATED

Étapes détaillées :

  1. Validation Identity Token : Vérifie le token du client, UUID, scopes
  2. Demande Auth Grant : Récupère le server session token, appel async au SessionServiceClient
  3. Traitement Token Client : Vérifie JWT signature, certificate binding, UUID/username
  4. Échange Server Token : Échange auth grant contre token avec fingerprint serveur
  5. Complétion : Crée PlayerAuthentication, passe au SetupPacketHandler

Le changement garantit qu'un échec à l'étape 4 ne permet plus de compléter l'authentification.


3. Transport QUIC - Optimisations réseau

Fichier : com/hypixel/hytale/server/core/io/transport/QUICTransport.java Lignes modifiées : 30, 128

3.1 Nouvel import

import io.netty.handler.codec.quic.QuicCongestionControlAlgorithm;

3.2 Activation de PMTU Discovery

Ajout : .discoverPmtu(true)

Le Path MTU Discovery permet au serveur de :

Fonctionnalité Description
Détection automatique Découvre la taille maximale de paquet sur chaque chemin réseau
Évitement fragmentation Prévient la fragmentation IP qui dégrade les performances
Optimisation throughput Utilise des paquets de taille optimale par connexion

3.3 Changement d'algorithme de congestion : BBR

Ajout : .congestionControlAlgorithm(QuicCongestionControlAlgorithm.BBR)

Comparaison des algorithmes

Aspect Cubic (implicite avant) BBR (nouveau)
Approche Loss-based Model-based
Réaction aux pertes Réduit agressivement le débit Maintient le débit estimé
Latence Variable, peut augmenter Minimisée activement
Réseaux lossy Performances dégradées Meilleures performances
Bufferbloat Peut remplir les buffers Évite le bufferbloat
WiFi/Mobile Sous-optimal Optimisé

BBR (Bottleneck Bandwidth and Round-trip propagation time) est l'algorithme développé par Google qui :

  • Estime continuellement la bande passante disponible
  • Maintient un RTT minimal
  • Offre de meilleures performances sur réseaux modernes

3.4 Configuration QUIC complète après modification

QuicServerCodecBuilder()
    .sslContext(sslContext)
    .tokenHandler(InsecureQuicTokenHandler.INSTANCE)
    .maxIdleTimeout(playTimeout.toMillis(), TimeUnit.MILLISECONDS)
    .ackDelayExponent(3L)                                    // 8ms entre acks
    .initialMaxData(524288L)                                 // 512 KB total
    .initialMaxStreamDataUnidirectional(0L)                  // Désactivé
    .initialMaxStreamsUnidirectional(0L)                     // Désactivé
    .initialMaxStreamDataBidirectionalLocal(131072L)         // 128 KB par stream
    .initialMaxStreamDataBidirectionalRemote(131072L)        // 128 KB par stream
    .initialMaxStreamsBidirectional(1L)                      // 1 stream
    .discoverPmtu(true)                                      // NOUVEAU
    .congestionControlAlgorithm(QuicCongestionControlAlgorithm.BBR)  // NOUVEAU

3.5 Contexte technique

Le QUICTransport configure également :

  • mTLS obligatoire : ClientAuth.REQUIRE
  • Certificat auto-signé : Généré au démarrage
  • IPv4/IPv6 dual-stack : Bootstraps séparés
  • Options socket : SO_REUSEADDR, IP_DONTFRAGMENT
  • Certificate fingerprinting : Pour validation JWT

4. Timeout Setup Handler

Fichier : com/hypixel/hytale/server/core/io/handlers/SetupPacketHandler.java Ligne modifiée : 102

4.1 Modification du timeout

// Avant
this.setTimeout("send-world-settings", () -> this.assets != null, 1L, TimeUnit.SECONDS);

// Après
this.setTimeout("send-world-settings", () -> this.assets != null, 10L, TimeUnit.SECONDS);

4.2 Contexte

Ce timeout attend que les assets soient prêts (this.assets != null) avant d'envoyer les paramètres du monde (WorldSettings) au client.

4.3 Raisons du changement

Scénario Impact avec 1s Impact avec 10s
Connexion haute latence Timeout fréquent Toléré
Serveur avec nombreux asset packs Timeout possible Toléré
Charge serveur élevée Timeout possible Toléré
Démarrage lent des assets Déconnexion Attente correcte

4.4 Autres timeouts dans le SetupPacketHandler

Timeout Durée Condition Usage
send-world-settings 10s (modifié) this.assets != null Attente chargement assets
receive-assets-request 120s Packet RequestAssets reçu Négociation d'assets
send-assets 120s SendCommonAssetsEvent terminé Envoi assets + translations
add-to-universe 60s Universe.addPlayer() terminé Création joueur dans monde

4.5 Phases du Setup Handler

[Post-Auth] → registered0()
    ↓
[WorldSettings + ServerInfo envoyés]
    ↓
[Client envoie RequestAssets] (120s max)
    ↓
[Envoi CommonAssets + Translations] (120s max)
    ↓
[Client envoie PlayerOptions]
    ↓
[AddToUniverse] (60s max)
    ↓
[Joueur connecté au monde]

5. Physique des Items - Collisions latérales

Fichier : com/hypixel/hytale/server/core/modules/entity/item/ItemPhysicsSystem.java Lignes modifiées : 84-97

5.1 Architecture du système

Élément Description
Classe parente EntityTickingSystem<EntityStore>
Pattern Entity Component System (ECS)
Tick rate Appelé à chaque tick du monde

5.2 Composants utilisés

Composant Rôle
ItemPhysicsComponent État physique spécifique aux items
BoundingBox Boîte de collision 3D
Velocity (VelocityComponent) Vecteur de vélocité
TransformComponent Position et rotation

5.3 Modification de la gestion des collisions

Avant : Seules les collisions verticales (sol) étaient gérées

BlockCollisionData blockCollisionData = collisionResult.getFirstBlockCollision();
if (blockCollisionData != null && blockCollisionData.collisionNormal.equals(Vector3d.UP)) {
    velocityComponent.setZero();
    position.assign(blockCollisionData.collisionPoint);
} else {
    velocityComponent.assignVelocityTo(scaledVelocity).scale(dt);
    position.add(scaledVelocity);
}

Après : Gestion complète de toutes les directions de collision

BlockCollisionData blockCollisionData = collisionResult.getFirstBlockCollision();
if (blockCollisionData != null) {
    if (blockCollisionData.collisionNormal.equals(Vector3d.UP)) {
        // Collision avec le sol : arrêt complet
        velocityComponent.setZero();
        position.assign(blockCollisionData.collisionPoint);
    } else {
        // Collision latérale : annulation de la composante perpendiculaire
        Vector3d velocity = velocityComponent.getVelocity();
        double dot = velocity.dot(blockCollisionData.collisionNormal);
        Vector3d velocityToCancel = blockCollisionData.collisionNormal.clone().scale(dot);
        velocity.subtract(velocityToCancel);
    }
} else {
    velocityComponent.assignVelocityTo(scaledVelocity).scale(dt);
    position.add(scaledVelocity);
}

5.4 Explication mathématique

Pour une collision latérale (mur, escalier, pente) :

  1. Calcul du produit scalaire : dot = velocity · normal

    • Mesure la composante de vélocité perpendiculaire à la surface
  2. Calcul du vecteur à annuler : velocityToCancel = normal × dot

    • Vecteur perpendiculaire à la surface avec magnitude égale à la composante perpendiculaire
  3. Soustraction : velocity = velocity - velocityToCancel

    • Projette la vélocité sur le plan tangent à la surface

Formule vectorielle : v' = v - (v · n) × n

5.5 Impact sur le gameplay

Comportement Avant Après
Item lancé contre un mur Traversait ou s'arrêtait net Glisse le long du mur
Item tombant sur une pente Comportement incohérent Roule correctement
Item dans un coin Pouvait se bloquer/traverser Réagit physiquement

5.6 Méthodes de détection de collision

Le système utilise deux méthodes selon la magnitude de vitesse :

Méthode Condition Usage
findBlockCollisionsShortDistance() Mouvement faible Optimisation pour petits mouvements
findBlockCollisionsIterative() Mouvement important Précision pour grandes vitesses

6. Nouvelle raison d'arrêt MISSING_ASSETS

Fichier : com/hypixel/hytale/server/core/ShutdownReason.java Ligne ajoutée : 17

6.1 Nouvelle constante

public static final ShutdownReason MISSING_ASSETS = new ShutdownReason(7);

6.2 Liste complète des ShutdownReason

Constante Exit Code Description Utilisation
SIGINT 130 Signal d'interruption Ctrl+C ou SIGINT reçu
SHUTDOWN 0 Arrêt gracieux Commande /stop ou shutdown normal
CRASH 1 Crash serveur Erreur non gérée pendant le boot
AUTH_FAILED 2 Échec authentification Impossible de s'authentifier auprès des services
WORLD_GEN 3 Erreur génération monde WorldGenLoadException
CLIENT_GONE 4 Client singleplayer disparu Mode singleplayer, client déconnecté
MISSING_REQUIRED_PLUGIN 5 Plugin core manquant Plugin requis non trouvé
VALIDATE_ERROR 6 Validation assets échouée Erreur de validation des assets
MISSING_ASSETS 7 Aucun asset pack NOUVEAU : Aucun pack chargé

6.3 Pattern d'utilisation

// Utilisation simple
HytaleServer.get().shutdownServer(ShutdownReason.MISSING_ASSETS);

// Avec message personnalisé
HytaleServer.get().shutdownServer(
    ShutdownReason.MISSING_ASSETS.withMessage("Failed to load any asset packs")
);

La méthode withMessage(String) crée une nouvelle instance de ShutdownReason avec le même code de sortie mais un message additionnel pour les logs.


7. Validation des Asset Packs

Fichier : com/hypixel/hytale/server/core/asset/AssetModule.java Lignes modifiées : 24, 104-106

7.1 Nouvel import

import com.hypixel.hytale.server.core.ShutdownReason;

7.2 Nouvelle vérification

Après le chargement des asset packs depuis tous les répertoires de mods :

for (Path modsPath : Options.getOptionSet().valuesOf(Options.MODS_DIRECTORIES)) {
    this.loadPacksFromDirectory(modsPath);
}

// NOUVEAU : Vérification des assets
if (this.assetPacks.isEmpty()) {
    HytaleServer.get().shutdownServer(
        ShutdownReason.MISSING_ASSETS.withMessage("Failed to load any asset packs")
    );
    return;
}

7.3 Cycle de vie de l'AssetModule

setup()
    ↓
[Création AssetMonitor si --disable-file-watcher absent]
    ↓
[Chargement packs CLI]
    ↓
[Chargement packs mods/]
    ↓
[NOUVEAU: Vérification assetPacks.isEmpty()] ← Shutdown si vide
    ↓
[Registration event listeners]
    ↓
LoadAssetEvent dispatch
    ↓
[AssetRegistryLoader charge tous les assets]
    ↓
BootEvent
    ↓
[Log nombre total d'assets]

7.4 Sources de chargement des asset packs

Source Méthode Description
CLI --assets loadPacksFromDirectory() Chemins spécifiés en ligne de commande
Dossier mods/ loadPacksFromDirectory() Packs dans le dossier mods par défaut
Autres --mods-directories loadPacksFromDirectory() Répertoires additionnels

7.5 Types de packs supportés

Type Détection Immutabilité
ZIP/JAR Contient manifest.json Toujours immutable
Répertoire Contient manifest.json Immutable si CommonAssetsIndex.hashes présent
Répertoire sans hash Contient manifest.json Mutable (rechargement possible)

7.6 AssetMonitor (rechargement temps réel)

Si --disable-file-watcher n'est pas passé, un AssetMonitor est créé pour :

  • Surveiller les modifications de fichiers dans les packs mutables
  • Déclencher un rechargement automatique des assets modifiés
  • Permettre le développement itératif sans redémarrage serveur

8. Télémétrie Sentry enrichie

Fichier : com/hypixel/hytale/server/core/HytaleServer.java Lignes modifiées : 30, 51, 155-170

8.1 Nouveaux imports

import com.hypixel.hytale.server.core.auth.SessionServiceClient;
import io.sentry.protocol.User;

8.2 Nouvelles données utilisateur collectées

Dans le callback beforeSend de Sentry, un objet User est maintenant créé et attaché à chaque événement :

User user = new User();
HashMap<String, Object> unknown = new HashMap<String, Object>();
user.setUnknown(unknown);

// 1. Hardware UUID
UUID hardwareUUID = HardwareUtil.getUUID();
if (hardwareUUID != null) {
    unknown.put("hardware-uuid", hardwareUUID.toString());
}

// 2. Mode d'authentification
ServerAuthManager authManager = ServerAuthManager.getInstance();
unknown.put("auth-mode", authManager.getAuthMode().toString());

// 3. Profil utilisateur (si authentifié)
SessionServiceClient.GameProfile profile = authManager.getSelectedProfile();
if (profile != null) {
    user.setUsername(profile.username);
    user.setId(profile.uuid.toString());
}

// 4. Adresse IP (auto-détection Sentry)
user.setIpAddress("{{auto}}");

event.setUser(user);

8.3 Données collectées par Sentry

Données utilisateur (nouvelles)

Champ Sentry Source Type Description
user.username GameProfile.username String Nom d'utilisateur du compte
user.id GameProfile.uuid UUID String Identifiant unique du compte
user.ip_address Auto-détecté IP Adresse IP du serveur
user.unknown.hardware-uuid HardwareUtil.getUUID() UUID String Identifiant matériel unique
user.unknown.auth-mode ServerAuthManager.getAuthMode() String Mode d'auth (ONLINE/OFFLINE/etc.)

Données contextuelles (existantes)

Contexte Données
server Nom, max-players, listeners
universe Chemin, nombre joueurs, liste worlds
plugins Version et état de chaque plugin

8.4 Conditions de collecte

Les données Sentry sont collectées uniquement si :

  • Aucun early plugin avec transformer n'est chargé
  • Le flag --disable-sentry n'est pas passé
  • Une erreur se produit (beforeSend callback)

8.5 Filtrage des erreurs

Les erreurs provenant de plugins tiers sont filtrées avant envoi :

if (PluginClassLoader.isFromThirdPartyPlugin(event.getThrowable())) {
    return null; // Ne pas envoyer
}

8.6 Configuration Sentry

Paramètre Valeur
DSN https://...@sentry.hytale.com/4
Environnement release
Tags patchline
Server name Hostname du serveur

9. Fix NullPointerException CraftingManager

Fichier : com/hypixel/hytale/builtin/crafting/component/CraftingManager.java Ligne modifiée : 500

9.1 Modification

Avant :

if (!itemResourceType.id.equals(slotCraftingMaterial.getResourceTypeId())) continue;

Après :

if (!slotCraftingMaterial.getResourceTypeId().equals(itemResourceType.id)) continue;

9.2 Contexte du code

Cette ligne se trouve dans une méthode de comparaison des matériaux de craft, dans une boucle imbriquée :

// Ligne ~497-502
if (slotCraftingMaterial.getResourceTypeId() == null ||
    slotItemStack.getItem().getResourceTypes() == null) continue;

for (ItemResourceType itemResourceType : slotItemStack.getItem().getResourceTypes()) {
    if (!slotCraftingMaterial.getResourceTypeId().equals(itemResourceType.id)) continue;
    return true;
}

9.3 Analyse du problème

Variable Peut être null ? Raison
slotCraftingMaterial.getResourceTypeId() Non Vérifié 2 lignes avant
itemResourceType.id Oui Pas de vérification préalable

Problème : Appeler .equals() sur itemResourceType.id quand celui-ci est null lance un NullPointerException.

Solution : Inverser l'ordre de comparaison pour appeler .equals() sur la valeur garantie non-null.

9.4 Architecture du CraftingManager

Élément Description
Interface Component<EntityStore> (ECS)
Attaché à Joueurs
Rôle Gestion complète du crafting

9.5 Classes internes

Classe Rôle
CraftingJob Représente un travail de crafting en cours
BenchUpgradingJob Représente une amélioration d'établi

9.6 Flow de crafting

queueCraft()
    ↓
[Job ajouté à BlockingQueue]
    ↓
tick() [appelé chaque tick]
    ↓
[Vérifie matériaux disponibles] ← Fix NPE ici
    ↓
[Retire matériaux de l'inventaire]
    ↓
[Accumule temps]
    ↓
[Si terminé: giveOutput()]
    ↓
[Items craftés donnés au joueur]

9.7 Impact

Aspect Avant Après
Items avec ResourceType.id = null Crash du crafting Comparaison correcte
Stabilité serveur NPE possible Stable
Crafting avec ressources custom Potentiellement cassé Fonctionnel

Résumé des impacts

Performance

Changement Impact
BBR congestion control Amélioration significative du débit réseau
PMTU Discovery Réduction de la fragmentation, meilleur throughput
Timeout setup 10s Négligeable

Sécurité

Changement Impact
Clock skew 300s Plus permissif mais toujours sécurisé
Validation iat Protection contre tokens futurs
Échec auth → disconnect Fermeture d'une faille potentielle

Stabilité

Changement Impact
MISSING_ASSETS shutdown Erreur claire au lieu de comportement indéfini
Fix NPE CraftingManager Élimination d'un crash potentiel
Collisions latérales items Comportement physique cohérent

Télémétrie

Changement Impact
User data Sentry Meilleur debugging des erreurs
Hardware UUID Identification des problèmes matériels
Auth mode tracking Corrélation erreurs/mode d'auth

Fichiers modifiés

Fichier Insertions Suppressions
server/core/auth/JWTValidator.java +11 -6
server/core/HytaleServer.java +18 0
server/core/ShutdownReason.java +1 0
server/core/asset/AssetModule.java +5 0
server/core/io/handlers/SetupPacketHandler.java +1 -1
server/core/io/handlers/login/HandshakeHandler.java +1 -2
server/core/io/transport/QUICTransport.java +2 -1
server/core/modules/entity/item/ItemPhysicsSystem.java +11 -3
builtin/crafting/component/CraftingManager.java +1 -1
Total +55 -18