diff --git a/bootstrap/bungeecord/build.gradle.kts b/bootstrap/bungeecord/build.gradle.kts
index c9ae080d9..fbf77f858 100644
--- a/bootstrap/bungeecord/build.gradle.kts
+++ b/bootstrap/bungeecord/build.gradle.kts
@@ -5,10 +5,11 @@ dependencies {
 }
 
 platformRelocate("net.md_5.bungee.jni")
-platformRelocate("com.fasterxml.jackson")
 platformRelocate("io.netty.channel.kqueue") // This is not used because relocating breaks natives, but we must include it or else we get ClassDefNotFound
 platformRelocate("net.kyori")
 platformRelocate("org.yaml") // Broken as of 1.20
+platformRelocate("org.spongepowered")
+platformRelocate("io.leangen.geantyref")
 platformRelocate("org.bstats")
 
 // These dependencies are already present on the platform
diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java
index 2a356140b..369788674 100644
--- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java
+++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java
@@ -39,7 +39,7 @@ import org.geysermc.geyser.api.command.Command;
 import org.geysermc.geyser.api.extension.Extension;
 import org.geysermc.geyser.api.util.PlatformType;
 import org.geysermc.geyser.command.GeyserCommandManager;
-import org.geysermc.geyser.configuration.ConfigLoaderTemp;
+import org.geysermc.geyser.configuration.ConfigLoader;
 import org.geysermc.geyser.configuration.GeyserPluginConfig;
 import org.geysermc.geyser.dump.BootstrapDumpInfo;
 import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
@@ -293,7 +293,7 @@ public class GeyserBungeePlugin extends Plugin implements GeyserPluginBootstrap
                 //noinspection ResultOfMethodCallIgnored
                 getDataFolder().mkdir();
             }
