diff --git a/gateway/EaglercraftXVelocity/EaglerXVelocity-Latest.jar b/gateway/EaglercraftXVelocity/EaglerXVelocity-Latest.jar
index c2f3cb0a..03b6f74d 100644
Binary files a/gateway/EaglercraftXVelocity/EaglerXVelocity-Latest.jar and b/gateway/EaglercraftXVelocity/EaglerXVelocity-Latest.jar differ
diff --git a/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/EaglerXVelocityVersion.java b/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/EaglerXVelocityVersion.java
index 83d4d034..f3f1f5be 100644
--- a/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/EaglerXVelocityVersion.java
+++ b/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/EaglerXVelocityVersion.java
@@ -17,13 +17,14 @@ package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity;
  */
 public class EaglerXVelocityVersion {
 
-	public static final String NATIVE_VELOCITY_BUILD = "3.4.0-SNAPSHOT:a33f2d1a:b452";
+	public static final String NATIVE_VELOCITY_BUILD = "3.4.0-SNAPSHOT:71feb11b:b461";
+	public static final String NATIVE_VELOCITY_BUILD_DL = "https://api.papermc.io/v2/projects/velocity/versions/3.4.0-SNAPSHOT/builds/461/downloads/velocity-3.4.0-SNAPSHOT-461.jar";
 
 	public static final String ID = "EaglerXVelocity";
 	public static final String PLUGIN_ID = "eaglerxvelocity";
 	public static final String NAME = "EaglercraftXVelocity";
 	public static final String DESCRIPTION = "Plugin to allow EaglercraftX 1.8 players to join your network, or allow EaglercraftX 1.8 players to use your network as a proxy to join other networks";
-	public static final String VERSION = "1.1.5";
+	public static final String VERSION = "1.1.6";
 	public static final String[] AUTHORS = new String[] { "lax1dude", "ayunami2000" };
 
 }
diff --git a/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/auth/AuthLoadingCache.java b/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/auth/AuthLoadingCache.java
index 2c74e8b8..7b98709a 100644
--- a/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/auth/AuthLoadingCache.java
+++ b/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/auth/AuthLoadingCache.java
@@ -4,6 +4,8 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EaglerXVelocityAPIHelper;
 
@@ -44,6 +46,7 @@ public class AuthLoadingCache<K, V> {
 		boolean shouldEvict(K key, V value);
 	}
 
+	private final ReadWriteLock cacheMapLock;
 	private final Map<K, CacheEntry<V>> cacheMap;
 	private final CacheLoader<K, V> provider;
 	private final long cacheTTL;
