diff --git a/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/NachoAuthenticationService.java b/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/NachoAuthenticationService.java new file mode 100644 index 000000000..b91d33bc1 --- /dev/null +++ b/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/NachoAuthenticationService.java @@ -0,0 +1,230 @@ +package com.github.sadcenter.auth; + +import com.github.sadcenter.auth.profile.NachoGameProfileRepository; +import com.github.sadcenter.auth.serializer.GameProfileSerializer; +import com.github.sadcenter.auth.serializer.UUIDSerializer; +import com.github.sadcenter.auth.session.NachoSessionService; +import com.github.sadcenter.auth.storage.CachedProfile; +import com.github.sadcenter.auth.storage.ProfileCache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.mojang.authlib.*; +import com.mojang.authlib.minecraft.MinecraftSessionService; +import com.mojang.authlib.properties.PropertyMap; +import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; +import com.mojang.util.UUIDTypeAdapter; +import me.elier.nachospigot.config.NachoConfig; +import net.minecraft.server.EntityPlayer; +import net.minecraft.server.MinecraftServer; +import org.apache.commons.io.Charsets; +import org.apache.commons.io.IOUtils; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; + +public class NachoAuthenticationService implements AuthenticationService { + + public static final Gson GSON = new GsonBuilder() + .registerTypeAdapter(GameProfile.class, new GameProfileSerializer()) + .registerTypeAdapter(PropertyMap.class, new PropertyMap.Serializer()) + .registerTypeAdapter(UUID.class, new UUIDSerializer()) + .create(); + public static final Executor EXECUTOR = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder() + .setNameFormat("Authenticator Thread - %1$d") + .setUncaughtExceptionHandler((t, e) -> e.printStackTrace()) + .build()); + private static final String API = "https://api.ashcon.app/mojang/v2/user/"; + private static final String BACKUP_API = "https://sessionserver.mojang.com/session/minecraft/profile/"; + private static final String UUID_API = "https://api.ashcon.app/mojang/v2/uuid/"; + private static final String BACKUP_UUID_API = "https://api.mojang.com/users/profiles/minecraft/"; + + private final LoadingCache> gameProfileCache = CacheBuilder.newBuilder() + .expireAfterWrite(3, TimeUnit.HOURS) + .maximumSize(5000) + .build(new CacheLoader>() { + @Override + public CompletableFuture load(String key) { + EntityPlayer player = MinecraftServer.getServer().getPlayerList().getPlayer(key); + CachedProfile cachedProfile = profileCache.getCachedProfile(key); + CompletableFuture gameProfile = player == null ? getProfileFromApi(key) : CompletableFuture.completedFuture(player.getProfile()); + + if (cachedProfile == null) { + gameProfile + .thenAccept(profile -> profileCache.putAndSave(profile.getName(), CachedProfile.fromGameProfile(profile))); + } else { + GameProfile cachedGameProfile = cachedProfile.toGameProfile(key); + gameProfile.thenAccept(profile -> { + if (!profile.equals(cachedGameProfile)) { + profileCache.putAndSave(profile.getName(), CachedProfile.fromGameProfile(profile)); + gameProfileCache.put(profile.getName(), gameProfile); + } + }); + + return CompletableFuture.completedFuture(cachedGameProfile); + } + + return gameProfile; + } + }); + private final ProfileCache profileCache = new ProfileCache(); + private final YggdrasilAuthenticationService yggdrasilAuthenticationService; + + public NachoAuthenticationService(YggdrasilAuthenticationService yggdrasilAuthenticationService) { + this.yggdrasilAuthenticationService = yggdrasilAuthenticationService; + } + + @Override + public GameProfileRepository createProfileRepository() { + return new NachoGameProfileRepository(this); + } + + @Override + public UserAuthentication createUserAuthentication(Agent agent) { + return this.yggdrasilAuthenticationService.createUserAuthentication(agent); + } + + @Override + public MinecraftSessionService createMinecraftSessionService() { + return new NachoSessionService(this); + } + + public CompletableFuture getProfile(String name) { + try { + return this.gameProfileCache.get(name); + } catch (ExecutionException e) { + e.printStackTrace(); + } + + return null; + } + + public GameProfile getPresentProfile(String name) { + CompletableFuture profile = this.gameProfileCache.getIfPresent(name); + return profile == null ? null : + profile.getNow(null); + } + + private CompletableFuture getProfileFromApi(String name) { + CompletableFuture profileFuture = this.getProfileFromApi(name, NachoConfig.texturesMojangPriority); + if (NachoConfig.backups) { + return profileFuture + .exceptionallyComposeAsync(throwable -> this.getProfileFromApi(name, !NachoConfig.texturesMojangPriority), EXECUTOR); + } + return profileFuture; + } + + private CompletableFuture getProfileFromApi(String name, boolean mojang) { + if (mojang) { + CompletableFuture uuidFuture = this.getUuidFromApi(name, true); + return uuidFuture + .thenCompose(uuid -> this.get(BACKUP_API + UUIDTypeAdapter.fromUUID(uuid), GameProfile.class)); + } else { + return this.get(API + name, GameProfile.class); + } + } + + public CompletableFuture getUuidFromApi(String name) { + CompletableFuture uuidFuture = this.getUuidFromApi(name, NachoConfig.uuidMojangPriority); + if (NachoConfig.backups) { + return uuidFuture + .exceptionallyCompose(throwable -> this.getUuidFromApi(name, !NachoConfig.uuidMojangPriority)); + } + return uuidFuture; + } + + private CompletableFuture getUuidFromApi(String name, boolean mojang) { + return mojang ? this.get(BACKUP_UUID_API + name, UUID.class) : + this.get(UUID_API + name, UUID.class); + } + + public CompletableFuture get(String url, Class type) { + return CompletableFuture.supplyAsync(() -> { + String result = null; + try { + result = this.fetchGet(url(url)); + } catch (Exception exception) { + if (!(exception instanceof FileNotFoundException)) { + throw new CompletionException(exception); + } + } + return GSON.fromJson(result, type); + }, EXECUTOR); + } + + public CompletableFuture post(String url, Object content, Class type) { + return CompletableFuture.supplyAsync(() -> { + try { + return GSON.fromJson(this.fetchPost(url(url), GSON.toJson(content)), type); + } catch (IOException exception) { + if (!(exception instanceof FileNotFoundException)) { + exception.printStackTrace(); + } + } + + return null; + }, EXECUTOR); + } + + public String fetchGet(URL url) throws IOException { + return IOUtils.toString(url, StandardCharsets.UTF_8); + } + + public String fetchPost(URL url, String content) throws IOException { + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + + connection.setRequestMethod("application/json"); + connection.setDoOutput(true); + + try (OutputStream outputStream = connection.getOutputStream()) { + IOUtils.write(content.getBytes(Charsets.UTF_8), outputStream); + } + + try (InputStream inputStream = connection.getInputStream()) { + return IOUtils.toString(inputStream); + } + } + + public void tick() { + this.profileCache.setTicked(false); + } + + public static URL url(String url) { + try { + return new URL(url); + } catch (MalformedURLException e) { + e.printStackTrace(); + return null; + } + } + + public static String json(T source) { + return GSON.toJson(source); + } + + public static String query(Map map) { + StringBuilder builder = new StringBuilder(); + AtomicBoolean firstPair = new AtomicBoolean(true); + + map.forEach((a, b) -> { + builder.append((firstPair.get() ? "?" : "&") + a + "=" + b); + + firstPair.set(false); + }); + + return builder.toString(); + } +} diff --git a/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/profile/NachoGameProfileRepository.java b/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/profile/NachoGameProfileRepository.java new file mode 100644 index 000000000..56cd9147d --- /dev/null +++ b/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/profile/NachoGameProfileRepository.java @@ -0,0 +1,34 @@ +package com.github.sadcenter.auth.profile; + +import com.github.sadcenter.auth.NachoAuthenticationService; +import com.mojang.authlib.Agent; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.GameProfileRepository; +import com.mojang.authlib.ProfileLookupCallback; + +public class NachoGameProfileRepository implements GameProfileRepository { + + private final NachoAuthenticationService authenticator; + + public NachoGameProfileRepository(NachoAuthenticationService authenticator) { + this.authenticator = authenticator; + } + + @Override + public void findProfilesByNames(String[] names, Agent agent, ProfileLookupCallback callback) { + for (String name : names) { + if (name == null || name.isEmpty()) { + continue; + } + + GameProfile gameProfile = new GameProfile(null, name); + this.authenticator.getUuidFromApi(name) + .thenApply(uuid -> new GameProfile(uuid, name)) + .thenAccept(callback::onProfileLookupSucceeded) + .exceptionally(throwable -> { + callback.onProfileLookupFailed(gameProfile, (Exception) throwable); + return null; + }); + } + } +} diff --git a/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/response/HasJoinedServerResponse.java b/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/response/HasJoinedServerResponse.java new file mode 100644 index 000000000..89133871c --- /dev/null +++ b/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/response/HasJoinedServerResponse.java @@ -0,0 +1,25 @@ +package com.github.sadcenter.auth.response; + +import com.mojang.authlib.properties.PropertyMap; +import com.mojang.util.UUIDTypeAdapter; + +import java.util.UUID; + +public class HasJoinedServerResponse { + + private final String id; + private final PropertyMap properties; + + public HasJoinedServerResponse(String uuid, PropertyMap propertyMap) { + this.id = uuid; + this.properties = propertyMap; + } + + public UUID getUuid() { + return UUIDTypeAdapter.fromString(this.id); + } + + public PropertyMap getPropertyMap() { + return this.properties; + } +} diff --git a/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/serializer/GameProfileSerializer.java b/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/serializer/GameProfileSerializer.java new file mode 100644 index 000000000..e7a60ef80 --- /dev/null +++ b/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/serializer/GameProfileSerializer.java @@ -0,0 +1,35 @@ +package com.github.sadcenter.auth.serializer; + +import com.google.gson.*; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import com.mojang.util.UUIDTypeAdapter; + +import java.lang.reflect.Type; +import java.util.UUID; + +public class GameProfileSerializer implements JsonDeserializer { + + private static final String ASHCON_UUID_FIELD = "uuid"; + private static final String MOJANG_UUID_FIELD = "id"; + + @Override + public GameProfile deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + boolean isAshCon = jsonObject.has(ASHCON_UUID_FIELD); + String stringId = jsonObject.getAsJsonPrimitive(isAshCon ? ASHCON_UUID_FIELD : MOJANG_UUID_FIELD).getAsString(); + + UUID id = isAshCon ? UUID.fromString(stringId) : UUIDTypeAdapter.fromString(stringId); + String name = jsonObject.getAsJsonPrimitive(isAshCon ? "username" : "name").getAsString(); + GameProfile gameProfile = new GameProfile(id, name); + + JsonObject propertyJson = (isAshCon ? + jsonObject.get("textures").getAsJsonObject().get("raw") : jsonObject.getAsJsonArray("properties").get(0)) + .getAsJsonObject(); + + Property property = new Property("textures", propertyJson.getAsJsonPrimitive("value").getAsString()); + gameProfile.getProperties().put("textures", property); + + return gameProfile; + } +} \ No newline at end of file diff --git a/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/serializer/UUIDSerializer.java b/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/serializer/UUIDSerializer.java new file mode 100644 index 000000000..de5e2d130 --- /dev/null +++ b/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/serializer/UUIDSerializer.java @@ -0,0 +1,22 @@ +package com.github.sadcenter.auth.serializer; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.mojang.util.UUIDTypeAdapter; + +import java.lang.reflect.Type; +import java.util.UUID; + +public class UUIDSerializer implements JsonDeserializer { + + @Override + public UUID deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + if (json.isJsonObject()) { + return UUIDTypeAdapter.fromString(json.getAsJsonObject().get("id").getAsString()); + } else { + return UUID.fromString(json.getAsString()); + } + } +} diff --git a/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/session/NachoSessionService.java b/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/session/NachoSessionService.java new file mode 100644 index 000000000..a047ff9c3 --- /dev/null +++ b/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/session/NachoSessionService.java @@ -0,0 +1,88 @@ +package com.github.sadcenter.auth.session; + +import com.github.sadcenter.auth.NachoAuthenticationService; +import com.github.sadcenter.auth.response.HasJoinedServerResponse; +import com.google.common.collect.Iterables; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.minecraft.MinecraftProfileTexture; +import com.mojang.authlib.minecraft.MinecraftSessionService; +import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; +import org.bukkit.Bukkit; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; + +public class NachoSessionService implements MinecraftSessionService { + + private static final String JOIN_URL = "https://sessionserver.mojang.com/session/minecraft/join"; + private static final String JOINED_URL = "https://sessionserver.mojang.com/session/minecraft/hasJoined"; + + private final NachoAuthenticationService authenticator; + + public NachoSessionService(NachoAuthenticationService authenticator) { + this.authenticator = authenticator; + } + + @Override + public void joinServer(GameProfile gameProfile, String authToken, String serverId) { + Map request = new HashMap() {{ + put("accessToken", authToken); + put("selectedProfile", gameProfile.getId()); + put("serverId", serverId); + }}; + + try { + this.authenticator.fetchPost(NachoAuthenticationService.url(JOIN_URL), NachoAuthenticationService.json(request)); + } catch (IOException e) { + Bukkit.getLogger().log(Level.SEVERE, "Error loading profile " + gameProfile.getName() + " (UUID: " + gameProfile.getId() + ")", e); + } + } + + @Override + public GameProfile hasJoinedServer(GameProfile gameProfile, String serverId) { + Map request = new HashMap() {{ + put("username", gameProfile.getName()); + put("serverId", serverId); + }}; + + return this.authenticator.get(JOINED_URL + NachoAuthenticationService.query(request), HasJoinedServerResponse.class).thenApply(response -> { + if (response == null || response.getUuid() == null) { + return null; + } + + GameProfile profile = new GameProfile(response.getUuid(), gameProfile.getName()); + PropertyMap propertyMap = response.getPropertyMap(); + + if (propertyMap != null && !propertyMap.isEmpty()) { + profile.getProperties().putAll(response.getPropertyMap()); + } + + return profile; + + }).join(); + } + + + @Override + public Map getTextures(GameProfile gameProfile, boolean secure) { + return Collections.emptyMap(); //not used at all? + } + + @Override + public GameProfile fillProfileProperties(GameProfile gameProfile, boolean secure) { + this.authenticator.getProfile(gameProfile.getName()).thenAccept(profile -> { + Property property = Iterables.getFirst(profile.getProperties().get("textures"), null); + + if (property != null) { + gameProfile.getProperties() + .put("textures", property); + } + }); //this might give more "steve" skin delay at the expense of better performance (not locking thread) + + return gameProfile; + } +} diff --git a/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/storage/CachedProfile.java b/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/storage/CachedProfile.java new file mode 100644 index 000000000..7e1ac665e --- /dev/null +++ b/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/storage/CachedProfile.java @@ -0,0 +1,38 @@ +package com.github.sadcenter.auth.storage; + +import com.google.common.collect.Iterables; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; + +import java.util.concurrent.TimeUnit; + +public class CachedProfile { + + private static final long CACHE_TIME = TimeUnit.DAYS.toMillis(1); + + private final String texture; + private long expiresOn; + + public CachedProfile(String property) { + this.texture = property; + this.refreshExpire(); + } + + public void refreshExpire() { + this.expiresOn = System.currentTimeMillis() + CACHE_TIME; + } + + public boolean isExpired() { + return this.expiresOn < System.currentTimeMillis(); + } + + public GameProfile toGameProfile(String name) { + GameProfile gameProfile = new GameProfile(null, name); + gameProfile.getProperties().put("textures", new Property("textures", this.texture)); + return gameProfile; + } + + public static CachedProfile fromGameProfile(GameProfile gameProfile) { + return new CachedProfile(Iterables.getFirst(gameProfile.getProperties().get("textures"), null).getValue()); + } +} diff --git a/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/storage/ProfileCache.java b/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/storage/ProfileCache.java new file mode 100644 index 000000000..7e61b0d26 --- /dev/null +++ b/NachoSpigot-Server/src/main/java/com/github/sadcenter/auth/storage/ProfileCache.java @@ -0,0 +1,104 @@ +package com.github.sadcenter.auth.storage; + +import com.github.sadcenter.auth.NachoAuthenticationService; +import com.google.common.reflect.TypeToken; +import net.minecraft.server.MinecraftServer; +import org.apache.commons.io.FileUtils; +import org.bukkit.Bukkit; +import org.spigotmc.CaseInsensitiveMap; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.stream.Collectors; + +public class ProfileCache { + + private static final File CACHE_FILE = new File("texturecache.json"); + + private final Map cachedProfiles; + private boolean ticked; + + public ProfileCache() { + this.cachedProfiles = this.load(); + this.save(); + } + + private Map load() { + if (!CACHE_FILE.exists()) { + try { + CACHE_FILE.createNewFile(); + FileUtils.writeStringToFile(CACHE_FILE, "{}"); + } catch (IOException e) { + e.printStackTrace(); + } + } else try (BufferedReader fileReader = new BufferedReader(new FileReader(CACHE_FILE))) { + Map loadedCache = NachoAuthenticationService.GSON + .fromJson(fileReader, new TypeToken>() { + }.getType()); + return this.filter(loadedCache); + } catch (IOException e) { + Bukkit.getLogger().log(Level.SEVERE, "Error while loading profile caches", e); + } + + return new CaseInsensitiveMap<>(); + } + + private Map filter(Map map) { + return new CaseInsensitiveMap<>(map.entrySet().stream().filter(cachedProfile -> !cachedProfile.getValue().isExpired()).collect(Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue + ))); + } + + public void save() { + this.save(true); + } + + public void save(boolean runAsync) { + if (this.isTicked() || this.cachedProfiles.isEmpty()) { + return; + } + + Runnable runnable = () -> { + try (BufferedWriter fileWriter = new BufferedWriter(new FileWriter(CACHE_FILE))) { + NachoAuthenticationService.GSON.toJson(this.cachedProfiles, fileWriter); + } catch (IOException e) { + e.printStackTrace(); + } + }; + + this.setTicked(true); + MinecraftServer.getServer().processQueue.add(runAsync ? () -> NachoAuthenticationService.EXECUTOR.execute(runnable) : runnable); + } + + public void put(String name, CachedProfile cachedProfile) { + this.cachedProfiles.put(name, cachedProfile); + } + + public void putAndSave(String name, CachedProfile cachedProfile) { + this.put(name, cachedProfile); + this.save(); + } + + public boolean remove(String name) { + return this.cachedProfiles.remove(name) != null; + } + + public CachedProfile getCachedProfile(String name) { + CachedProfile cachedProfile = this.cachedProfiles.get(name); + if (cachedProfile != null) { + cachedProfile.refreshExpire(); + } + return cachedProfile; + } + + public void setTicked(boolean ticked) { + this.ticked = ticked; + } + + public boolean isTicked() { + return this.ticked; + } +} diff --git a/NachoSpigot-Server/src/main/java/me/elier/nachospigot/config/NachoConfig.java b/NachoSpigot-Server/src/main/java/me/elier/nachospigot/config/NachoConfig.java index fa21841c5..019907b4b 100644 --- a/NachoSpigot-Server/src/main/java/me/elier/nachospigot/config/NachoConfig.java +++ b/NachoSpigot-Server/src/main/java/me/elier/nachospigot/config/NachoConfig.java @@ -141,6 +141,10 @@ static void loadComments() { c.addComment("settings.commands.permissions.version", "Enables a required permission to use /version"); c.addComment("settings.commands.permissions.plugins", "Enables a required permission to use /plugins"); c.addComment("settings.commands.enable-help-command", "Toggles the /help command"); + c.addComment("settings.authenticator.use-nacho-authentication", "Enables our own authentication system."); + c.addComment("settings.authenticator.backups", "Should backup calls be enabled (recommended)"); + c.addComment("settings.authenticator.uuid-priority", "What api should be used first in uuid fetching stage (ashcon, mojang)"); + c.addComment("settings.authenticator.textures-priority", "What api should be used first in textures downloading stage (ashcon, mojang)"); c.addComment("settings.use-improved-hitreg", "Enables the usage of an improved hitreg based on lag compensation and small other details."); NachoWorldConfig.loadComments(); } @@ -320,19 +324,19 @@ private static void hideProjectilesFromHiddenPlayers() { } public static boolean lagCompensatedPotions; - + private static void lagCompensatedPotions() { lagCompensatedPotions = getBoolean("settings.lag-compensated-potions", false); } public static boolean smoothPotting; - + private static void smoothPotting() { smoothPotting = getBoolean("settings.smooth-potting", false); } public static boolean antiEnderPearlGlitch; - + private static void antiEnderPearlGlitch() { antiEnderPearlGlitch = getBoolean("settings.anti-enderpearl-glitch", false); } @@ -383,7 +387,7 @@ private static void modeTcpFastOpen() { private static void enableProtocolShim() { if (config.contains("settings.enable-protocolib-shim")) getBoolean("settings.enable-protocol-shim", config.getBoolean("settings.enable-protocolib-shim")); else - enableProtocolShim = getBoolean("settings.enable-protocol-shim", true); + enableProtocolShim = getBoolean("settings.enable-protocol-shim", true); } public static boolean instantPlayInUseEntity; @@ -392,10 +396,25 @@ private static void instantPlayInUseEntity() { instantPlayInUseEntity = getBoolean("settings.instant-interaction", false); } + public static boolean useNachoAuthenticator; + public static boolean backups; + public static boolean uuidMojangPriority; + public static boolean texturesMojangPriority; + + private static void authentication() { + useNachoAuthenticator = getBoolean("settings.authenticator.use-nacho-authentication", false); + backups = getBoolean("settings.authenticator.backups", true); + uuidMojangPriority = getString("settings.authenticator.uuid-priority", "ashcon") + .equalsIgnoreCase("ashcon"); + texturesMojangPriority = getString("settings.authenticator.textures-priority", "mojang") + .equalsIgnoreCase("ashcon"); + } + public static boolean enableImprovedHitReg; private static void enableImprovedHitReg() { enableImprovedHitReg = getBoolean("settings.use-improved-hitreg", false); + } } diff --git a/NachoSpigot-Server/src/main/java/net/minecraft/server/DedicatedServer.java b/NachoSpigot-Server/src/main/java/net/minecraft/server/DedicatedServer.java index 98910e7e8..90a6353a9 100644 --- a/NachoSpigot-Server/src/main/java/net/minecraft/server/DedicatedServer.java +++ b/NachoSpigot-Server/src/main/java/net/minecraft/server/DedicatedServer.java @@ -5,9 +5,12 @@ import java.net.InetAddress; import java.net.Proxy; import java.util.Random; +import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; +import com.github.sadcenter.auth.NachoAuthenticationService; +import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; import com.destroystokyo.paper.PaperConfig; import dev.cobblesword.nachospigot.Nacho; import dev.cobblesword.nachospigot.commons.IPUtils; @@ -178,7 +181,14 @@ public void run() { // Spigot start NachoConfig.init((File) options.valueOf("nacho-settings")); // NachoSpigot - Load config before PlayerList KnockbackConfig.init((File) options.valueOf("knockback-settings")); - this.setPlayerList(new DedicatedPlayerList(this)); // Nacho - deobfuscate setPlayerList + // Nacho start - Use our own authentication system + YggdrasilAuthenticationService yggdrasilAuthenticationService = new YggdrasilAuthenticationService(super.e, UUID.randomUUID().toString()); + this.V = NachoConfig.useNachoAuthenticator ? new NachoAuthenticationService(yggdrasilAuthenticationService) : yggdrasilAuthenticationService; + this.W = this.V.createMinecraftSessionService(); + this.Y = this.V.createProfileRepository(); + // Nacho end + + this.setPlayerList(new DedicatedPlayerList(this)); org.spigotmc.SpigotConfig.init((File) options.valueOf("spigot-settings")); org.spigotmc.SpigotConfig.registerCommands(); // Spigot end diff --git a/NachoSpigot-Server/src/main/java/net/minecraft/server/EntityHuman.java b/NachoSpigot-Server/src/main/java/net/minecraft/server/EntityHuman.java index d10c5472c..2a91649bf 100644 --- a/NachoSpigot-Server/src/main/java/net/minecraft/server/EntityHuman.java +++ b/NachoSpigot-Server/src/main/java/net/minecraft/server/EntityHuman.java @@ -322,7 +322,7 @@ protected void s() { return; } // CraftBukkit end - + ItemStack itemstack = this.g.b(this.world, this); if (itemstack != this.g || itemstack != null && itemstack.count != i) { @@ -435,13 +435,13 @@ public void m() { List list = this.world.getEntities(this, axisalignedbb); if (this.ae()) { // Spigot: Add this.ae() condition (second !this.isDead near bottom of EntityLiving) - for (int i = 0; i < list.size(); ++i) { - Entity entity = (Entity) list.get(i); + for (int i = 0; i < list.size(); ++i) { + Entity entity = (Entity) list.get(i); - if (!entity.dead) { - this.d(entity); + if (!entity.dead) { + this.d(entity); + } } - } } // Spigot } @@ -660,21 +660,21 @@ public float a(Block block) { float f1 = 1.0F; switch (this.getEffect(MobEffectList.SLOWER_DIG).getAmplifier()) { - case 0: - f1 = 0.3F; - break; + case 0: + f1 = 0.3F; + break; - case 1: - f1 = 0.09F; - break; + case 1: + f1 = 0.09F; + break; - case 2: - f1 = 0.0027F; - break; + case 2: + f1 = 0.0027F; + break; - case 3: - default: - f1 = 8.1E-4F; + case 3: + default: + f1 = 8.1E-4F; } f *= f1; @@ -1015,7 +1015,7 @@ public void attack(Entity entity) { KnockbackConfig.getCurrentKb() : entity.getKnockbackProfile(); entity.g( (-MathHelper.sin((float) (this.yaw * Math.PI / 180.0D)) * i * profile.getExtraHorizontal()), - profile.getExtraVertical(), + profile.getExtraVertical(), (MathHelper.cos((float) (this.yaw * Math.PI / 180.0D)) * i * profile.getExtraHorizontal())); this.motX *= 0.6D; this.motZ *= 0.6D; @@ -1175,20 +1175,20 @@ public EntityHuman.EnumBedResult a(BlockPosition blockposition) { float f1 = 0.5F; switch (EntityHuman.SyntheticClass_1.a[enumdirection.ordinal()]) { - case 1: - f1 = 0.9F; - break; + case 1: + f1 = 0.9F; + break; - case 2: - f1 = 0.1F; - break; + case 2: + f1 = 0.1F; + break; - case 3: - f = 0.1F; - break; + case 3: + f = 0.1F; + break; - case 4: - f = 0.9F; + case 4: + f = 0.9F; } this.a(enumdirection); @@ -1212,20 +1212,20 @@ private void a(EnumDirection enumdirection) { this.by = 0.0F; this.bz = 0.0F; switch (EntityHuman.SyntheticClass_1.a[enumdirection.ordinal()]) { - case 1: - this.bz = -1.8F; - break; + case 1: + this.bz = -1.8F; + break; - case 2: - this.bz = 1.8F; - break; + case 2: + this.bz = 1.8F; + break; - case 3: - this.by = 1.8F; - break; + case 3: + this.by = 1.8F; + break; - case 4: - this.by = -1.8F; + case 4: + this.by = -1.8F; } } diff --git a/NachoSpigot-Server/src/main/java/net/minecraft/server/ItemSkull.java b/NachoSpigot-Server/src/main/java/net/minecraft/server/ItemSkull.java index 519574bcb..3e8015638 100644 --- a/NachoSpigot-Server/src/main/java/net/minecraft/server/ItemSkull.java +++ b/NachoSpigot-Server/src/main/java/net/minecraft/server/ItemSkull.java @@ -1,6 +1,11 @@ package net.minecraft.server; +import com.github.sadcenter.auth.NachoAuthenticationService; +import com.google.common.base.Predicate; import com.mojang.authlib.GameProfile; +import me.elier.nachospigot.config.NachoConfig; + +import javax.annotation.Nullable; import java.util.UUID; public class ItemSkull extends Item { @@ -121,14 +126,21 @@ public boolean a(final NBTTagCompound nbttagcompound) { // Spigot - make final GameProfile gameprofile = new GameProfile((UUID) null, nbttagcompound.getString("SkullOwner")); // Spigot start - TileEntitySkull.b(gameprofile, new com.google.common.base.Predicate() { - - @Override - public boolean apply(GameProfile gameprofile) { - nbttagcompound.set("SkullOwner", GameProfileSerializer.serialize(new NBTTagCompound(), gameprofile)); - return false; - } - }); + // Nacho start - Use our own authentication system + if(NachoConfig.useNachoAuthenticator) { + ((NachoAuthenticationService) MinecraftServer.getServer().getAuthenticator()).getProfile(gameprofile.getName()).thenAccept(profile -> { + nbttagcompound.set("SkullOwner", GameProfileSerializer.serialize(new NBTTagCompound(), profile)); + }); + } else { + TileEntitySkull.b(gameprofile, new Predicate() { + @Override + public boolean apply(@Nullable GameProfile gameProfile) { + nbttagcompound.set("SkullOwner", GameProfileSerializer.serialize(new NBTTagCompound(), gameProfile)); + return false; + } + }); + } + // Nacho end // Spigot end return true; } else { diff --git a/NachoSpigot-Server/src/main/java/net/minecraft/server/MinecraftServer.java b/NachoSpigot-Server/src/main/java/net/minecraft/server/MinecraftServer.java index e25f90631..1a52c952f 100644 --- a/NachoSpigot-Server/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/NachoSpigot-Server/src/main/java/net/minecraft/server/MinecraftServer.java @@ -1,10 +1,12 @@ package net.minecraft.server; +import com.github.sadcenter.auth.NachoAuthenticationService; import com.google.common.base.Charsets; import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFutureTask; +import com.mojang.authlib.AuthenticationService; import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfileRepository; import com.mojang.authlib.minecraft.MinecraftSessionService; @@ -97,10 +99,10 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs private String S; private boolean T; private boolean U; - private final YggdrasilAuthenticationService V; - private final MinecraftSessionService W; + protected AuthenticationService V; + protected MinecraftSessionService W; private long X = 0L; - private final GameProfileRepository Y; + protected GameProfileRepository Y; private final UserCache Z; protected final Queue> j = new java.util.concurrent.ConcurrentLinkedQueue>(); // Spigot, PAIL: Rename private Thread serverThread; @@ -128,9 +130,6 @@ public MinecraftServer(OptionSet options, Proxy proxy, File file1) { this.Z = new UserCache(this, file1); this.b = this.h(); // this.convertable = new WorldLoaderServer(file); // CraftBukkit - moved to DedicatedServer.init - this.V = new YggdrasilAuthenticationService(proxy, UUID.randomUUID().toString()); - this.W = this.V.createMinecraftSessionService(); - this.Y = this.V.createProfileRepository(); // CraftBukkit start this.options = options; // Try to see if we're actually running in a terminal, disable jline if not @@ -800,6 +799,11 @@ public void B() { processQueue.remove().run(); } SpigotTimings.processQueueTimer.stopTiming(); // Spigot + // Nacho start + if(NachoConfig.useNachoAuthenticator) { + ((NachoAuthenticationService) this.V).tick(); + } + // Nacho end SpigotTimings.chunkIOTickTimer.startTiming(); // Spigot org.bukkit.craftbukkit.chunkio.ChunkIOExecutor.tick(); @@ -1425,6 +1429,12 @@ public int getMaxBuildHeight() { return this.F; } + // Nacho start + public AuthenticationService getAuthenticator() { + return this.V; + } + // Nacho end + public void c(int i) { this.F = i; } diff --git a/NachoSpigot-Server/src/main/java/net/minecraft/server/TileEntitySkull.java b/NachoSpigot-Server/src/main/java/net/minecraft/server/TileEntitySkull.java index 04d0e666c..3d894d1b9 100644 --- a/NachoSpigot-Server/src/main/java/net/minecraft/server/TileEntitySkull.java +++ b/NachoSpigot-Server/src/main/java/net/minecraft/server/TileEntitySkull.java @@ -1,32 +1,30 @@ package net.minecraft.server; +import com.github.sadcenter.auth.NachoAuthenticationService; +import com.google.common.base.Predicate; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.Iterables; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.mojang.authlib.Agent; import com.mojang.authlib.GameProfile; +import com.mojang.authlib.ProfileLookupCallback; import com.mojang.authlib.properties.Property; -import java.util.UUID; - -// Spigot start -import com.google.common.base.Predicate; +import me.elier.nachospigot.config.NachoConfig; +import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import com.mojang.authlib.Agent; -import com.mojang.authlib.ProfileLookupCallback; -// Spigot end - public class TileEntitySkull extends TileEntity { private int a; private int rotation; private GameProfile g = null; - // Spigot start + //Spigot start public static final Executor executor = Executors.newFixedThreadPool(3, new ThreadFactoryBuilder() .setNameFormat("Head Conversion Thread - %1$d") @@ -58,7 +56,7 @@ public void onProfileLookupFailed(GameProfile gp, Exception excptn) { GameProfile profile = profiles[ 0 ]; if (profile == null) { - UUID uuid = EntityHuman.createPlayerUUID(new GameProfile(null, key)); // Nacho - deobfuscate createPlayerUUID + UUID uuid = EntityHuman.createPlayerUUID(new GameProfile(null, key)); profile = new GameProfile(uuid, key); gameProfileLookup.onProfileLookupSucceeded(profile); @@ -77,8 +75,8 @@ public void onProfileLookupFailed(GameProfile gp, Exception excptn) { return profile; } } ); - - // Spigot end + + //Spigot end public TileEntitySkull() {} @@ -139,11 +137,20 @@ public void setGameProfile(GameProfile gameprofile) { private void e() { // Spigot start GameProfile profile = this.g; - setSkullType( 0 ); // Work around client bug - b(profile, new Predicate() { - - @Override - public boolean apply(GameProfile input) { + setSkullType(0); // Work around client bug + // Nacho start - Use our own authentication system + if (NachoConfig.useNachoAuthenticator && profile != null) { + NachoAuthenticationService authenticator = (NachoAuthenticationService) MinecraftServer.getServer().getAuthenticator(); + authenticator.getProfile(profile.getName()).thenAccept(gameProfile -> { + setSkullType(3); // Work around client bug + g = gameProfile; + update(); + if (world != null) { + world.notify(position); + } + }); + } else if (!NachoConfig.useNachoAuthenticator) { + b(profile, input -> { setSkullType(3); // Work around client bug g = input; update(); @@ -151,9 +158,9 @@ public boolean apply(GameProfile input) { world.notify(position); } return false; - } - }); - // Spigot end + }); + } else setSkullType(3); // Work around client bug + // Spigot/Nacho end } // Spigot start - Support async lookups @@ -197,7 +204,7 @@ public void run() { callback.apply(gameprofile); } } - // Spigot end + // Spigot end public int getSkullType() { return this.a; diff --git a/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java b/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java index 6575bbfc0..3ad1c4679 100644 --- a/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java +++ b/NachoSpigot-Server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java @@ -1,19 +1,16 @@ package org.bukkit.craftbukkit.inventory; -import java.util.Map; - +import com.github.sadcenter.auth.NachoAuthenticationService; +import com.google.common.collect.ImmutableMap.Builder; +import com.mojang.authlib.GameProfile; +import me.elier.nachospigot.config.NachoConfig; import net.minecraft.server.*; - -// PaperSpigot start -// PaperSpigot end - import org.bukkit.Material; import org.bukkit.configuration.serialization.DelegateDeserialization; import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta; import org.bukkit.inventory.meta.SkullMeta; -import com.google.common.collect.ImmutableMap.Builder; -import com.mojang.authlib.GameProfile; +import java.util.Map; @DelegateDeserialization(SerializableMeta.class) class CraftMetaSkull extends CraftMetaItem implements SkullMeta { @@ -73,22 +70,31 @@ void applyToItem(final NBTTagCompound tag) { // Spigot - make final super.applyToItem(tag); if (profile != null) { - NBTTagCompound owner = new NBTTagCompound(); - GameProfileSerializer.serialize(owner, profile); - tag.set( SKULL_OWNER.NBT, owner ); - // Spigot start - do an async lookup of the profile. - // Unfortunately there is not way to refresh the holding - // inventory, so that responsibility is left to the user. - net.minecraft.server.TileEntitySkull.b(profile, input -> { - NBTTagCompound owner1 = new NBTTagCompound(); - GameProfileSerializer.serialize(owner1, input ); - tag.set( SKULL_OWNER.NBT, owner1); + // Nacho start - Use our own authentication system + setSkullNbt(tag, profile); + + if (NachoConfig.useNachoAuthenticator) { + NachoAuthenticationService authenticator = (NachoAuthenticationService) MinecraftServer.getServer().getAuthenticator(); + + authenticator.getProfile(profile.getName()).thenAccept(gameProfile -> { + setSkullNbt(tag, gameProfile); + }); + } else TileEntitySkull.b(profile, gameProfile -> { + setSkullNbt(tag, gameProfile); return false; }); - // Spigot end + // Nacho end } } + // Nacho start + private void setSkullNbt(NBTTagCompound tag, GameProfile gameProfile) { + NBTTagCompound ownerTag = new NBTTagCompound(); + GameProfileSerializer.serialize(ownerTag, gameProfile); + tag.set(SKULL_OWNER.NBT, ownerTag); + } + // Nacho end + @Override boolean isEmpty() { return super.isEmpty() && isSkullEmpty(); @@ -126,18 +132,22 @@ public boolean setOwner(String name) { if (name == null || name.length() > MAX_OWNER_LENGTH) { return false; } - + // PaperSpigot start - Check usercache if the player is online - EntityPlayer player = MinecraftServer.getServer().getPlayerList().getPlayer(name); - if (profile == null && player != null) profile = player.getProfile(); - // PaperSpigot end - + EntityPlayer player = MinecraftServer.getServer().getPlayerList().getPlayer(name); + if (profile == null && player != null) profile = player.getProfile(); + // PaperSpigot end + if (profile == null) { // name.toLowerCase(java.util.Locale.ROOT) causes the NPE - profile = TileEntitySkull.skinCache.getIfPresent(name.toLowerCase(java.util.Locale.ROOT)); // Paper // tries to get from skincache + // Nacho start - Use our own authentication system + profile = NachoConfig.useNachoAuthenticator ? + ((NachoAuthenticationService) MinecraftServer.getServer().getAuthenticator()).getPresentProfile(name) : + TileEntitySkull.skinCache.getIfPresent(name); // Paper // tries to get from skincache + // Nacho end } if (profile == null) profile = new GameProfile(null, name); - + return true; } diff --git a/pom.xml b/pom.xml index 84f6e9492..88c930733 100644 --- a/pom.xml +++ b/pom.xml @@ -28,4 +28,4 @@ UTF-8 - + \ No newline at end of file