-            this.geyserConfig = ConfigLoaderTemp.load(new File(getDataFolder(), "config.yml"), GeyserPluginConfig.class);
+            this.geyserConfig = ConfigLoader.load(new File(getDataFolder(), "config.yml"), GeyserPluginConfig.class);
         } catch (IOException ex) {
             geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
             return false;
diff --git a/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricDumpInfo.java b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricDumpInfo.java
index 75da9125f..c4efdf9ff 100644
--- a/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricDumpInfo.java
+++ b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricDumpInfo.java
@@ -25,6 +25,7 @@
 
 package org.geysermc.geyser.platform.fabric;
 
+import com.google.gson.annotations.JsonAdapter;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 import net.fabricmc.api.EnvType;
@@ -48,7 +49,7 @@ public class GeyserFabricDumpInfo extends BootstrapDumpInfo {
     private final String minecraftVersion;
     private final EnvType environmentType;
 
-    @AsteriskSerializer.Asterisk(isIp = true)
+    @JsonAdapter(value = AsteriskSerializer.class)
     private final String serverIP;
     private final int serverPort;
     private final boolean onlineMode;
diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeDumpInfo.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeDumpInfo.java
index 623f68d3a..c8104f2d4 100644
--- a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeDumpInfo.java
+++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeDumpInfo.java
@@ -25,6 +25,7 @@
 
 package org.geysermc.geyser.platform.neoforge;
 
+import com.google.gson.annotations.JsonAdapter;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 import net.minecraft.server.MinecraftServer;
@@ -47,7 +48,7 @@ public class GeyserNeoForgeDumpInfo extends BootstrapDumpInfo {
     private final String minecraftVersion;
     private final Dist dist;
 
-    @AsteriskSerializer.Asterisk(isIp = true)
+    @JsonAdapter(value = AsteriskSerializer.class)
     private final String serverIP;
     private final int serverPort;
     private final boolean onlineMode;
@@ -81,4 +82,4 @@ public class GeyserNeoForgeDumpInfo extends BootstrapDumpInfo {
         public String version;
         public String url;
     }
-}
\ No newline at end of file
+}
diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java
index c8597bc54..7410b359c 100644
--- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java
@@ -44,7 +44,7 @@ import org.geysermc.geyser.api.command.Command;
 import org.geysermc.geyser.api.extension.Extension;
 import org.geysermc.geyser.command.GeyserCommand;
 import org.geysermc.geyser.command.GeyserCommandManager;
-import org.geysermc.geyser.configuration.ConfigLoaderTemp;
+import org.geysermc.geyser.configuration.ConfigLoader;
 import org.geysermc.geyser.configuration.GeyserPluginConfig;
 import org.geysermc.geyser.dump.BootstrapDumpInfo;
 import org.geysermc.geyser.level.WorldManager;
@@ -286,7 +286,7 @@ public abstract class GeyserModBootstrap implements GeyserPluginBootstrap {
                 dataFolder.toFile().mkdir();
             }
 
-            this.geyserConfig = ConfigLoaderTemp.load(dataFolder.resolve("config.yml").toFile(), GeyserPluginConfig.class);
+            this.geyserConfig = ConfigLoader.load(dataFolder.resolve("config.yml").toFile(), GeyserPluginConfig.class);
             return true;
         } catch (IOException ex) {
             geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts
index 810bbc5b2..adac8d2a0 100644
--- a/bootstrap/spigot/build.gradle.kts
+++ b/bootstrap/spigot/build.gradle.kts
@@ -33,7 +33,6 @@ dependencies {
 }
 
 platformRelocate("it.unimi.dsi.fastutil")
-platformRelocate("com.fasterxml.jackson")
 // Relocate net.kyori but exclude the component logger
 platformRelocate("net.kyori", "net.kyori.adventure.text.logger.slf4j.ComponentLogger")
 platformRelocate("org.objectweb.asm")
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotDumpInfo.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotDumpInfo.java
index 329663709..6e063f415 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotDumpInfo.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotDumpInfo.java
@@ -25,6 +25,7 @@
 
 package org.geysermc.geyser.platform.spigot;
 
+import com.google.gson.annotations.JsonAdapter;
 import lombok.Getter;
 import org.bukkit.Bukkit;
 import org.bukkit.plugin.Plugin;
@@ -42,7 +43,7 @@ public class GeyserSpigotDumpInfo extends BootstrapDumpInfo {
     private final String platformAPIVersion;
     private final boolean onlineMode;
 
-    @AsteriskSerializer.Asterisk(isIp = true)
+    @JsonAdapter(value = AsteriskSerializer.class)
     private final String serverIP;
     private final int serverPort;
     private final List<PluginInfo> plugins;
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java
index 1eccca30e..7686333ef 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java
@@ -53,7 +53,7 @@ import org.geysermc.geyser.api.command.Command;
 import org.geysermc.geyser.api.extension.Extension;
 import org.geysermc.geyser.api.util.PlatformType;
 import org.geysermc.geyser.command.GeyserCommandManager;
-import org.geysermc.geyser.configuration.ConfigLoaderTemp;
+import org.geysermc.geyser.configuration.ConfigLoader;
 import org.geysermc.geyser.configuration.GeyserPluginConfig;
 import org.geysermc.geyser.dump.BootstrapDumpInfo;
 import org.geysermc.geyser.level.WorldManager;
@@ -485,7 +485,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserPluginBootst
                 //noinspection ResultOfMethodCallIgnored
                 getDataFolder().mkdir();
             }
-            this.geyserConfig = ConfigLoaderTemp.load(new File(getDataFolder(), "config.yml"), GeyserPluginConfig.class);
+            this.geyserConfig = ConfigLoader.load(new File(getDataFolder(), "config.yml"), GeyserPluginConfig.class);
         } catch (IOException ex) {
             geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
             ex.printStackTrace();
diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java
index 0037e376d..ea29e5e17 100644
--- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java
+++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java
@@ -38,7 +38,7 @@ import org.geysermc.geyser.GeyserBootstrap;
 import org.geysermc.geyser.GeyserImpl;
 import org.geysermc.geyser.api.util.PlatformType;
 import org.geysermc.geyser.command.GeyserCommandManager;
-import org.geysermc.geyser.configuration.ConfigLoaderTemp;
+import org.geysermc.geyser.configuration.ConfigLoader;
 import org.geysermc.geyser.configuration.GeyserConfig;
 import org.geysermc.geyser.configuration.GeyserRemoteConfig;
 import org.geysermc.geyser.dump.BootstrapDumpInfo;
@@ -160,7 +160,7 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
     @Override
     public void onGeyserEnable() {
         try {
-            geyserConfig = ConfigLoaderTemp.load(new File(configFilename), GeyserRemoteConfig.class, this::handleArgsConfigOptions);
+            geyserConfig = ConfigLoader.load(new File(configFilename), GeyserRemoteConfig.class, this::handleArgsConfigOptions);
         } catch (IOException ex) {
             geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
             if (gui == null) {
diff --git a/bootstrap/velocity/build.gradle.kts b/bootstrap/velocity/build.gradle.kts
index 103e40dac..a50d922b9 100644
--- a/bootstrap/velocity/build.gradle.kts
+++ b/bootstrap/velocity/build.gradle.kts
@@ -5,10 +5,11 @@ dependencies {
     compileOnlyApi(libs.velocity.api)
 }
 
-platformRelocate("com.fasterxml.jackson")
 platformRelocate("it.unimi.dsi.fastutil")
 platformRelocate("net.kyori.adventure.text.serializer.gson.legacyimpl")
 platformRelocate("org.yaml")
+platformRelocate("org.spongepowered")
+platformRelocate("io.leangen.geantyref")
 platformRelocate("org.bstats")
 
 exclude("com.google.*:*")
diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityDumpInfo.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityDumpInfo.java
index 45eb7abb9..6bc309f29 100644
--- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityDumpInfo.java
+++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityDumpInfo.java
@@ -25,6 +25,7 @@
 
 package org.geysermc.geyser.platform.velocity;
 
+import com.google.gson.annotations.JsonAdapter;
 import com.velocitypowered.api.plugin.PluginContainer;
 import com.velocitypowered.api.proxy.ProxyServer;
 import lombok.Getter;
@@ -42,7 +43,7 @@ public class GeyserVelocityDumpInfo extends BootstrapDumpInfo {
     private final String platformVendor;
     private final boolean onlineMode;
 
-    @AsteriskSerializer.Asterisk(isIp = true)
+    @JsonAdapter(value = AsteriskSerializer.class)
     private final String serverIP;
     private final int serverPort;
     private final List<PluginInfo> plugins;
diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java
index edda21d28..08b2c28d7 100644
--- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java
+++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java
@@ -46,7 +46,7 @@ import org.geysermc.geyser.api.command.Command;
 import org.geysermc.geyser.api.extension.Extension;
 import org.geysermc.geyser.api.util.PlatformType;
 import org.geysermc.geyser.command.GeyserCommandManager;
-import org.geysermc.geyser.configuration.ConfigLoaderTemp;
+import org.geysermc.geyser.configuration.ConfigLoader;
 import org.geysermc.geyser.configuration.GeyserPluginConfig;
 import org.geysermc.geyser.dump.BootstrapDumpInfo;
 import org.geysermc.geyser.network.GameProtocol;
@@ -248,7 +248,7 @@ public class GeyserVelocityPlugin implements GeyserPluginBootstrap {
                 //noinspection ResultOfMethodCallIgnored
                 configFolder.toFile().mkdirs();
             }
-            this.geyserConfig = ConfigLoaderTemp.load(configFolder.resolve("config.yml").toFile(), GeyserPluginConfig.class);
+            this.geyserConfig = ConfigLoader.load(configFolder.resolve("config.yml").toFile(), GeyserPluginConfig.class);
         } catch (IOException ex) {
             geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
             ex.printStackTrace();
diff --git a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyDumpInfo.java b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyDumpInfo.java
index 0bfc9d022..4478d11ef 100644
--- a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyDumpInfo.java
+++ b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyDumpInfo.java
@@ -24,6 +24,7 @@
  */
 package org.geysermc.geyser.platform.viaproxy;
 
+import com.google.gson.annotations.JsonAdapter;
 import lombok.Getter;
 import net.raphimc.viaproxy.ViaProxy;
 import net.raphimc.viaproxy.plugins.ViaProxyPlugin;
@@ -41,7 +42,7 @@ public class GeyserViaProxyDumpInfo extends BootstrapDumpInfo {
     private final String platformVersion;
     private final boolean onlineMode;
 
-    @AsteriskSerializer.Asterisk(isIp = true)
+    @JsonAdapter(value = AsteriskSerializer.class)
     private final String serverIP;
     private final int serverPort;
     private final List<PluginInfo> plugins;
diff --git a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java
index 04276f64d..59a0d7866 100644
--- a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java
+++ b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java
@@ -42,7 +42,7 @@ import org.geysermc.geyser.api.event.EventRegistrar;
 import org.geysermc.geyser.api.network.AuthType;
 import org.geysermc.geyser.api.util.PlatformType;
 import org.geysermc.geyser.command.GeyserCommandManager;
-import org.geysermc.geyser.configuration.ConfigLoaderTemp;
+import org.geysermc.geyser.configuration.ConfigLoader;
 import org.geysermc.geyser.configuration.GeyserPluginConfig;
 import org.geysermc.geyser.dump.BootstrapDumpInfo;
 import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
@@ -218,7 +218,7 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserPlugin
 
     private boolean loadConfig() {
         try {
-            this.config = ConfigLoaderTemp.load(new File(ROOT_FOLDER, "config.yml"), GeyserPluginConfig.class, node -> {
+            this.config = ConfigLoader.load(new File(ROOT_FOLDER, "config.yml"), GeyserPluginConfig.class, node -> {
                 try {
                     if (!ViaProxy.getConfig().getWildcardDomainHandling().equals(ViaProxyConfig.WildcardDomainHandling.NONE)) { // TODO
                         node.node("java", "forward-host").set(true);
diff --git a/core/src/main/java/org/geysermc/geyser/configuration/ConfigLoaderTemp.java b/core/src/main/java/org/geysermc/geyser/configuration/ConfigLoader.java
similarity index 98%
rename from core/src/main/java/org/geysermc/geyser/configuration/ConfigLoaderTemp.java
rename to core/src/main/java/org/geysermc/geyser/configuration/ConfigLoader.java
index 59a68ba0e..43e5fa1f7 100644
--- a/core/src/main/java/org/geysermc/geyser/configuration/ConfigLoaderTemp.java
+++ b/core/src/main/java/org/geysermc/geyser/configuration/ConfigLoader.java
@@ -39,7 +39,7 @@ import static org.spongepowered.configurate.NodePath.path;
 import static org.spongepowered.configurate.transformation.TransformAction.remove;
 import static org.spongepowered.configurate.transformation.TransformAction.rename;
 
-public final class ConfigLoaderTemp {
+public final class ConfigLoader {
     private static final String HEADER = """
             --------------------------------
             Geyser Configuration File
@@ -132,4 +132,7 @@ public final class ConfigLoaderTemp {
 
         return config;
     }
+
+    private ConfigLoader() {
+    }
 }
diff --git a/core/src/main/java/org/geysermc/geyser/configuration/EmoteOffhandWorkaroundOption.java b/core/src/main/java/org/geysermc/geyser/configuration/EmoteOffhandWorkaroundOption.java
deleted file mode 100644
index fd44d3903..000000000
--- a/core/src/main/java/org/geysermc/geyser/configuration/EmoteOffhandWorkaroundOption.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * @author GeyserMC
- * @link https://github.com/GeyserMC/Geyser
- */
-
-package org.geysermc.geyser.configuration;
-
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.JsonDeserializer;
-
-import java.io.IOException;
-
-public enum EmoteOffhandWorkaroundOption {
-    NO_EMOTES,
-    EMOTES_AND_OFFHAND,
-    DISABLED;
-
-    public static class Deserializer extends JsonDeserializer<EmoteOffhandWorkaroundOption> {
-        @Override
-        public EmoteOffhandWorkaroundOption deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
-            String value = p.getValueAsString();
-            return switch (value) {
-                case "no-emotes" -> NO_EMOTES;
-                case "emotes-and-offhand" -> EMOTES_AND_OFFHAND;
-                default -> DISABLED;
-            };
-        }
-    }
-}
diff --git a/core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java b/core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java
index 7851fadfd..2956c0329 100644
--- a/core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java
+++ b/core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java
@@ -25,6 +25,7 @@
 
 package org.geysermc.geyser.dump;
 
+import com.google.gson.annotations.JsonAdapter;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 import org.geysermc.geyser.GeyserImpl;
@@ -55,7 +56,7 @@ public class BootstrapDumpInfo {
     @AllArgsConstructor
     public static class ListenerInfo {
 
-        @AsteriskSerializer.Asterisk(isIp = true)
+        @JsonAdapter(value = AsteriskSerializer.class)
         public String ip;
         public int port;
     }
diff --git a/core/src/main/java/org/geysermc/geyser/ping/GeyserPingInfo.java b/core/src/main/java/org/geysermc/geyser/ping/GeyserPingInfo.java
index 9d8da114d..cff46d834 100644
--- a/core/src/main/java/org/geysermc/geyser/ping/GeyserPingInfo.java
+++ b/core/src/main/java/org/geysermc/geyser/ping/GeyserPingInfo.java
@@ -25,9 +25,6 @@
 
 package org.geysermc.geyser.ping;
 
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonSetter;
-import com.fasterxml.jackson.databind.JsonNode;
 import lombok.Data;
 import org.checkerframework.checker.nullness.qual.Nullable;
 
@@ -36,7 +33,6 @@ import org.checkerframework.checker.nullness.qual.Nullable;
  * designed for the format received by {@link GeyserLegacyPingPassthrough}.
  */
 @Data
-@JsonIgnoreProperties(ignoreUnknown = true)
 public class GeyserPingInfo {
 
     @Nullable
@@ -58,13 +54,7 @@ public class GeyserPingInfo {
         this.players = new Players(maxPlayers, onlinePlayers);
     }
 
-    @JsonSetter("description")
-    void setDescription(JsonNode description) {
-        this.description = description.toString();
-    }
-
     @Data
-    @JsonIgnoreProperties(ignoreUnknown = true)
     public static class Players {
 
         private int max;
diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomSkullRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomSkullRegistryPopulator.java
index e44550d71..472b4c8ac 100644
--- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomSkullRegistryPopulator.java
+++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomSkullRegistryPopulator.java
@@ -64,7 +64,7 @@ public class CustomSkullRegistryPopulator {
             GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
             Path skullConfigPath = bootstrap.getConfigFolder().resolve("custom-skulls.yml");
             File skullConfigFile = FileUtils.fileOrCopiedFromResource(skullConfigPath.toFile(), "custom-skulls.yml", Function.identity(), bootstrap);
-            skullConfig = FileUtils.loadConfigNew(skullConfigFile, GeyserCustomSkullConfiguration.class);
+            skullConfig = FileUtils.loadConfig(skullConfigFile, GeyserCustomSkullConfiguration.class);
         } catch (IOException e) {
             GeyserImpl.getInstance().getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.config.failed"), e);
             return;
diff --git a/core/src/main/java/org/geysermc/geyser/text/AsteriskSerializer.java b/core/src/main/java/org/geysermc/geyser/text/AsteriskSerializer.java
index 66b61dbff..325eaccae 100644
--- a/core/src/main/java/org/geysermc/geyser/text/AsteriskSerializer.java
+++ b/core/src/main/java/org/geysermc/geyser/text/AsteriskSerializer.java
@@ -25,76 +25,26 @@
 
 package org.geysermc.geyser.text;
 
-import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.databind.BeanProperty;
-import com.fasterxml.jackson.databind.JsonSerializer;
-import com.fasterxml.jackson.databind.SerializerProvider;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import com.fasterxml.jackson.databind.ser.ContextualSerializer;
-import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
 
-import java.io.IOException;
-import java.io.Serial;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.util.Optional;
-
-public class AsteriskSerializer extends StdSerializer<Object> implements ContextualSerializer {
-
-    @Serial
-    private static final long serialVersionUID = 1L;
+import java.lang.reflect.Type;
+import java.net.InetAddress;
 
+public class AsteriskSerializer implements JsonSerializer<String> {
     public static final String[] NON_SENSITIVE_ADDRESSES = {"", "0.0.0.0", "localhost", "127.0.0.1", "auto", "unknown"};
 
     public static boolean showSensitive = false;
 
-    @Target({ElementType.FIELD})
-    @Retention(RetentionPolicy.RUNTIME)
-    @JacksonAnnotationsInside
-    @JsonSerialize(using = AsteriskSerializer.class)
-    public @interface Asterisk {
-        String value() default "***";
-        /**
-         * If true, this value will be shown if {@link #showSensitive} is true, or if the IP is determined to not be a public IP
-         * 
-         * @return true if this should be analyzed and treated as an IP
-         */
-        boolean isIp() default false;
-    }
-
-    String asterisk;
-    boolean isIp;
-
-    @SuppressWarnings("unused") // Used by Jackson for Geyser dumps
-    public AsteriskSerializer() {
-        super(Object.class);
-    }
-
-    public AsteriskSerializer(String asterisk, boolean isIp) {
-        super(Object.class);
-        this.asterisk = asterisk;
-        this.isIp = isIp;
-    }
-
     @Override
-    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty property) {
-        Optional<Asterisk> anno = Optional.ofNullable(property)
-                .map(prop -> prop.getAnnotation(Asterisk.class));
-
-        return new AsteriskSerializer(anno.map(Asterisk::value).orElse(null), anno.map(Asterisk::isIp).orElse(false));
-    }
-
-    @Override
-    public void serialize(Object obj, JsonGenerator gen, SerializerProvider prov) throws IOException {
-        if (isIp && (showSensitive || !isSensitiveIp((String) obj))) {
-            gen.writeObject(obj);
-            return;
+    public JsonElement serialize(String src, Type typeOfSrc, JsonSerializationContext context) {
+        if (showSensitive || !isSensitiveIp(src)) {
+            return new JsonPrimitive(src);
         }
 
-        gen.writeString(asterisk);
+        return new JsonPrimitive("***");
     }
 
     private boolean isSensitiveIp(String ip) {
@@ -103,6 +53,16 @@ public class AsteriskSerializer extends StdSerializer<Object> implements Context
                 return false;
             }
         }
+
+        try {
+            InetAddress address = InetAddress.getByName(ip);
+            if (address.isSiteLocalAddress() || address.isLoopbackAddress()) {
+                return false;
+            }
+        } catch (Exception e) {
+            // Ignore
+        }
+
         return true;
     }
 }
diff --git a/core/src/main/java/org/geysermc/geyser/util/FileUtils.java b/core/src/main/java/org/geysermc/geyser/util/FileUtils.java
index 0bf389d3f..3ea113154 100644
--- a/core/src/main/java/org/geysermc/geyser/util/FileUtils.java
+++ b/core/src/main/java/org/geysermc/geyser/util/FileUtils.java
@@ -25,11 +25,6 @@
 
 package org.geysermc.geyser.util;
 
-import com.fasterxml.jackson.annotation.JsonSetter;
-import com.fasterxml.jackson.annotation.Nulls;
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
 import org.geysermc.geyser.GeyserBootstrap;
 import org.geysermc.geyser.GeyserImpl;
 import org.spongepowered.configurate.ConfigurationNode;
@@ -51,26 +46,8 @@ import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-public class FileUtils {
-
-    /**
-     * Load the given YAML file into the given class
-     *
-     * @param src File to load
-     * @param valueType Class to load file into
-     * @param <T> the type
-     * @return The data as the given class
-     * @throws IOException if the config could not be loaded
-     */
+public final class FileUtils {
     public static <T> T loadConfig(File src, Class<T> valueType) throws IOException {
-        ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory())
-                // Allow inference of single values as arrays
-                .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
-                .setDefaultSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY));
-        return objectMapper.readValue(src, valueType);
-    }
-
-    public static <T> T loadConfigNew(File src, Class<T> valueType) throws IOException {
         YamlConfigurationLoader loader = YamlConfigurationLoader.builder()
             .file(src)
             .build();
@@ -257,4 +234,7 @@ public class FileUtils {
             throw new RuntimeException(e);
         }
     }
+
+    private FileUtils() {
+    }
 }
diff --git a/core/src/main/java/org/geysermc/geyser/util/WebUtils.java b/core/src/main/java/org/geysermc/geyser/util/WebUtils.java
index 1c9df9c08..5f489b900 100644
--- a/core/src/main/java/org/geysermc/geyser/util/WebUtils.java
+++ b/core/src/main/java/org/geysermc/geyser/util/WebUtils.java
@@ -25,7 +25,6 @@
 
 package org.geysermc.geyser.util;
 
-import com.fasterxml.jackson.databind.JsonNode;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 import com.google.gson.stream.JsonReader;
@@ -34,7 +33,11 @@ import org.geysermc.geyser.GeyserImpl;
 
 import javax.naming.directory.Attribute;
 import javax.naming.directory.InitialDirContext;
-import java.io.*;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.net.URLEncoder;
@@ -69,7 +72,7 @@ public class WebUtils {
     }
 
     /**
-     * Makes a web request to the given URL and returns the body as a {@link JsonNode}.
+     * Makes a web request to the given URL and returns the body as a {@link JsonObject}.
      *
      * @param reqURL URL to fetch
      * @return the response as JSON