File Details
arcadia-lib-1.2.11
- R
- May 19, 2026
- 2.57 MB
- 1.4K
- 1.21.1
- NeoForge
File Name
arcadia-lib-1.2.11.jar
Supported Versions
- 1.21.1
Curse Maven Snippet
## [1.2.11] - 2026-05-19 (latest)
### Security
- **C2SDashboardAction payload validation + server-side OPEN_TAB / SWITCH_TAB permission re-check** — Client-supplied `payload` strings were forwarded as-is to `ArcadiaModRegistry.executeServerAction` (e.g. `"pets.summon:" + payload`), letting a crafted packet inject a second `:` and shift the action dispatch. `OPEN_TAB` and the `ext:` form of `SWITCH_TAB` also opened any tab the client asked for, ignoring the card's `permissionNode` — tab-level gating only existed in the client UI. Payloads are now length-capped at 128 chars and matched against a strict whitelist regex; the server re-resolves the target card and rejects any opening attempt that fails `PermissionService.hasPermissionStrict`.
- **`SafeCommandUtil` allow-list + new `runTrusted` template entry point** — `runAsServer(server, command)` previously executed any string at operator level with no sanitization. It now rejects anything outside a narrow prefix allow-list (`tp`, `give`, `playsound`, `particle`, `title`, `tellraw`, `execute`, `effect`, `spawnpoint`, `gamemode`, `say`). A new `runTrusted(server, template, args...)` overload formats a hard-coded template with allow-listed argument tokens, giving downstream mods a safe path when one of them needs to interpolate user input.
- **`PermissionBackend` fail-closed on dedicated servers** — The lenient `NOOP` backend (returning `true` for every node) was the runtime default when LuckPerms was missing. On a dedicated box that meant every player was implicitly granted every node until init finished. New `PermissionBackend.DENY` is the default on dedicated servers; `PermissionBackend.PERMISSIVE` is the singleplayer fallback. The legacy `NOOP` alias now points at `DENY`. `PermissionService.init(backend, isDedicated)` picks the right default automatically.
- **`DebugMode` is now UUID-based** — Previously matched `player.getName().getString()` against `Set.of("SiriusT", "Dev")`. On offline-mode / cracked servers that's spoofable: any client naming themselves `SiriusT` walked in as ADMIN if someone accidentally flipped `ENABLED` true. The set is now `Set<UUID>` and ships empty by default.
- **`NbtSerializer.deserializeTag` size-bounded** — `NbtAccounter.unlimitedHeap()` let a crafted 1 MiB compressed blob expand to gigabytes and OOM the server. Now bounded to a 2 MiB heap budget and rejects Base64 inputs over 1 MiB before decoding.
- **`DashboardMenu.stillValid` ties container liveness to `player.containerMenu == this`** — Returning `true` unconditionally let the menu survive teleports, deaths and dimension changes; any stale C2S packet was still dispatched to the tab handler.
- **`DatabaseConfig` defaults the username / password to empty strings + startup refuses an empty username** — `arcadia_prestige` was the literal default for both fields. If a deployment accidentally relied on that default it became a credential leak. Empty defaults fail-fast at startup with a clear error.
### Fixed
- **`SchedulerService` race on async-scheduled tasks** — `nextId` was a plain `int++` and `currentTick` a plain `long`. An async DB callback calling `delayed()` or `repeating()` raced the tick thread into ID collisions and stale `nextRun` offsets. Converted to `AtomicInteger` / `AtomicLong`. `cancelAll()` no longer resets the ID counter (avoids aliasing stale IDs across sessions).
- **`AchievementManager` duplicate coin reward on crash** — Reward was granted before the DB write was confirmed. A crash between the two left `unlocked=true` in memory but unwritten, so the next session reloaded progress >= target and re-granted the reward. Unlock now persists first and the reward fires only after `saveDbSync` returns true; failed writes withhold the reward and log.
- **`DatabaseManager` main-thread freeze on executor restart window** — When the primary pool was shut down and not yet rebuilt, the previous fallback ran the task inline on the caller — which during `PlayerLoggedInEvent` is the main server thread. Added a long-lived single-thread `FALLBACK_EXECUTOR` that catches that gap. JDBC URLs now bracket IPv6 host literals (`::1` → `[::1]`).
- **`PlayerDataHandler.canClaimDaily` reads from the DB on the dedicated path** — UI-side cooldown check used the local cache; another instance advancing `last_claim` after a cross-server claim made the gate flicker back to "ready". The strict check now mirrors `claimDaily` and hits the DB.
- **`TeleportManager` cooldown structure + tick lookup** — Disconnect cleanup was an O(N) scan of a flat `"uuid:action"` keyed map; now `Map<UUID, Map<String, Long>>` with O(1) removal. The per-tick player resolver now uses `PlayerManager.getPlayer` instead of `ServerLifecycleHooks → PlayerList`.
### Performance
- **`ArcadiaHubScreen` no longer rebuilds its row layout every frame** — A fresh `TreeMap` plus row `ArrayList`s were allocated inside `render()` 60 times per second despite the layout never changing while the screen is open. Layout is now computed once in `init()` and read directly by `render()`.
- **`CreativeSearchHandler` caches the search `EditBox`** — Iterating `screen.children()` on every post-render frame was wasted work; cached via `WeakReference` and invalidated on screen open.
### Added
- **`SanctionRepository` — persistent, cross-server moderation store** — Backs the staff mute system with an `arcadia_sanctions` MySQL table (target, kind, expiry, reason, issued_by, revoked). A mute issued on server A is now in force on servers B and C and survives a restart. Hot-path checks still read from an in-memory cache hydrated on player join via `StaffActions.onPlayerJoin`. `Kind` is an enum so ban / warn / kick can extend the same table later without a schema migration.
- **`AuditLog` — append-only staff action trail** — Every mute, unmute, day-advance and future ban/warn/kick is written to `arcadia_audit` (timestamp, actor, target, action, detail). `AuditLog.getLast(target, limit)` returns the most recent entries for a player — ready for a future `/arcadia history` view.
- **`/arcadia reload [module]`** — Hot-reload of lib-owned configs without restarting the server. Modules: `all`, `database`, `permissions`, `staff`, `economy`. Gated behind `StaffRole.ADMIN` (silent at command-discovery time, explicit denial only on actual invocation so it doesn't spam tab-completion).
### Sécurité
- **Validation des payloads `C2SDashboardAction` + re-vérif serveur de la permission sur `OPEN_TAB` / `SWITCH_TAB`** — Les `payload` clients étaient transmis tels quels à `ArcadiaModRegistry.executeServerAction` (ex. `"pets.summon:" + payload`) : un paquet forgé pouvait injecter un second `:` et déplacer le dispatch d'action. `OPEN_TAB` et la forme `ext:` de `SWITCH_TAB` ouvraient aussi n'importe quel onglet demandé par le client en ignorant le `permissionNode` de la carte — la gate ne vivait qu'en UI. Les payloads sont maintenant plafonnés à 128 caractères et filtrés par regex stricte ; le serveur re-résout la carte cible et rejette toute ouverture qui ne passe pas `PermissionService.hasPermissionStrict`.
- **Allow-list `SafeCommandUtil` + nouveau point d'entrée `runTrusted`** — `runAsServer(server, command)` exécutait n'importe quelle chaîne au niveau opérateur sans sanitization. Il refuse maintenant tout ce qui sort d'une liste blanche étroite (`tp`, `give`, `playsound`, `particle`, `title`, `tellraw`, `execute`, `effect`, `spawnpoint`, `gamemode`, `say`). Une nouvelle surcharge `runTrusted(server, template, args...)` formate un template figé avec des arguments validés contre une whitelist — voie sûre pour les mods qui doivent interpoler de l'input utilisateur.
- **`PermissionBackend` fail-closed sur serveur dédié** — Le backend permissif `NOOP` (qui retournait `true` partout) servait de défaut runtime tant que LuckPerms n'avait pas init. Sur un dédié ça signifie que chaque joueur disposait implicitement de chaque node pendant la fenêtre. Nouveau `PermissionBackend.DENY` comme défaut sur dédié ; `PermissionBackend.PERMISSIVE` reste le fallback solo. L'alias `NOOP` historique pointe désormais sur `DENY`. `PermissionService.init(backend, isDedicated)` choisit automatiquement le bon défaut.
- **`DebugMode` passe sur authentification par UUID** — Avant : match du nom Mojang contre `Set.of("SiriusT", "Dev")`. Sur un serveur offline / cracked c'est spoofable : n'importe quel client nommé `SiriusT` rentrait ADMIN si quelqu'un flippait `ENABLED` à true. Le set est maintenant `Set<UUID>` et vide par défaut.
- **`NbtSerializer.deserializeTag` borné en taille** — `NbtAccounter.unlimitedHeap()` laissait un blob compressé d'1 MiB se décompresser en gigaoctets et faire OOM le serveur. Borné maintenant à 2 MiB de budget heap et rejet des entrées Base64 supérieures à 1 MiB avant décodage.
- **`DashboardMenu.stillValid` lie la validité du conteneur à `player.containerMenu == this`** — Retourner `true` inconditionnellement laissait le menu survivre aux téléportations, morts et changements de dimension ; tout paquet C2S stale était quand même dispatché au handler d'onglet.
- **`DatabaseConfig` met user / password à chaîne vide par défaut + refus au démarrage si username vide** — `arcadia_prestige` était la valeur littérale par défaut des deux champs. Un déploiement qui s'appuyait sur ce défaut devenait un leak de credential. Défauts vides → fail-fast au démarrage avec erreur claire.
### Correctifs
- **Race `SchedulerService` sur les tâches programmées en async** — `nextId` était un `int++` brut et `currentTick` un `long` brut. Un callback DB async appelant `delayed()` ou `repeating()` se mettait en concurrence avec le tick thread et provoquait des collisions d'ID + des offsets `nextRun` stales. Passés en `AtomicInteger` / `AtomicLong`. `cancelAll()` ne reset plus le compteur d'ID (évite l'aliasing entre sessions).
- **Doublon de récompense `AchievementManager` au crash** — La récompense était accordée avant la confirmation d'écriture en base. Un crash entre les deux laissait `unlocked=true` en mémoire mais non écrit, donc la session suivante rechargeait la progression >= target et re-versait la récompense. L'unlock persiste maintenant d'abord et la récompense ne tombe qu'après le retour `true` de `saveDbSync` ; les écritures échouées retiennent la récompense et logguent.
- **Gel du main thread `DatabaseManager` pendant la fenêtre de redémarrage de l'executor** — Quand le pool primaire était shutdown et pas encore reconstruit, le fallback précédent exécutait la tâche inline sur l'appelant — c'est-à-dire le main thread serveur pendant `PlayerLoggedInEvent`. Ajout d'un `FALLBACK_EXECUTOR` single-thread longue durée pour boucher ce trou. Les URLs JDBC bracketent désormais les hôtes IPv6 littéraux (`::1` → `[::1]`).
- **`PlayerDataHandler.canClaimDaily` lit depuis la DB sur le chemin dédié** — Le check de cooldown côté UI tapait dans le cache local ; une autre instance qui avançait `last_claim` après un claim cross-serveur faisait clignoter la gate à "prêt". Le check strict reflète maintenant `claimDaily` et tape la base.
- **Structure des cooldowns `TeleportManager` + lookup tick** — Le cleanup de déconnexion était un scan O(N) d'un map plat clé `"uuid:action"` ; maintenant `Map<UUID, Map<String, Long>>` avec retrait O(1). Le résolveur de joueur par tick passe par `PlayerManager.getPlayer` au lieu de `ServerLifecycleHooks → PlayerList`.
### Performance
- **`ArcadiaHubScreen` ne reconstruit plus son layout par ligne à chaque frame** — Un nouveau `TreeMap` + des `ArrayList` par ligne étaient alloués dans `render()` 60 fois par seconde alors que le layout ne bouge pas tant que l'écran est ouvert. Layout calculé une fois dans `init()` et lu directement par `render()`.
- **`CreativeSearchHandler` cache la search `EditBox`** — Itérer `screen.children()` à chaque frame post-render était du travail jeté ; cache par `WeakReference` invalidé à l'ouverture d'écran.
### Ajouts
- **`SanctionRepository` — store de modération persistant cross-serveur** — Backe le système de mutes staff avec une table MySQL `arcadia_sanctions` (target, kind, expiry, reason, issued_by, revoked). Un mute posé sur le serveur A s'applique sur B et C et survit au restart. Les checks hot-path lisent toujours un cache mémoire hydraté au join via `StaffActions.onPlayerJoin`. `Kind` est un enum pour que ban / warn / kick étendent la même table sans migration de schema.
- **`AuditLog` — trail append-only des actions staff** — Chaque mute, unmute, day-advance et futur ban/warn/kick est écrit dans `arcadia_audit` (timestamp, actor, target, action, detail). `AuditLog.getLast(target, limit)` retourne les entrées les plus récentes pour un joueur — prêt pour une future vue `/arcadia history`.
- **`/arcadia reload [module]`** — Hot-reload des configs lib sans restart serveur. Modules : `all`, `database`, `permissions`, `staff`, `economy`. Gated derrière `StaffRole.ADMIN` (silencieux à la découverte de commande, refus explicite seulement à l'invocation réelle pour ne pas spammer la tab-completion).

