Skip to content

Commit

Permalink
Merge pull request #274 from Condordito/fix/xskull-reflection
Browse files Browse the repository at this point in the history
[XSkull] Legacy support
  • Loading branch information
CryptoMorin authored Jun 22, 2024
2 parents b633d00 + 9650076 commit 01fb76f
Show file tree
Hide file tree
Showing 3 changed files with 8 additions and 144 deletions.
28 changes: 6 additions & 22 deletions src/main/java/com/cryptomorin/xseries/profiles/ProfilesCore.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,12 @@ public final class ProfilesCore {

public static final Map<String, Object> UserCache_profilesByName;
public static final Map<UUID, Object> UserCache_profilesByUUID;
public static final Deque<GameProfile> UserCache_gameProfiles;

public static final MethodHandle
FILL_PROFILE_PROPERTIES, GET_PROFILE_BY_NAME, GET_PROFILE_BY_UUID, CACHE_PROFILE,
CRAFT_META_SKULL_PROFILE_GETTER, CRAFT_META_SKULL_PROFILE_SETTER,
CRAFT_SKULL_PROFILE_SETTER, CRAFT_SKULL_PROFILE_GETTER,
PROPERTY_GET_VALUE,
UserCache_getNextOperation,
UserCacheEntry_CTOR, UserCacheEntry_getProfile, UserCacheEntry_getExpirationDate, UserCacheEntry_setLastAccess;
PROPERTY_GET_VALUE, UserCacheEntry_getProfile;

/**
* In v1.20.2, Mojang switched to {@code record} class types for their {@link Property} class.
Expand Down Expand Up @@ -158,36 +155,23 @@ public final class ProfilesCore {
CRAFT_SKULL_PROFILE_SETTER = craftProfile.setter().unreflect();
CRAFT_SKULL_PROFILE_GETTER = craftProfile.getter().unreflect();

// noinspection MethodMayBeStatic
UserCache_getNextOperation = GameProfileCache.method("private long getNextOperation();")
.map(MinecraftMapping.OBFUSCATED, v(21, "e").v(16, "d").orElse("d")).unreflect();

MinecraftClassHandle UserCacheEntry = GameProfileCache
.inner("private static class GameProfileInfo {}")
.map(MinecraftMapping.SPIGOT, "UserCacheEntry");
UserCacheEntry_CTOR = UserCacheEntry.constructor("private UserCacheEntry(GameProfile gameprofile, Date date);")
.unreflect();

UserCacheEntry_getProfile = UserCacheEntry.method("public GameProfile getProfile();")
.map(MinecraftMapping.OBFUSCATED, "a").unreflect();
UserCacheEntry_getExpirationDate = UserCacheEntry.method("public Date getExpirationDate();")
.map(MinecraftMapping.OBFUSCATED, "b").unreflect();
UserCacheEntry_setLastAccess = UserCacheEntry.method("public void setLastAccess(long i);")
.map(MinecraftMapping.OBFUSCATED, "a").reflectOrNull();
.map(MinecraftMapping.OBFUSCATED, "a").makeAccessible()
.unreflect();

try {
// private final Map<String, UserCache.UserCacheEntry> profilesByName = Maps.newConcurrentMap();
UserCache_profilesByName = (Map<String, Object>) GameProfileCache.field("private final Map<String, UserCache.UserCacheEntry> profilesByName;")
.getter().map(MinecraftMapping.OBFUSCATED, v(21, "e").v(16, "c").orElse("d"))
.getter().map(MinecraftMapping.OBFUSCATED, v(17, "e").v(16, 2, "c").v(9, "d").orElse("c"))
.reflect().invoke(userCache);
// private final Map<UUID, UserCache.UserCacheEntry> profilesByUUID = Maps.newConcurrentMap();
UserCache_profilesByUUID = (Map<UUID, Object>) GameProfileCache.field("private final Map<UUID, UserCache.UserCacheEntry> profilesByUUID;")
.getter().map(MinecraftMapping.OBFUSCATED, v(21, "f").v(16, "d").orElse("e"))
.getter().map(MinecraftMapping.OBFUSCATED, v(17, "f").v(16, 2, "d").v(9, "e").orElse("d"))
.reflect().invoke(userCache);

// private final Deque<GameProfile> f = new LinkedBlockingDeque(); Removed in v1.16
MethodHandle deque = GameProfileCache.field("private final Deque<GameProfile> f;")
.getter().reflectOrNull();
UserCache_gameProfiles = deque == null ? null : (Deque<GameProfile>) deque.invoke(userCache);
} catch (Throwable e) {
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

@ApiStatus.Internal
public final class MojangAPI {
private static final MojangProfileCache MOJANG_PROFILE_CACHE = ProfilesCore.NULLABILITY_RECORD_UPDATE ?
private static final MojangProfileCache MOJANG_PROFILE_CACHE = !ProfilesCore.NULLABILITY_RECORD_UPDATE ?
new MojangProfileCache.GameProfileCache(ProfilesCore.YggdrasilMinecraftSessionService_insecureProfiles) :
new MojangProfileCache.ProfileResultCache(ProfilesCore.YggdrasilMinecraftSessionService_insecureProfiles);
/**
Expand Down Expand Up @@ -125,44 +125,19 @@ public static Optional<GameProfile> profileFromUsername(String username) {
public static Optional<GameProfile> profileFromUsername0(String username) throws Throwable {
String normalized = username.toLowerCase(Locale.ROOT);
Object usercache_usercacheentry = ProfilesCore.UserCache_profilesByName.get(normalized);
boolean flag = false;

if (usercache_usercacheentry != null && (new Date()).getTime() >=
((Date) ProfilesCore.UserCacheEntry_getExpirationDate.invoke(usercache_usercacheentry)).getTime()) {
GameProfile gameProfile = (GameProfile) ProfilesCore.UserCacheEntry_getProfile.invoke(usercache_usercacheentry);
ProfilesCore.UserCache_profilesByName.remove(gameProfile.getName().toLowerCase(Locale.ROOT));
ProfilesCore.UserCache_profilesByUUID.remove(gameProfile.getId());
flag = true;
usercache_usercacheentry = null;
}

Optional<GameProfile> optional;

if (usercache_usercacheentry != null) {
if (ProfilesCore.UserCacheEntry_setLastAccess != null) {
// usercache_usercacheentry.setLastAccess(this.getNextOperation());
long nextOperation = (long) ProfilesCore.UserCache_getNextOperation.invoke(ProfilesCore.USER_CACHE);
ProfilesCore.UserCacheEntry_setLastAccess.invoke(usercache_usercacheentry, nextOperation);
}
optional = Optional.of((GameProfile) ProfilesCore.UserCacheEntry_getProfile.invoke(usercache_usercacheentry));
} else {
// optional = lookupGameProfile(this.profileRepository, username); // CraftBukkit - use correct case for offline players
UUID realUUID = PlayerUUIDs.getRealUUIDOfPlayer(username);
if (realUUID == null) return Optional.empty();

optional = Optional.of(PlayerProfiles.signXSeries(new GameProfile(realUUID, username)));
// this.add((GameProfile) optional.get());
cacheProfile(optional.get());
flag = false;
}

// Should we implement this too?
// Maybe we can even make our own method to save real UUIDs too, but that'd need a lot of work,
// and it wouldn't be reliable if the config option is disabled.
// if (flag && !org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) { // Spigot - skip saving if disabled
// YggdrasilGameProfileRepository.save();
// }

return optional;
}

Expand Down Expand Up @@ -358,73 +333,4 @@ public static GameProfile fetchProfile(@Nonnull final GameProfile profile) {
return profile;
}

/**
* Fetches additional properties for the given {@link GameProfile} if possible (like the texture).
*
* @param profile The {@link GameProfile} for which properties are to be fetched.
* @return The updated {@link GameProfile} with fetched properties, sanitized for consistency.
* @throws IllegalArgumentException if a player with the specified profile properties (username and UUID) doesn't exist.
*/
@Nonnull
private static GameProfile fetchProfileOriginal(@Nonnull GameProfile profile) {
GameProfile original = profile;
if (!ProfilesCore.NULLABILITY_RECORD_UPDATE) {
GameProfile cached = null; // INSECURE_PROFILES.getIfPresent(profile.getId());
GameProfile newProfile;

try {
/* This URL endpoint still works as of 1.20.6
* URL url = HttpAuthenticationService.constantURL("https://sessionserver.mojang.com/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(profile.getId()));
* url = HttpAuthenticationService.concatenateURL(url, "unsigned=" + !requireSecure);
* GameProfile result = new GameProfile(response.getId(), response.getName());
* result.getProperties().putAll(response.getProperties());
* profile.getProperties().putAll(response.getProperties());
*/
// These results are not cached unlike the newer fetchProfile()
if (cached == null) {
newProfile = (GameProfile) ProfilesCore.FILL_PROFILE_PROPERTIES
.invoke(ProfilesCore.MINECRAFT_SESSION_SERVICE, profile, REQUIRE_SECURE_PROFILES);
if (profile == newProfile) {
// Returns the same instance if any error occurs.
throw new UnknownPlayerException("Player with the given properties not found: " + profile);
}
} else {
newProfile = (profile = cached);
}
ProfilesCore.debug("Filled properties: {} -> {}", original, newProfile);
} catch (UnknownPlayerException ex) {
throw ex;
} catch (Throwable throwable) {
throw new RuntimeException("Unable to fetch profile properties: " + profile, throwable);
}
} else {
UUID realUUID;
if (profile.getName().equals(PlayerProfiles.DEFAULT_PROFILE_NAME)) {
realUUID = profile.getId();
} else {
realUUID = PlayerUUIDs.getRealUUIDOfPlayer(profile.getName(), profile.getId());
if (realUUID == null) {
throw new UnknownPlayerException("Player with the given properties not found: " + profile);
}
}

// Implemented by YggdrasilMinecraftSessionService
// fetchProfile(UUID profileId, boolean requireSecure) -> fetchProfileUncached(UUID profileId, boolean requireSecure)
// This cache expireAfterWrite every 6 hours.
com.mojang.authlib.yggdrasil.ProfileResult result = ((MinecraftSessionService) ProfilesCore.MINECRAFT_SESSION_SERVICE)
.fetchProfile(realUUID, REQUIRE_SECURE_PROFILES);
if (result != null) {
profile = result.profile();
ProfilesCore.debug("Yggdrasil provided profile is {} with actions {} for {}", result.profile(), result.actions(), profile);
} else {
ProfilesCore.debug("Yggdrasil provided profile is null with actions for {}", profile);
throw new UnknownPlayerException("fetchProfile could not find " + realUUID + " from " + original);
}
}

profile = PlayerProfiles.sanitizeProfile(profile);
PlayerProfiles.signXSeries(profile);
cacheProfile(profile);
return profile;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,7 @@ static <C extends Collection<Profileable>> CompletableFuture<C> prepare(
String username = null;
if (profileable instanceof UsernameProfileable) {
username = ((UsernameProfileable) profileable).username;
} else if (profileable instanceof PlayerProfileable) {
username = ((PlayerProfileable) profileable).player.getName();
} else if (profileable instanceof OfflinePlayerProfileable) {
} else if (profileable instanceof OfflinePlayerProfileable) {
username = ((OfflinePlayerProfileable) profileable).player.getName();
} else if (profileable instanceof StringProfileable) {
if (((StringProfileable) profileable).determineType().type == ProfileInputType.USERNAME) {
Expand Down Expand Up @@ -142,15 +140,6 @@ static Profileable of(GameProfile profile) {
return new GameProfileProfileable(profile);
}

/**
* Sets the skull texture based on the specified player.
*
* @param player The player to generate the {@link GameProfile}.
*/
static Profileable of(Player player) {
return new PlayerProfileable(player);
}

/**
* Sets the skull texture based on the specified offline player.
* The profile lookup will depend on whether the server is running in online mode.
Expand Down Expand Up @@ -245,21 +234,6 @@ public GameProfile getProfile0() {
}
}

final class PlayerProfileable extends AbstractProfileable {
private final Player player;

public PlayerProfileable(Player player) {this.player = Objects.requireNonNull(player);}

@Override
public GameProfile getProfile0() {
// Why are we using the username instead of getting the cached UUID like profile(player.getUniqueId())?
// If it's about online/offline mode support why should we have a separate method for this instead of
// letting profile(OfflinePlayer) to take care of it?
if (PlayerUUIDs.isOnlineMode()) return new UUIDProfileable(player.getUniqueId()).getProfile();
return new UsernameProfileable(player.getName()).getProfile();
}
}

final class OfflinePlayerProfileable extends AbstractProfileable {
private final OfflinePlayer player;

Expand Down

0 comments on commit 01fb76f

Please sign in to comment.