|
10 | 10 | import fr.xephi.authme.permission.PermissionsManager; |
11 | 11 | import fr.xephi.authme.permission.PlayerStatePermission; |
12 | 12 | import fr.xephi.authme.platform.ChatAdapter; |
| 13 | +import fr.xephi.authme.platform.EventRegistrationAdapter; |
13 | 14 | import fr.xephi.authme.platform.TeleportAdapter; |
14 | 15 | import fr.xephi.authme.process.Management; |
15 | 16 | import fr.xephi.authme.service.AntiBotService; |
|
19 | 20 | import fr.xephi.authme.service.ValidationService; |
20 | 21 | import fr.xephi.authme.settings.Settings; |
21 | 22 | import fr.xephi.authme.settings.SpawnLoader; |
| 23 | +import fr.xephi.authme.service.PremiumLoginVerifier; |
22 | 24 | import fr.xephi.authme.settings.properties.HooksSettings; |
| 25 | +import fr.xephi.authme.settings.properties.PremiumSettings; |
23 | 26 | import fr.xephi.authme.settings.properties.RegistrationSettings; |
24 | 27 | import fr.xephi.authme.settings.properties.RestrictionSettings; |
25 | 28 | import org.bukkit.ChatColor; |
|
59 | 62 | import org.bukkit.inventory.InventoryView; |
60 | 63 |
|
61 | 64 | import javax.inject.Inject; |
| 65 | +import java.nio.charset.StandardCharsets; |
62 | 66 | import java.util.Locale; |
63 | 67 | import java.util.Set; |
64 | 68 | import java.util.UUID; |
@@ -106,6 +110,10 @@ public class PlayerListener implements Listener { |
106 | 110 | private ChatAdapter chatAdapter; |
107 | 111 | @Inject |
108 | 112 | private TeleportAdapter teleportAdapter; |
| 113 | + @Inject |
| 114 | + private PremiumLoginVerifier premiumLoginVerifier; |
| 115 | + @Inject |
| 116 | + private EventRegistrationAdapter eventRegistrationAdapter; |
109 | 117 |
|
110 | 118 | // Lowest priority to apply fast protection checks |
111 | 119 | @EventHandler(priority = EventPriority.LOWEST) |
@@ -164,6 +172,7 @@ public void onAsyncPlayerPreLoginEventHighest(AsyncPlayerPreLoginEvent event) { |
164 | 172 | onJoinVerifier.checkNameCasing(name, auth); |
165 | 173 | final String ip = event.getAddress().getHostAddress(); |
166 | 174 | onJoinVerifier.checkPlayerCountry(name, ip, isAuthAvailable); |
| 175 | + normalizePremiumUuidIfNeeded(event, name, auth); |
167 | 176 | } catch (FailedVerificationException e) { |
168 | 177 | event.setKickMessage(messages.retrieveSingle(name, e.getReason(), e.getArgs())); |
169 | 178 | event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); |
@@ -247,6 +256,36 @@ public void onPlayerQuit(PlayerQuitEvent event) { |
247 | 256 | management.performQuit(player); |
248 | 257 | } |
249 | 258 |
|
| 259 | + /** |
| 260 | + * When the backend is in offline-mode but the proxy has forwarded a Mojang UUID (v4) for a |
| 261 | + * premium player, replaces the event UUID with the deterministic offline UUID so that all |
| 262 | + * plugins — permissions, inventories, etc. — consistently use the same identifier. |
| 263 | + * |
| 264 | + * The Mojang UUID is saved in {@link PremiumLoginVerifier} so that |
| 265 | + * {@code AsynchronousJoin#canBypassWithPremium} can still verify premium identity via the |
| 266 | + * existing {@code getVerifiedUuid} path without requiring the PacketEvents handshake. |
| 267 | + * |
| 268 | + * UUID replacement is platform-specific: Paper/Folia implement it via |
| 269 | + * {@link EventRegistrationAdapter#normalizePreLoginUuid}; Spigot silently skips it. |
| 270 | + */ |
| 271 | + private void normalizePremiumUuidIfNeeded(AsyncPlayerPreLoginEvent event, String name, PlayerAuth auth) { |
| 272 | + if (event.getUniqueId().version() != 4 |
| 273 | + || !Boolean.TRUE.equals(settings.getProperty(HooksSettings.BUNGEECORD)) |
| 274 | + || !Boolean.TRUE.equals(settings.getProperty(PremiumSettings.ENABLE_PREMIUM))) { |
| 275 | + return; |
| 276 | + } |
| 277 | + // Skip if the player registered with a v4 UUID (truly online-mode backend or equivalent). |
| 278 | + if (auth == null || (auth.getUuid() != null && auth.getUuid().version() == 4)) { |
| 279 | + return; |
| 280 | + } |
| 281 | + UUID mojangUuid = event.getUniqueId(); |
| 282 | + UUID offlineUuid = UUID.nameUUIDFromBytes( |
| 283 | + ("OfflinePlayer:" + event.getName()).getBytes(StandardCharsets.UTF_8)); |
| 284 | + // Store the Mojang UUID so canBypassWithPremium can verify it after the UUID is replaced. |
| 285 | + premiumLoginVerifier.storeVerified(name, mojangUuid); |
| 286 | + eventRegistrationAdapter.normalizePreLoginUuid(event, offlineUuid); |
| 287 | + } |
| 288 | + |
250 | 289 | private void saveStateBeforeQuit(Player player) { |
251 | 290 | Set<EnderPearlRestoreData> pearls = player.getServer().getWorlds().stream() |
252 | 291 | .flatMap(world -> world.getEntitiesByClass(EnderPearl.class).stream()) |
|
0 commit comments