@@ -51,6 +54,7 @@ public class AuthLoadingCache<K, V> {
 	private long cacheTimer;
 
 	public AuthLoadingCache(CacheLoader<K, V> provider, long cacheTTL) {
+		this.cacheMapLock = new ReentrantReadWriteLock();
 		this.cacheMap = new HashMap<>();
 		this.provider = provider;
 		this.cacheTTL = cacheTTL;
@@ -58,13 +62,19 @@ public class AuthLoadingCache<K, V> {
 
 	public V get(K key) {
 		CacheEntry<V> etr;
-		synchronized(cacheMap) {
+		cacheMapLock.readLock().lock();
+		try {
 			etr = cacheMap.get(key);
+		}finally {
+			cacheMapLock.readLock().unlock();
 		}
 		if(etr == null) {
+			cacheMapLock.writeLock().lock();
 			V loaded = provider.load(key);
-			synchronized(cacheMap) {
+			try {
 				cacheMap.put(key, new CacheEntry<>(loaded));
+			}finally {
+				cacheMapLock.writeLock().unlock();
 			}
 			return loaded;
 		}else {
@@ -74,13 +84,17 @@ public class AuthLoadingCache<K, V> {
 	}
 
 	public void evict(K key) {
-		synchronized(cacheMap) {
+		cacheMapLock.writeLock().lock();
+		try {
 			cacheMap.remove(key);
+		}finally {
+			cacheMapLock.writeLock().unlock();
 		}
 	}
 
 	public void evictAll(CacheVisitor<K, V> visitor) {
-		synchronized(cacheMap) {
+		cacheMapLock.writeLock().lock();
+		try {
 			Iterator<Entry<K,CacheEntry<V>>> itr = cacheMap.entrySet().iterator();
 			while(itr.hasNext()) {
 				Entry<K,CacheEntry<V>> etr = itr.next();
@@ -88,6 +102,8 @@ public class AuthLoadingCache<K, V> {
 					itr.remove();
 				}
 			}
+		}finally {
+			cacheMapLock.writeLock().unlock();
 		}
 	}
 
@@ -95,7 +111,8 @@ public class AuthLoadingCache<K, V> {
 		long millis = EaglerXVelocityAPIHelper.steadyTimeMillis();
 		if(millis - cacheTimer > (cacheTTL / 2L)) {
 			cacheTimer = millis;
-			synchronized(cacheMap) {
+			cacheMapLock.writeLock().lock();
+			try {
 				Iterator<CacheEntry<V>> mapItr = cacheMap.values().iterator();
 				while(mapItr.hasNext()) {
 					CacheEntry<V> etr = mapItr.next();
@@ -103,13 +120,18 @@ public class AuthLoadingCache<K, V> {
 						mapItr.remove();
 					}
 				}
+			}finally {
+				cacheMapLock.writeLock().unlock();
 			}
 		}
 	}
 
 	public void flush() {
-		synchronized(cacheMap) {
+		cacheMapLock.writeLock().lock();
+		try {
 			cacheMap.clear();
+		}finally {
+			cacheMapLock.writeLock().unlock();
 		}
 	}
 
diff --git a/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/server/HttpWebSocketHandler.java b/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/server/HttpWebSocketHandler.java
index b40324b7..38c1d0b8 100644
--- a/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/server/HttpWebSocketHandler.java
+++ b/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/server/HttpWebSocketHandler.java
@@ -118,6 +118,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
 	private static final Method loginEventFiredMethod;
 	private static final Constructor<ConnectedPlayer> stupid3Constructor;
 	private static final Constructor<ConnectedPlayer> stupid3Constructor_new;
+	private static final Constructor<ConnectedPlayer> stupid3Constructor_new_new;
 	private static final Method setPermissionFunctionMethod;
 	private static final Field defaultPermissionsField;
 	private static final Constructor<InitialConnectSessionHandler> stupid4Constructor;
@@ -137,16 +138,24 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
 			loginEventFiredMethod.setAccessible(true);
 			Constructor<ConnectedPlayer> c3 = null;
 			Constructor<ConnectedPlayer> c3_new = null;
+			Constructor<ConnectedPlayer> c3_new_new = null;
 			try {
-				c3_new = ConnectedPlayer.class.getDeclaredConstructor(VelocityServer.class, GameProfile.class, MinecraftConnection.class, InetSocketAddress.class, String.class, boolean.class, IdentifiedKey.class);
-				c3_new.setAccessible(true);
+				c3_new_new = ConnectedPlayer.class.getDeclaredConstructor(VelocityServer.class, GameProfile.class, MinecraftConnection.class, InetSocketAddress.class, String.class, boolean.class, HandshakeIntent.class, IdentifiedKey.class);
+				c3_new_new.setAccessible(true);
 			} catch (NoSuchMethodException e) {
-				c3 = ConnectedPlayer.class.getDeclaredConstructor(VelocityServer.class, GameProfile.class, MinecraftConnection.class, InetSocketAddress.class, boolean.class, IdentifiedKey.class);
-				c3.setAccessible(true);
-				c3_new = null;
+				try {
+					c3_new_new = null;
+					c3_new = ConnectedPlayer.class.getDeclaredConstructor(VelocityServer.class, GameProfile.class, MinecraftConnection.class, InetSocketAddress.class, String.class, boolean.class, IdentifiedKey.class);
+					c3_new.setAccessible(true);
+				} catch (NoSuchMethodException ee) {
+					c3_new = null;
+					c3 = ConnectedPlayer.class.getDeclaredConstructor(VelocityServer.class, GameProfile.class, MinecraftConnection.class, InetSocketAddress.class, boolean.class, IdentifiedKey.class);
+					c3.setAccessible(true);
+				}
 			}
 			stupid3Constructor = c3;
 			stupid3Constructor_new = c3_new;
+			stupid3Constructor_new_new = c3_new_new;
 			setPermissionFunctionMethod = ConnectedPlayer.class.getDeclaredMethod("setPermissionFunction", PermissionFunction.class);
 			setPermissionFunctionMethod.setAccessible(true);
 			defaultPermissionsField = ConnectedPlayer.class.getDeclaredField("DEFAULT_PERMISSIONS");
@@ -1080,7 +1089,13 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
 								}
 
 								ConnectedPlayer player;
-								if(stupid3Constructor_new != null) {
+								if(stupid3Constructor_new_new != null) {
+									try {
+										player = stupid3Constructor_new_new.newInstance(bungee, gp, con, lic.getVirtualHost().orElse(null), lic.getRawVirtualHost().orElse(null), false, HandshakeIntent.LOGIN, lic.getIdentifiedKey());
+									} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
+										throw new RuntimeException(e);
+									}
+								}else if(stupid3Constructor_new != null) {
 									try {
 										player = stupid3Constructor_new.newInstance(bungee, gp, con, lic.getVirtualHost().orElse(null), lic.getRawVirtualHost().orElse(null), false, lic.getIdentifiedKey());
 									} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
@@ -1309,14 +1324,11 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
 
 														pp.remove(HttpWebSocketHandler.this);
 
-														pp
-																.addLast("EaglerMinecraftByteBufDecoder", new EaglerMinecraftDecoder())
-																.addLast(LEGACY_PING_DECODER, new LegacyPingDecoder())
+														pp.addLast("EaglerMinecraftByteBufDecoder", new EaglerMinecraftDecoder())
 																.addLast(READ_TIMEOUT,
 																		new ReadTimeoutHandler(bungee.getConfiguration().getReadTimeout(),
 																				TimeUnit.MILLISECONDS))
 																.addLast("EaglerMinecraftByteBufEncoder", new EaglerMinecraftEncoder())
-																.addLast(LEGACY_PING_ENCODER, LegacyPingEncoder.INSTANCE)
 																.addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolUtils.Direction.SERVERBOUND))
 																.addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolUtils.Direction.CLIENTBOUND));
 														
diff --git a/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/server/protocol/GameProtocolMessageController.java b/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/server/protocol/GameProtocolMessageController.java
index 96caab5a..f027d194 100644
--- a/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/server/protocol/GameProtocolMessageController.java
+++ b/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/server/protocol/GameProtocolMessageController.java
@@ -1,6 +1,7 @@
 package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.protocol;
 
 import java.io.IOException;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.Callable;
@@ -241,10 +242,12 @@ public class GameProtocolMessageController {
 			while(!sendQueueV4.isEmpty()) {
 				sendCount = 0;
 				totalLen = 0;
+				Iterator<byte[]> itr = sendQueueV4.iterator();
 				do {
-					i = sendQueueV4.get(sendCount++).length;
+					i = itr.next().length;
 					totalLen += GamePacketOutputBuffer.getVarIntSize(i) + i;
-				}while(totalLen < 32760 && sendCount < sendQueueV4.size());
+					++sendCount;
+				}while(totalLen < 32760 && itr.hasNext());
 				if(totalLen >= 32760) {
 					--sendCount;
 				}
diff --git a/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/server/web/HttpWebServer.java b/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/server/web/HttpWebServer.java
index 356937e0..1edc9721 100644
--- a/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/server/web/HttpWebServer.java
+++ b/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/server/web/HttpWebServer.java
@@ -41,7 +41,6 @@ public class HttpWebServer {
 	private final String page404;
 	private static HttpMemoryCache default404Page;
 	private static HttpMemoryCache default404UpgradePage;
-	private static final Object cacheClearLock = new Object();
 	
 	public HttpWebServer(File directory, Map<String,HttpContentType> contentTypes, List<String> index, String page404) {
 		this.directory = directory;
@@ -53,15 +52,13 @@ public class HttpWebServer {
 	
 	public void flushCache() {
 		long millis = EaglerXVelocityAPIHelper.steadyTimeMillis();
-		synchronized(cacheClearLock) {
-			synchronized(filesCache) {
-				Iterator<HttpMemoryCache> itr = filesCache.values().iterator();
-				while(itr.hasNext()) {
-					HttpMemoryCache i = itr.next();
-					if(i.contentType.fileBrowserCacheTTL != Long.MAX_VALUE && millis - i.lastCacheHit > 900000l) {
-						i.fileData.release();
-						itr.remove();
-					}
+		synchronized(filesCache) {
+			Iterator<HttpMemoryCache> itr = filesCache.values().iterator();
+			while(itr.hasNext()) {
+				HttpMemoryCache i = itr.next();
+				if(i.contentType.fileBrowserCacheTTL != Long.MAX_VALUE && millis - i.lastCacheHit > 900000l) {
+					i.fileData.release();
+					itr.remove();
 				}
 			}
 		}
@@ -94,20 +91,17 @@ public class HttpWebServer {
 			}
 			
 			String joinedPath = String.join("/", pathList);
-	
-			synchronized(cacheClearLock) {
-				synchronized(filesCache) {
-					cached = filesCache.get(joinedPath);
-				}
+
+			//TODO: Rewrite this to cause less lock contention
+			synchronized(filesCache) {
+				cached = filesCache.get(joinedPath);
 				
 				if(cached != null) {
 					cached = validateCache(cached);
 					if(cached != null) {
 						return cached;
 					}else {
-						synchronized(filesCache) {
-							filesCache.remove(joinedPath);
-						}
+						filesCache.remove(joinedPath);
 					}
 				}
 				
@@ -124,19 +118,13 @@ public class HttpWebServer {
 				if(f.isDirectory()) {
 					for(int i = 0, l = index.size(); i < l; ++i) {
 						String p = joinedPath + "/" + index.get(i);
-						synchronized(filesCache) {
-							cached = filesCache.get(p);
-						}
+						cached = filesCache.get(p);
 						if(cached != null) {
 							cached = validateCache(cached);
 							if(cached != null) {
-								synchronized(filesCache) {
-									filesCache.put(joinedPath, cached);
-								}
+								filesCache.put(joinedPath, cached);
 							}else {
-								synchronized(filesCache) {
-									filesCache.remove(p);
-								}
+								filesCache.remove(p);
 								if(page404 == null || path.equals(page404)) {
 									return default404Page;
 								}else {
@@ -152,9 +140,7 @@ public class HttpWebServer {
 						if(ff.isFile()) {
 							HttpMemoryCache memCache = retrieveFile(ff, p);
 							if(memCache != null) {
-								synchronized(filesCache) {
-									filesCache.put(joinedPath, memCache);
-								}
+								filesCache.put(joinedPath, memCache);
 								return memCache;
 							}
 						}
@@ -167,9 +153,7 @@ public class HttpWebServer {
 				}else {
 					HttpMemoryCache memCache = retrieveFile(f, joinedPath);
 					if(memCache != null) {
-						synchronized(filesCache) {
-							filesCache.put(joinedPath, memCache);
-						}
+						filesCache.put(joinedPath, memCache);
 						return memCache;
 					}else {
 						if(page404 == null || path.equals(page404)) {
diff --git a/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/shit/CompatWarning.java b/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/shit/CompatWarning.java
index e27b828a..54b8102b 100644
--- a/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/shit/CompatWarning.java
+++ b/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/shit/CompatWarning.java
@@ -34,6 +34,7 @@ public class CompatWarning {
 				":>  apart from the versions listed below:",
 				":>  ",
 				":>  - Velocity: " + EaglerXVelocityVersion.NATIVE_VELOCITY_BUILD,
+				":>  - " + EaglerXVelocityVersion.NATIVE_VELOCITY_BUILD_DL,
 				":>  ",
 				":>  This is not a Bukkit/Spigot plugin!",
 				":>  ",
diff --git a/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/skins/CapeServiceOffline.java b/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/skins/CapeServiceOffline.java
index 6044c7de..0522bf5b 100644
--- a/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/skins/CapeServiceOffline.java
+++ b/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/skins/CapeServiceOffline.java
@@ -1,8 +1,8 @@
 package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.skins;
 
-import java.util.HashMap;
-import java.util.Map;
 import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
 import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPlayerData;
 import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
@@ -12,7 +12,7 @@ import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherCape
 import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherCapePresetEAG;
 
 /**
- * Copyright (c) 2024 lax1dude. All Rights Reserved.
+ * Copyright (c) 2024-2025 lax1dude. All Rights Reserved.
  * 
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@@ -30,20 +30,15 @@ public class CapeServiceOffline {
 
 	public static final int masterRateLimitPerPlayer = 250;
 
-	private final Map<UUID, GameMessagePacket> capesCache = new HashMap<>();
+	private final ConcurrentMap<UUID, GameMessagePacket> capesCache = new ConcurrentHashMap<>();
 
 	public void registerEaglercraftPlayer(UUID playerUUID, GameMessagePacket capePacket) {
-		synchronized(capesCache) {
-			capesCache.put(playerUUID, capePacket);
-		}
+		capesCache.put(playerUUID, capePacket);
 	}
 
 	public void processGetOtherCape(UUID searchUUID, EaglerPlayerData sender) {
 		if(sender.skinLookupRateLimiter.rateLimit(masterRateLimitPerPlayer)) {
-			GameMessagePacket maybeCape;
-			synchronized(capesCache) {
-				maybeCape = capesCache.get(searchUUID);
-			}
+			GameMessagePacket maybeCape = capesCache.get(searchUUID);
 			if(maybeCape != null) {
 				sender.sendEaglerMessage(maybeCape);
 			}else {
@@ -54,10 +49,7 @@ public class CapeServiceOffline {
 	}
 
 	public void processForceCape(UUID clientUUID, EaglerPlayerData initialHandler) {
-		GameMessagePacket maybeCape;
-		synchronized(capesCache) {
-			maybeCape = capesCache.get(clientUUID);
-		}
+		GameMessagePacket maybeCape = capesCache.get(clientUUID);
 		if(maybeCape != null) {
 			if (maybeCape instanceof SPacketOtherCapePresetEAG) {
 				initialHandler.sendEaglerMessage(
@@ -70,15 +62,11 @@ public class CapeServiceOffline {
 	}
 
 	public void unregisterPlayer(UUID playerUUID) {
-		synchronized(capesCache) {
-			capesCache.remove(playerUUID);
-		}
+		capesCache.remove(playerUUID);
 	}
 
 	public GameMessagePacket getCape(UUID clientUUID) {
-		synchronized(capesCache) {
-			return capesCache.get(clientUUID);
-		}
+		return capesCache.get(clientUUID);
 	}
 
 	public byte[] getCapeHandshakeData(UUID clientUUID) {
@@ -107,8 +95,6 @@ public class CapeServiceOffline {
 	}
 
 	public void shutdown() {
-		synchronized(capesCache) {
-			capesCache.clear();
-		}
+		capesCache.clear();
 	}
 }
diff --git a/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/skins/SkinService.java b/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/skins/SkinService.java
index b8bed998..615d7907 100644
--- a/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/skins/SkinService.java
+++ b/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/skins/SkinService.java
@@ -1,6 +1,7 @@
 package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.skins;
 
 import java.net.URI;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -10,6 +11,10 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.function.Consumer;
 
 import com.google.common.collect.Multimap;
@@ -36,7 +41,7 @@ import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkin
 import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
 
 /**
- * Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
+ * Copyright (c) 2022-2025 lax1dude. All Rights Reserved.
  * 
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@@ -54,16 +59,20 @@ public class SkinService implements ISkinService {
 
 	public static final int masterRateLimitPerPlayer = 250;
 
-	private final Map<UUID, CachedPlayerSkin> onlinePlayersCache = new HashMap<>();
+	private final ConcurrentMap<UUID, CachedPlayerSkin> onlinePlayersCache = new ConcurrentHashMap<>();
+	private final ConcurrentMap<UUID, UUID> onlinePlayersToTexturesMap = new ConcurrentHashMap<>();
+	private final ConcurrentMap<UUID, CachedForeignSkin> foreignSkinCache = new ConcurrentHashMap<>();
+
+	private final ReadWriteLock onlinePlayersFromTexturesMapLock = new ReentrantReadWriteLock();
 	private final Multimap<UUID, UUID> onlinePlayersFromTexturesMap = MultimapBuilder.hashKeys().hashSetValues().build();
-	private final Map<UUID, UUID> onlinePlayersToTexturesMap = new HashMap<>();
-	private final Map<UUID, CachedForeignSkin> foreignSkinCache = new HashMap<>();
 
 	private final Map<UUID, PendingTextureDownload> pendingTextures = new HashMap<>();
 	private final Map<UUID, PendingProfileUUIDLookup> pendingUUIDs = new HashMap<>();
 	private final Map<String, PendingProfileNameLookup> pendingNameLookups = new HashMap<>();
 
+	private final ReadWriteLock antagonistsLock = new ReentrantReadWriteLock();
 	private final Object2IntMap<UUID> antagonists = new Object2IntOpenHashMap<>();
+
 	private long antagonistCooldown = EaglerXVelocityAPIHelper.steadyTimeMillis();
 
 	private final Consumer<Set<UUID>> antagonistLogger = new Consumer<Set<UUID>>() {
@@ -73,7 +82,8 @@ public class SkinService implements ISkinService {
 			if(t.size() == 1) {
 				int limit = EaglerXVelocity.getEagler().getConfig().getAntagonistsRateLimit() << 1;
 				UUID offender = t.iterator().next();
-				synchronized(antagonists) {
+				antagonistsLock.writeLock().lock();
+				try {
 					int v = antagonists.getInt(offender);
 					if(v == antagonists.defaultReturnValue()) {
 						antagonists.put(offender, 1);
@@ -82,6 +92,8 @@ public class SkinService implements ISkinService {
 							antagonists.put(offender, v + 1);
 						}
 					}
+				}finally {
+					antagonistsLock.writeLock().unlock();
 				}
 			}
 		}
@@ -95,7 +107,7 @@ public class SkinService implements ISkinService {
 		protected final UUID uuid;
 		protected final SkinPacketVersionCache data;
 		protected final int modelKnown;
-		protected long lastHit;
+		protected volatile long lastHit;
 
 		protected CachedForeignSkin(UUID uuid, SkinPacketVersionCache data, int modelKnown) {
 			this.uuid = uuid;
@@ -130,7 +142,7 @@ public class SkinService implements ISkinService {
 		protected final Consumer<Set<UUID>> antagonistsCallback;
 
 		protected final long initializedTime;
-		protected boolean finalized;
+		protected volatile boolean finalized;
 
 		protected PendingTextureDownload(UUID textureUUID, String textureURL, UUID caller, Consumer<byte[]> callback,
 				Consumer<Set<UUID>> antagonistsCallback) {
@@ -172,7 +184,7 @@ public class SkinService implements ISkinService {
 		protected final Consumer<Set<UUID>> antagonistsCallback;
 
 		protected final long initializedTime;
-		protected boolean finalized;
+		protected volatile boolean finalized;
 
 		protected PendingProfileUUIDLookup(UUID profileUUID, UUID caller, Consumer<CacheFetchedProfile> callback,
 				Consumer<Set<UUID>> antagonistsCallback) {
@@ -213,7 +225,7 @@ public class SkinService implements ISkinService {
 		protected final Consumer<Set<UUID>> antagonistsCallback;
 
 		protected final long initializedTime;
-		protected boolean finalized;
+		protected volatile boolean finalized;
 
 		protected PendingProfileNameLookup(String profileName, UUID caller, Consumer<CacheFetchedProfile> callback,
 				Consumer<Set<UUID>> antagonistsCallback) {
@@ -260,60 +272,46 @@ public class SkinService implements ISkinService {
 			return;
 		}
 		
-		CachedPlayerSkin maybeCachedPacket;
-		synchronized(onlinePlayersCache) {
-			maybeCachedPacket = onlinePlayersCache.get(searchUUID);
-		}
+		CachedPlayerSkin maybeCachedPacket = onlinePlayersCache.get(searchUUID);
 		
 		if(maybeCachedPacket != null) {
 			sender.sendEaglerMessage(maybeCachedPacket.data.get(sender.getEaglerProtocol()));
 		}else {
 			Player player = EaglerXVelocity.proxy().getPlayer(searchUUID).orElse(null);
-			UUID playerTexture;
-			synchronized(onlinePlayersToTexturesMap) {
-				playerTexture = onlinePlayersToTexturesMap.get(searchUUID);
-			}
+			UUID playerTexture = onlinePlayersToTexturesMap.get(searchUUID);
 			if(playerTexture != null) {
 				Collection<UUID> possiblePlayers;
-				synchronized(onlinePlayersFromTexturesMap) {
-					possiblePlayers = onlinePlayersFromTexturesMap.get(playerTexture);
+				onlinePlayersFromTexturesMapLock.readLock().lock();
+				try {
+					possiblePlayers = new ArrayList<>(onlinePlayersFromTexturesMap.get(playerTexture));
+				}finally {
+					onlinePlayersFromTexturesMapLock.readLock().unlock();
 				}
 				boolean playersExist = possiblePlayers.size() > 0;
 				if(playersExist) {
 					for(UUID uuid : possiblePlayers) {
-						synchronized(onlinePlayersCache) {
-							maybeCachedPacket = onlinePlayersCache.get(uuid);
-						}
+						maybeCachedPacket = onlinePlayersCache.get(uuid);
 						if(maybeCachedPacket != null) {
 							SkinPacketVersionCache rewritten = SkinPacketVersionCache.rewriteUUID(
 									maybeCachedPacket.data, searchUUID.getMostSignificantBits(),
 									searchUUID.getLeastSignificantBits());
 							if(player != null) {
-								synchronized(onlinePlayersCache) {
-									onlinePlayersCache.put(searchUUID, new CachedPlayerSkin(rewritten,
-											maybeCachedPacket.textureUUID, maybeCachedPacket.modelId));
-								}
+								onlinePlayersCache.put(searchUUID, new CachedPlayerSkin(rewritten,
+										maybeCachedPacket.textureUUID, maybeCachedPacket.modelId));
 							}
 							sender.sendEaglerMessage(rewritten.get(sender.getEaglerProtocol()));
 							return;
 						}
 					}
 				}
-				CachedForeignSkin foreignSkin;
-				synchronized(foreignSkinCache) {
-					foreignSkin = foreignSkinCache.get(playerTexture);
-				}
+				CachedForeignSkin foreignSkin = foreignSkinCache.get(playerTexture);
 				if(foreignSkin != null && foreignSkin.modelKnown != -1) {
 					if(player != null) {
-						synchronized(onlinePlayersCache) {
-							onlinePlayersCache.put(searchUUID,
-									new CachedPlayerSkin(SkinPacketVersionCache.rewriteUUID(foreignSkin.data,
-											searchUUID.getMostSignificantBits(), searchUUID.getLeastSignificantBits()),
-											playerTexture, foreignSkin.modelKnown));
-						}
-						synchronized(foreignSkinCache) {
-							foreignSkinCache.remove(playerTexture);
-						}
+						onlinePlayersCache.put(searchUUID,
+								new CachedPlayerSkin(SkinPacketVersionCache.rewriteUUID(foreignSkin.data,
+										searchUUID.getMostSignificantBits(), searchUUID.getLeastSignificantBits()),
+										playerTexture, foreignSkin.modelKnown));
+						foreignSkinCache.remove(playerTexture);
 					}else {
 						foreignSkin.lastHit = EaglerXVelocityAPIHelper.steadyTimeMillis();
 					}
@@ -336,7 +334,7 @@ public class SkinService implements ISkinService {
 									if(skinObj != null) {
 										JsonElement url = json.get("url");
 										if(url != null) {
-											String urlStr = SkinService.sanitizeTextureURL(url.getAsString());
+											String urlStr = sanitizeTextureURL(url.getAsString());
 											if(urlStr == null) {
 												break;
 											}
@@ -350,19 +348,14 @@ public class SkinService implements ISkinService {
 											}
 											UUID skinUUID = SkinPackets.createEaglerURLSkinUUID(urlStr);
 											
-											CachedForeignSkin foreignSkin;
-											synchronized(foreignSkinCache) {
-												foreignSkin = foreignSkinCache.remove(skinUUID);
-											}
+											CachedForeignSkin foreignSkin = foreignSkinCache.remove(skinUUID);
 											if(foreignSkin != null) {
 												registerTextureToPlayerAssociation(skinUUID, searchUUID);
 												SkinPacketVersionCache rewrite = SkinPacketVersionCache
 														.rewriteUUIDModel(foreignSkin.data,
 																searchUUID.getMostSignificantBits(),
 																searchUUID.getLeastSignificantBits(), model);
-												synchronized(onlinePlayersCache) {
-													onlinePlayersCache.put(searchUUID, new CachedPlayerSkin(rewrite, skinUUID, model));
-												}
+												onlinePlayersCache.put(searchUUID, new CachedPlayerSkin(rewrite, skinUUID, model));
 												sender.sendEaglerMessage(rewrite.get(sender.getEaglerProtocol()));
 												return;
 											}
@@ -395,10 +388,7 @@ public class SkinService implements ISkinService {
 					});
 				}
 			}else {
-				CachedForeignSkin foreignSkin;
-				synchronized(foreignSkinCache) {
-					foreignSkin = foreignSkinCache.get(searchUUID);
-				}
+				CachedForeignSkin foreignSkin = foreignSkinCache.get(searchUUID);
 				if(foreignSkin != null) {
 					foreignSkin.lastHit = EaglerXVelocityAPIHelper.steadyTimeMillis();
 					sender.sendEaglerMessage(foreignSkin.data.get(sender.getEaglerProtocol()));
@@ -426,25 +416,22 @@ public class SkinService implements ISkinService {
 		if(!sender.skinLookupRateLimiter.rateLimit(masterRateLimitPerPlayer)) {
 			return;
 		}
-		CachedForeignSkin foreignSkin;
-		synchronized(foreignSkinCache) {
-			foreignSkin = foreignSkinCache.get(searchUUID);
-		}
+		CachedForeignSkin foreignSkin = foreignSkinCache.get(searchUUID);
 		if(foreignSkin != null) {
 			foreignSkin.lastHit = EaglerXVelocityAPIHelper.steadyTimeMillis();
 			sender.sendEaglerMessage(foreignSkin.data.get(sender.getEaglerProtocol()));
 		}else {
 			Collection<UUID> possiblePlayers;
-			synchronized(onlinePlayersFromTexturesMap) {
-				possiblePlayers = onlinePlayersFromTexturesMap.get(searchUUID);
+			onlinePlayersFromTexturesMapLock.readLock().lock();
+			try {
+				possiblePlayers = new ArrayList<>(onlinePlayersFromTexturesMap.get(searchUUID));
+			}finally {
+				onlinePlayersFromTexturesMapLock.readLock().unlock();
 			}
 			boolean playersExist = possiblePlayers.size() > 0;
 			if(playersExist) {
 				for(UUID uuid : possiblePlayers) {
-					CachedPlayerSkin maybeCachedPacket;
-					synchronized(onlinePlayersCache) {
-						maybeCachedPacket = onlinePlayersCache.get(uuid);
-					}
+					CachedPlayerSkin maybeCachedPacket = onlinePlayersCache.get(uuid);
 					if(maybeCachedPacket != null) {
 						sender.sendEaglerMessage(maybeCachedPacket.data.get(sender.getEaglerProtocol(),
 								searchUUID.getMostSignificantBits(), searchUUID.getLeastSignificantBits()));
@@ -457,10 +444,17 @@ public class SkinService implements ISkinService {
 						searchUUID.getLeastSignificantBits(), 0));
 				return;
 			}
-			if(sender.skinTextureDownloadRateLimiter.rateLimit(config.getSkinRateLimitPlayer()) && !isLimitedAsAntagonist(sender.getUniqueId())) {
-				doAsync(() -> {
-					processResolveURLTextureForForeign(sender, searchUUID, searchUUID, skinURL, -1);
-				});
+			skinURL = sanitizeTextureURL(skinURL);
+			if(skinURL != null) {
+				final String skinURL_ = skinURL;
+				if(sender.skinTextureDownloadRateLimiter.rateLimit(config.getSkinRateLimitPlayer()) && !isLimitedAsAntagonist(sender.getUniqueId())) {
+					doAsync(() -> {
+						processResolveURLTextureForForeign(sender, searchUUID, searchUUID, skinURL_, -1);
+					});
+				}
+			}else {
+				sender.sendEaglerMessage(new SPacketOtherSkinPresetEAG(searchUUID.getMostSignificantBits(),
+						searchUUID.getLeastSignificantBits(), 0));
 			}
 		}
 	}
@@ -475,10 +469,7 @@ public class SkinService implements ISkinService {
 
 						@Override
 						public void accept(byte[] t) {
-							CachedPlayerSkin skin;
-							synchronized(onlinePlayersCache) {
-								skin = onlinePlayersCache.get(onlineCacheUUID);
-							}
+							CachedPlayerSkin skin = onlinePlayersCache.get(onlineCacheUUID);
 							if(skin != null) {
 								initiator.sendEaglerMessage(skin.data.get(initiator.getEaglerProtocol()));
 							}
@@ -505,9 +496,7 @@ public class SkinService implements ISkinService {
 										onlineCacheUUID.getMostSignificantBits(),
 										onlineCacheUUID.getLeastSignificantBits()), null, -1);
 							}
-							synchronized (onlinePlayersCache) {
-								onlinePlayersCache.put(onlineCacheUUID, skin);
-							}
+							onlinePlayersCache.put(onlineCacheUUID, skin);
 							initiator.sendEaglerMessage(skin.data.get(initiator.getEaglerProtocol()));
 						}
 
@@ -532,10 +521,7 @@ public class SkinService implements ISkinService {
 
 						@Override
 						public void accept(byte[] t) {
-							CachedForeignSkin skin;
-							synchronized(foreignSkinCache) {
-								skin = foreignSkinCache.get(foreignCacheUUID);
-							}
+							CachedForeignSkin skin = foreignSkinCache.get(foreignCacheUUID);
 							if(skin != null) {
 								initiator.sendEaglerMessage(skin.data.get(initiator.getEaglerProtocol()));
 							}
@@ -563,9 +549,7 @@ public class SkinService implements ISkinService {
 												foreignCacheUUID.getLeastSignificantBits()),
 										-1);
 							}
-							synchronized (foreignSkinCache) {
-								foreignSkinCache.put(foreignCacheUUID, skin);
-							}
+							foreignSkinCache.put(foreignCacheUUID, skin);
 							initiator.sendEaglerMessage(skin.data.get(initiator.getEaglerProtocol()));
 						}
 
@@ -590,10 +574,7 @@ public class SkinService implements ISkinService {
 						@Override
 						public void accept(CacheFetchedProfile t) {
 							if(t == null || t.texture == null) {
-								CachedPlayerSkin skin;
-								synchronized(onlinePlayersCache) {
-									skin = onlinePlayersCache.get(playerUUID);
-								}
+								CachedPlayerSkin skin = onlinePlayersCache.get(playerUUID);
 								if(skin != null) {
 									initiator.sendEaglerMessage(skin.data.get(initiator.getEaglerProtocol()));
 								}
@@ -625,9 +606,7 @@ public class SkinService implements ISkinService {
 												SkinPackets.getModelId(t.model) == 1 ? 1 : 0),
 										null, -1);
 							}
-							synchronized(onlinePlayersCache) {
-								onlinePlayersCache.put(playerUUID, skin);
-							}
+							onlinePlayersCache.put(playerUUID, skin);
 							initiator.sendEaglerMessage(skin.data.get(initiator.getEaglerProtocol()));
 						}else {
 							processResolveURLTextureForOnline(initiator, playerUUID, t.textureUUID, t.texture,
@@ -656,10 +635,7 @@ public class SkinService implements ISkinService {
 						@Override
 						public void accept(CacheFetchedProfile t) {
 							if(t == null || t.texture == null) {
-								CachedPlayerSkin skin;
-								synchronized(onlinePlayersCache) {
-									skin = onlinePlayersCache.get(t.uuid);
-								}
+								CachedPlayerSkin skin = onlinePlayersCache.get(t.uuid);
 								if(skin != null) {
 									initiator.sendEaglerMessage(skin.data.get(initiator.getEaglerProtocol()));
 								}
@@ -689,9 +665,7 @@ public class SkinService implements ISkinService {
 										mapUUID.getMostSignificantBits(), mapUUID.getLeastSignificantBits(),
 										SkinPackets.getModelId(t.model) == 1 ? 1 : 0), null, -1);
 							}
-							synchronized(onlinePlayersCache) {
-								onlinePlayersCache.put(mapUUID, skin);
-							}
+							onlinePlayersCache.put(mapUUID, skin);
 							initiator.sendEaglerMessage(skin.data.get(initiator.getEaglerProtocol()));
 						}else {
 							processResolveURLTextureForOnline(initiator, mapUUID, t.textureUUID, t.texture,
@@ -720,10 +694,7 @@ public class SkinService implements ISkinService {
 						@Override
 						public void accept(CacheFetchedProfile t) {
 							if(t == null || t.texture == null) {
-								CachedForeignSkin skin;
-								synchronized(foreignSkinCache) {
-									skin = foreignSkinCache.get(playerUUID);
-								}
+								CachedForeignSkin skin = foreignSkinCache.get(playerUUID);
 								if(skin != null) {
 									initiator.sendEaglerMessage(skin.data.get(initiator.getEaglerProtocol()));
 								}
@@ -755,9 +726,7 @@ public class SkinService implements ISkinService {
 												SkinPackets.getModelId(t.model) == 1 ? 1 : 0),
 										-1);
 							}
-							synchronized(foreignSkinCache) {
-								foreignSkinCache.put(playerUUID, skin);
-							}
+							foreignSkinCache.put(playerUUID, skin);
 							initiator.sendEaglerMessage(skin.data.get(initiator.getEaglerProtocol()));
 						}else {
 							processResolveURLTextureForForeign(initiator, playerUUID, t.textureUUID, t.texture,
@@ -777,27 +746,16 @@ public class SkinService implements ISkinService {
 	}
 	
 	public void registerEaglercraftPlayer(UUID clientUUID, SkinPacketVersionCache generatedPacket, int modelId) {
-		synchronized(foreignSkinCache) {
-			foreignSkinCache.remove(clientUUID);
-		}
-		synchronized(onlinePlayersCache) {
-			onlinePlayersCache.put(clientUUID, new CachedPlayerSkin(generatedPacket, null, modelId));
-		}
+		foreignSkinCache.remove(clientUUID);
+		onlinePlayersCache.put(clientUUID, new CachedPlayerSkin(generatedPacket, null, modelId));
 	}
 	
 	public void unregisterPlayer(UUID clientUUID) {
-		CachedPlayerSkin data;
-		synchronized(onlinePlayersCache) {
-			data = onlinePlayersCache.remove(clientUUID);
-		}
+		CachedPlayerSkin data = onlinePlayersCache.remove(clientUUID);
 		if(data != null) {
-			synchronized(foreignSkinCache) {
-				foreignSkinCache.put(clientUUID, new CachedForeignSkin(clientUUID, data.data, data.modelId));
-			}
+			foreignSkinCache.put(clientUUID, new CachedForeignSkin(clientUUID, data.data, data.modelId));
 			if(data.textureUUID != null) {
-				synchronized(foreignSkinCache) {
-					foreignSkinCache.put(data.textureUUID, new CachedForeignSkin(data.textureUUID, data.data, data.modelId));
-				}
+				foreignSkinCache.put(data.textureUUID, new CachedForeignSkin(data.textureUUID, data.data, data.modelId));
 			}
 			deletePlayerTextureAssociation(clientUUID, data.textureUUID);
 		}else {
@@ -807,94 +765,77 @@ public class SkinService implements ISkinService {
 	
 	private void deletePlayerTextureAssociation(UUID clientUUID, UUID textureUUID) {
 		if(textureUUID != null) {
-			synchronized(onlinePlayersToTexturesMap) {
-				onlinePlayersToTexturesMap.remove(clientUUID);
-			}
-			synchronized(onlinePlayersFromTexturesMap) {
+			onlinePlayersToTexturesMap.remove(clientUUID);
+			onlinePlayersFromTexturesMapLock.writeLock().lock();
+			try {
 				onlinePlayersFromTexturesMap.remove(textureUUID, clientUUID);
+			}finally {
+				onlinePlayersFromTexturesMapLock.writeLock().unlock();
 			}
 		}else {
-			UUID removedUUID;
-			synchronized(onlinePlayersToTexturesMap) {
-				removedUUID = onlinePlayersToTexturesMap.remove(clientUUID);
-			}
+			UUID removedUUID = onlinePlayersToTexturesMap.remove(clientUUID);
 			if(removedUUID != null) {
-				synchronized(onlinePlayersFromTexturesMap) {
+				onlinePlayersFromTexturesMapLock.writeLock().lock();
+				try {
 					onlinePlayersFromTexturesMap.remove(removedUUID, clientUUID);
+				}finally {
+					onlinePlayersFromTexturesMapLock.writeLock().unlock();
 				}
 			}
 		}
 	}
 	
 	public void registerTextureToPlayerAssociation(UUID textureUUID, UUID playerUUID) {
-		synchronized(onlinePlayersFromTexturesMap) {
+		onlinePlayersFromTexturesMapLock.writeLock().lock();
+		try {
 			onlinePlayersFromTexturesMap.put(textureUUID, playerUUID);
+		}finally {
+			onlinePlayersFromTexturesMapLock.writeLock().unlock();
 		}
-		synchronized(onlinePlayersToTexturesMap) {
-			onlinePlayersToTexturesMap.put(playerUUID, textureUUID);
-		}
-		CachedForeignSkin foreign;
-		synchronized(foreignSkinCache) {
-			foreign = foreignSkinCache.remove(textureUUID);
-		}
+		onlinePlayersToTexturesMap.put(playerUUID, textureUUID);
+		CachedForeignSkin foreign = foreignSkinCache.remove(textureUUID);
 		if(foreign != null) {
-			synchronized(onlinePlayersCache) {
-				onlinePlayersCache.put(playerUUID, new CachedPlayerSkin(foreign.data, textureUUID, foreign.modelKnown));
-			}
+			onlinePlayersCache.put(playerUUID, new CachedPlayerSkin(foreign.data, textureUUID, foreign.modelKnown));
 		}
 	}
 
 	public void processForceSkin(UUID playerUUID, EaglerPlayerData eaglerHandler) {
-		CachedPlayerSkin maybeCachedPacket;
-		synchronized(onlinePlayersCache) {
-			maybeCachedPacket = onlinePlayersCache.get(playerUUID);
-		}
+		CachedPlayerSkin maybeCachedPacket = onlinePlayersCache.get(playerUUID);
 		
 		if(maybeCachedPacket != null) {
 			eaglerHandler.sendEaglerMessage(maybeCachedPacket.data.getForceClientV4());
 		}else {
-			UUID playerTexture;
-			synchronized(onlinePlayersToTexturesMap) {
-				playerTexture = onlinePlayersToTexturesMap.get(playerUUID);
-			}
+			UUID playerTexture = onlinePlayersToTexturesMap.get(playerUUID);
 			if(playerTexture != null) {
 				Collection<UUID> possiblePlayers;
-				synchronized(onlinePlayersFromTexturesMap) {
-					possiblePlayers = onlinePlayersFromTexturesMap.get(playerTexture);
+				onlinePlayersFromTexturesMapLock.readLock().lock();
+				try {
+					possiblePlayers = new ArrayList<>(onlinePlayersFromTexturesMap.get(playerTexture));
+				}finally {
+					onlinePlayersFromTexturesMapLock.readLock().unlock();
 				}
 				boolean playersExist = possiblePlayers.size() > 0;
 				if(playersExist) {
 					for(UUID uuid : possiblePlayers) {
-						synchronized(onlinePlayersCache) {
-							maybeCachedPacket = onlinePlayersCache.get(uuid);
-						}
+						maybeCachedPacket = onlinePlayersCache.get(uuid);
 						if(maybeCachedPacket != null) {
 							SkinPacketVersionCache rewritten = SkinPacketVersionCache.rewriteUUID(
 									maybeCachedPacket.data, playerUUID.getMostSignificantBits(),
 									playerUUID.getLeastSignificantBits());
-							synchronized(onlinePlayersCache) {
-								onlinePlayersCache.put(playerUUID, new CachedPlayerSkin(rewritten,
-										maybeCachedPacket.textureUUID, maybeCachedPacket.modelId));
-							}
+							onlinePlayersCache.put(playerUUID, new CachedPlayerSkin(rewritten,
+									maybeCachedPacket.textureUUID, maybeCachedPacket.modelId));
 							eaglerHandler.sendEaglerMessage(rewritten.getForceClientV4());
 							return;
 						}
 					}
 				}
-				CachedForeignSkin foreignSkin;
-				synchronized(foreignSkinCache) {
-					foreignSkin = foreignSkinCache.get(playerTexture);
-				}
+				CachedForeignSkin foreignSkin = foreignSkinCache.get(playerTexture);
 				if(foreignSkin != null && foreignSkin.modelKnown != -1) {
-					synchronized(onlinePlayersCache) {
-						onlinePlayersCache.put(playerUUID,
-								new CachedPlayerSkin(SkinPacketVersionCache.rewriteUUID(foreignSkin.data,
-										playerUUID.getMostSignificantBits(), playerUUID.getLeastSignificantBits()),
-										playerTexture, foreignSkin.modelKnown));
-					}
-					synchronized(foreignSkinCache) {
-						foreignSkinCache.remove(playerTexture);
-					}
+					onlinePlayersCache.put(playerUUID,
+							new CachedPlayerSkin(SkinPacketVersionCache.rewriteUUID(foreignSkin.data,
+									playerUUID.getMostSignificantBits(), playerUUID.getLeastSignificantBits()),
+									playerTexture, foreignSkin.modelKnown));
+					foreignSkinCache.remove(playerTexture);
 					eaglerHandler.sendEaglerMessage(foreignSkin.data.getForceClientV4());
 					return;
 				}
@@ -913,7 +854,7 @@ public class SkinService implements ISkinService {
 								if(skinObj != null) {
 									JsonElement url = json.get("url");
 									if(url != null) {
-										String urlStr = SkinService.sanitizeTextureURL(url.getAsString());
+										String urlStr = sanitizeTextureURL(url.getAsString());
 										if(urlStr == null) {
 											break;
 										}
@@ -927,19 +868,14 @@ public class SkinService implements ISkinService {
 										}
 										UUID skinUUID = SkinPackets.createEaglerURLSkinUUID(urlStr);
 										
-										CachedForeignSkin foreignSkin;
-										synchronized(foreignSkinCache) {
-											foreignSkin = foreignSkinCache.remove(skinUUID);
-										}
+										CachedForeignSkin foreignSkin = foreignSkinCache.remove(skinUUID);
 										if(foreignSkin != null) {
 											registerTextureToPlayerAssociation(skinUUID, playerUUID);
 											SkinPacketVersionCache rewrite = SkinPacketVersionCache
 													.rewriteUUIDModel(foreignSkin.data,
 															playerUUID.getMostSignificantBits(),
 															playerUUID.getLeastSignificantBits(), model);
-											synchronized(onlinePlayersCache) {
-												onlinePlayersCache.put(playerUUID, new CachedPlayerSkin(rewrite, skinUUID, model));
-											}
+											onlinePlayersCache.put(playerUUID, new CachedPlayerSkin(rewrite, skinUUID, model));
 											eaglerHandler.sendEaglerMessage(rewrite.getForceClientV4());
 											return;
 										}
@@ -967,10 +903,7 @@ public class SkinService implements ISkinService {
 					}
 				});
 			}else {
-				CachedForeignSkin foreignSkin;
-				synchronized(foreignSkinCache) {
-					foreignSkin = foreignSkinCache.get(playerUUID);
-				}
+				CachedForeignSkin foreignSkin = foreignSkinCache.get(playerUUID);
 				if(foreignSkin != null) {
 					foreignSkin.lastHit = EaglerXVelocityAPIHelper.steadyTimeMillis();
 					eaglerHandler.sendEaglerMessage(foreignSkin.data.getForceClientV4());
@@ -997,10 +930,7 @@ public class SkinService implements ISkinService {
 
 						@Override
 						public void accept(byte[] t) {
-							CachedPlayerSkin skin;
-							synchronized(onlinePlayersCache) {
-								skin = onlinePlayersCache.get(onlineCacheUUID);
-							}
+							CachedPlayerSkin skin = onlinePlayersCache.get(onlineCacheUUID);
 							if(skin != null) {
 								initiator.sendEaglerMessage(skin.data.getForceClientV4());
 							}
@@ -1027,9 +957,7 @@ public class SkinService implements ISkinService {
 										onlineCacheUUID.getMostSignificantBits(),
 										onlineCacheUUID.getLeastSignificantBits()), null, -1);
 							}
-							synchronized (onlinePlayersCache) {
-								onlinePlayersCache.put(onlineCacheUUID, skin);
-							}
+							onlinePlayersCache.put(onlineCacheUUID, skin);
 							initiator.sendEaglerMessage(skin.data.getForceClientV4());
 						}
 
@@ -1054,10 +982,7 @@ public class SkinService implements ISkinService {
 
 						@Override
 						public void accept(byte[] t) {
-							CachedForeignSkin skin;
-							synchronized(foreignSkinCache) {
-								skin = foreignSkinCache.get(foreignCacheUUID);
-							}
+							CachedForeignSkin skin = foreignSkinCache.get(foreignCacheUUID);
 							if(skin != null) {
 								initiator.sendEaglerMessage(skin.data.getForceClientV4());
 							}
@@ -1085,9 +1010,7 @@ public class SkinService implements ISkinService {
 												foreignCacheUUID.getLeastSignificantBits()),
 										-1);
 							}
-							synchronized (foreignSkinCache) {
-								foreignSkinCache.put(foreignCacheUUID, skin);
-							}
+							foreignSkinCache.put(foreignCacheUUID, skin);
 							initiator.sendEaglerMessage(skin.data.getForceClientV4());
 						}
 
@@ -1112,10 +1035,7 @@ public class SkinService implements ISkinService {
 						@Override
 						public void accept(CacheFetchedProfile t) {
 							if(t == null || t.texture == null) {
-								CachedPlayerSkin skin;
-								synchronized(onlinePlayersCache) {
-									skin = onlinePlayersCache.get(playerUUID);
-								}
+								CachedPlayerSkin skin = onlinePlayersCache.get(playerUUID);
 								if(skin != null) {
 									initiator.sendEaglerMessage(skin.data.getForceClientV4());
 								}
@@ -1147,9 +1067,7 @@ public class SkinService implements ISkinService {
 												SkinPackets.getModelId(t.model) == 1 ? 1 : 0),
 										null, -1);
 							}
-							synchronized(onlinePlayersCache) {
-								onlinePlayersCache.put(playerUUID, skin);
-							}
+							onlinePlayersCache.put(playerUUID, skin);
 							initiator.sendEaglerMessage(skin.data.getForceClientV4());
 						}else {
 							processResolveURLTextureForOnlineToForce(initiator, playerUUID, t.textureUUID, t.texture,
@@ -1178,10 +1096,7 @@ public class SkinService implements ISkinService {
 						@Override
 						public void accept(CacheFetchedProfile t) {
 							if(t == null || t.texture == null) {
-								CachedPlayerSkin skin;
-								synchronized(onlinePlayersCache) {
-									skin = onlinePlayersCache.get(t.uuid);
-								}
+								CachedPlayerSkin skin = onlinePlayersCache.get(t.uuid);
 								if(skin != null) {
 									initiator.sendEaglerMessage(skin.data.getForceClientV4());
 								}
@@ -1211,9 +1126,7 @@ public class SkinService implements ISkinService {
 										mapUUID.getMostSignificantBits(), mapUUID.getLeastSignificantBits(),
 										SkinPackets.getModelId(t.model) == 1 ? 1 : 0), null, -1);
 							}
-							synchronized(onlinePlayersCache) {
-								onlinePlayersCache.put(mapUUID, skin);
-							}
+							onlinePlayersCache.put(mapUUID, skin);
 							initiator.sendEaglerMessage(skin.data.getForceClientV4());
 						}else {
 							processResolveURLTextureForOnlineToForce(initiator, mapUUID, t.textureUUID, t.texture,
@@ -1242,10 +1155,7 @@ public class SkinService implements ISkinService {
 						@Override
 						public void accept(CacheFetchedProfile t) {
 							if(t == null || t.texture == null) {
-								CachedForeignSkin skin;
-								synchronized(foreignSkinCache) {
-									skin = foreignSkinCache.get(playerUUID);
-								}
+								CachedForeignSkin skin = foreignSkinCache.get(playerUUID);
 								if(skin != null) {
 									initiator.sendEaglerMessage(skin.data.getForceClientV4());
 								}
@@ -1277,9 +1187,7 @@ public class SkinService implements ISkinService {
 												SkinPackets.getModelId(t.model) == 1 ? 1 : 0),
 										-1);
 							}
-							synchronized(foreignSkinCache) {
-								foreignSkinCache.put(playerUUID, skin);
-							}
+							foreignSkinCache.put(playerUUID, skin);
 							initiator.sendEaglerMessage(skin.data.getForceClientV4());
 						}else {
 							processResolveURLTextureForForeignToForce(initiator, playerUUID, t.textureUUID, t.texture,
@@ -1301,12 +1209,16 @@ public class SkinService implements ISkinService {
 	public void flush() {
 		long millis = EaglerXVelocityAPIHelper.steadyTimeMillis();
 		
-		synchronized(foreignSkinCache) {
-			Iterator<CachedForeignSkin> itr = foreignSkinCache.values().iterator();
-			while(itr.hasNext()) {
-				if(millis - itr.next().lastHit > 900000l) { // 15 minutes
-					itr.remove();
-				}
+		final List<UUID> foreignSkinCleanup = new ArrayList<>(4);
+		foreignSkinCache.entrySet().forEach((etr) -> {
+			if(millis - etr.getValue().lastHit > 900000l) { // 15 minutes
+				foreignSkinCleanup.add(etr.getKey());
+			}
+		});
+		
+		if(!foreignSkinCleanup.isEmpty()) {
+			for(UUID uuid : foreignSkinCleanup) {
+				foreignSkinCache.remove(uuid);
 			}
 		}
 		
@@ -1357,7 +1269,8 @@ public class SkinService implements ISkinService {
 		elapsedCooldown /= cooldownPeriod;
 		if(elapsedCooldown > 0) {
 			antagonistCooldown += elapsedCooldown * cooldownPeriod;
-			synchronized(antagonists) {
+			antagonistsLock.writeLock().lock();
+			try {
 				Iterator<UUID> itr = antagonists.keySet().iterator();
 				while(itr.hasNext()) {
 					UUID key = itr.next();
@@ -1368,6 +1281,8 @@ public class SkinService implements ISkinService {
 						antagonists.put(key, i);
 					}
 				}
+			}finally {
+				antagonistsLock.writeLock().unlock();
 			}
 		}
 		
@@ -1375,10 +1290,7 @@ public class SkinService implements ISkinService {
 	}
 
 	public SkinPacketVersionCache getSkin(UUID playerUUID) {
-		CachedPlayerSkin skin;
-		synchronized(onlinePlayersCache) {
-			skin = onlinePlayersCache.get(playerUUID);
-		}
+		CachedPlayerSkin skin = onlinePlayersCache.get(playerUUID);
 		return skin != null ? skin.data : null;
 	}
 
@@ -1393,25 +1305,26 @@ public class SkinService implements ISkinService {
 	private boolean isLimitedAsAntagonist(UUID uuid) {
 		int limit = EaglerXVelocity.getEagler().getConfig().getAntagonistsRateLimit();
 		limit += limit >> 1;
-		synchronized(antagonists) {
-			int i = antagonists.getInt(uuid);
-			return i != antagonists.defaultReturnValue() && i > limit;
+		int i;
+		antagonistsLock.readLock().lock();
+		try {
+			i = antagonists.getInt(uuid);
+		}finally {
+			antagonistsLock.readLock().unlock();
 		}
+		return i != antagonists.defaultReturnValue() && i > limit;
 	}
 	
 	private void resetMaps() {
-		synchronized(onlinePlayersCache) {
-			onlinePlayersCache.clear();
-		}
-		synchronized(onlinePlayersFromTexturesMap) {
+		onlinePlayersCache.clear();
+		onlinePlayersFromTexturesMapLock.writeLock().lock();
+		try {
 			onlinePlayersFromTexturesMap.clear();
+		}finally {
+			onlinePlayersFromTexturesMapLock.writeLock().unlock();
 		}
-		synchronized(onlinePlayersToTexturesMap) {
-			onlinePlayersToTexturesMap.clear();
-		}
-		synchronized(foreignSkinCache) {
-			foreignSkinCache.clear();
-		}
+		onlinePlayersToTexturesMap.clear();
+		foreignSkinCache.clear();
 		synchronized(pendingTextures) {
 			pendingTextures.clear();
 		}
@@ -1421,8 +1334,11 @@ public class SkinService implements ISkinService {
 		synchronized(pendingNameLookups) {
 			pendingNameLookups.clear();
 		}
-		synchronized(antagonists) {
+		antagonistsLock.writeLock().lock();
+		try {
 			antagonists.clear();
+		}finally {
+			antagonistsLock.writeLock().unlock();
 		}
 	}
 	
@@ -1439,7 +1355,7 @@ public class SkinService implements ISkinService {
 				return null;
 			}
 			String host = uri.getHost();
-			if(host == null) {
+			if(host == null || !EaglerXVelocity.getEagler().getConfig().isValidSkinHost(host)) {
 				return null;
 			}
 			scheme = scheme.toLowerCase();
diff --git a/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/skins/SkinServiceOffline.java b/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/skins/SkinServiceOffline.java
index 6591dfc8..b26af62a 100644
--- a/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/skins/SkinServiceOffline.java
+++ b/gateway/EaglercraftXVelocity/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_velocity/skins/SkinServiceOffline.java
@@ -1,20 +1,15 @@
 package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.skins;
 
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
 import java.util.UUID;
-
-import com.google.common.collect.Multimap;
-import com.google.common.collect.MultimapBuilder;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
 import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPlayerData;
 import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkinPresetEAG;
 import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
 
 /**
- * Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
+ * Copyright (c) 2022-2025 lax1dude. All Rights Reserved.
  * 
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@@ -44,22 +39,15 @@ public class SkinServiceOffline implements ISkinService {
 
 	}
 
-	private final Map<UUID, CachedSkin> skinCache = new HashMap<>();
-
-	private final Multimap<UUID, UUID> onlinePlayersFromTexturesMap = MultimapBuilder.hashKeys().hashSetValues().build();
+	private final ConcurrentMap<UUID, CachedSkin> skinCache = new ConcurrentHashMap<>();
 
 	public void init(String uri, String driverClass, String driverPath, int keepObjectsDays, int keepProfilesDays,
 			int maxObjects, int maxProfiles) {
-		synchronized(skinCache) {
-			skinCache.clear();
-		}
+		skinCache.clear();
 	}
 
 	public void processGetOtherSkin(UUID searchUUID, EaglerPlayerData sender) {
-		CachedSkin cached;
-		synchronized(skinCache) {
-			cached = skinCache.get(searchUUID);
-		}
+		CachedSkin cached = skinCache.get(searchUUID);
 		if(cached != null) {
 			sender.sendEaglerMessage(cached.packet.get(sender.getEaglerProtocol()));
 		}else {
@@ -69,24 +57,6 @@ public class SkinServiceOffline implements ISkinService {
 	}
 
 	public void processGetOtherSkin(UUID searchUUID, String skinURL, EaglerPlayerData sender) {
-		Collection<UUID> uuids;
-		synchronized(onlinePlayersFromTexturesMap) {
-			uuids = onlinePlayersFromTexturesMap.get(searchUUID);
-		}
-		if(uuids.size() > 0) {
-			CachedSkin cached;
-			synchronized(skinCache) {
-				Iterator<UUID> uuidItr = uuids.iterator();
-				while(uuidItr.hasNext()) {
-					cached = skinCache.get(uuidItr.next());
-					if(cached != null) {
-						sender.sendEaglerMessage(cached.packet.get(sender.getEaglerProtocol(),
-								searchUUID.getMostSignificantBits(), searchUUID.getLeastSignificantBits()));
-						return;
-					}
-				}
-			}
-		}
 		if(skinURL.startsWith("eagler://")) { // customs skulls from exported singleplayer worlds
 			sender.sendEaglerMessage(new SPacketOtherSkinPresetEAG(searchUUID.getMostSignificantBits(),
 					searchUUID.getLeastSignificantBits(), 0));
@@ -97,28 +67,21 @@ public class SkinServiceOffline implements ISkinService {
 	}
 
 	public void registerEaglercraftPlayer(UUID clientUUID, SkinPacketVersionCache generatedPacket, int modelId) {
-		synchronized(skinCache) {
-			skinCache.put(clientUUID, new CachedSkin(clientUUID, generatedPacket));
-		}
+		skinCache.put(clientUUID, new CachedSkin(clientUUID, generatedPacket));
 	}
 
 	public void unregisterPlayer(UUID clientUUID) {
-		synchronized(skinCache) {
-			skinCache.remove(clientUUID);
-		}
+		skinCache.remove(clientUUID);
+	}
+
+	public void registerTextureToPlayerAssociation(String textureURL, UUID playerUUID) {
 	}
 
 	public void registerTextureToPlayerAssociation(UUID textureUUID, UUID playerUUID) {
-		synchronized(onlinePlayersFromTexturesMap) {
-			onlinePlayersFromTexturesMap.put(textureUUID, playerUUID);
-		}
 	}
 
 	public void processForceSkin(UUID playerUUID, EaglerPlayerData initialHandler) {
-		CachedSkin cached;
-		synchronized(skinCache) {
-			cached = skinCache.get(playerUUID);
-		}
+		CachedSkin cached = skinCache.get(playerUUID);
 		if(cached != null) {
 			initialHandler.sendEaglerMessage(cached.packet.getForceClientV4());
 		}
@@ -129,16 +92,11 @@ public class SkinServiceOffline implements ISkinService {
 	}
 
 	public void shutdown() {
-		synchronized(skinCache) {
-			skinCache.clear();
-		}
+		skinCache.clear();
 	}
 
 	public SkinPacketVersionCache getSkin(UUID playerUUID) {
-		CachedSkin cached;
-		synchronized(skinCache) {
-			cached = skinCache.get(playerUUID);
-		}
+		CachedSkin cached = skinCache.get(playerUUID);
 		return cached != null ? cached.packet : null;
 	}
 
diff --git a/gateway/EaglercraftXVelocity/src/main/resources/velocity-plugin.json b/gateway/EaglercraftXVelocity/src/main/resources/velocity-plugin.json
index 6d8420e4..0794334c 100644
--- a/gateway/EaglercraftXVelocity/src/main/resources/velocity-plugin.json
+++ b/gateway/EaglercraftXVelocity/src/main/resources/velocity-plugin.json
@@ -1 +1 @@
-{"id":"eaglerxvelocity","name":"EaglercraftXVelocity","version":"1.1.5","description":"Plugin to allow EaglercraftX 1.8 players to join your network, or allow EaglercraftX 1.8 players to use your network as a proxy to join other networks","authors":["lax1dude", "ayunami2000"],"dependencies":[],"main":"net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocity"}
\ No newline at end of file
+{"id":"eaglerxvelocity","name":"EaglercraftXVelocity","version":"1.1.6","description":"Plugin to allow EaglercraftX 1.8 players to join your network, or allow EaglercraftX 1.8 players to use your network as a proxy to join other networks","authors":["lax1dude", "ayunami2000"],"dependencies":[],"main":"net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocity"}
\ No newline at end of file
diff --git a/gateway_version_velocity b/gateway_version_velocity
index 314c3d71..ab679818 100644
--- a/gateway_version_velocity
+++ b/gateway_version_velocity
@@ -1 +1 @@
-1.1.5
\ No newline at end of file
+1.1.6
\ No newline at end of file