diff --git a/gradle.properties b/gradle.properties index d44f929..1c3e1eb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,10 +50,10 @@ enableGenericInjection = true # Generate a class with a String field for the mod version named as defined below. # If generateGradleTokenClass is empty or not missing, no such class will be generated. # If gradleTokenVersion is empty or missing, the field will not be present in the class. -generateGradleTokenClass = +generateGradleTokenClass = glowredman.defaultserverlist.Tags # Name of the token containing the project's current version to generate/replace. -gradleTokenVersion = +gradleTokenVersion = VERSION # [DEPRECATED] Mod ID replacement token. gradleTokenModId = @@ -106,7 +106,7 @@ coreModClass = LoadingPlugin # If your project is only a consolidation of mixins or a core mod and does NOT contain a 'normal' mod ( = some class # that is annotated with @Mod) you want this to be true. When in doubt: leave it on false! -containsMixinsAndOrCoreModOnly = false +containsMixinsAndOrCoreModOnly = true # Enables Mixins even if this mod doesn't use them, useful if one of the dependencies uses mixins. forceEnableMixins = false diff --git a/src/main/java/glowredman/defaultserverlist/Config.java b/src/main/java/glowredman/defaultserverlist/Config.java index cbe8701..54b4066 100644 --- a/src/main/java/glowredman/defaultserverlist/Config.java +++ b/src/main/java/glowredman/defaultserverlist/Config.java @@ -1,7 +1,8 @@ package glowredman.defaultserverlist; +import static net.minecraftforge.common.config.Configuration.CATEGORY_GENERAL; + import java.io.File; -import java.io.IOException; import java.io.Reader; import java.net.URL; import java.nio.charset.StandardCharsets; @@ -15,92 +16,169 @@ import java.util.Map; import net.minecraft.client.multiplayer.ServerData; +import net.minecraftforge.common.config.Configuration; import org.apache.commons.io.IOUtils; import com.google.common.reflect.TypeToken; import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import com.google.gson.annotations.SerializedName; -import cpw.mods.fml.common.FMLLog; - public class Config { - public static ConfigObj config = new ConfigObj(); public static final List SERVERS = new ArrayList<>(); - private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); - private static Path configPath; - - public static void preInit(File configDir) { - Reader fileReader = null; - try { - configPath = configDir.toPath().resolve("defaultserverlist.json"); - - if (!Files.exists(configPath)) { - saveConfig(config); - } else { - fileReader = Files.newBufferedReader(configPath, StandardCharsets.UTF_8); - config = GSON.fromJson(fileReader, ConfigObj.class); + public static boolean allowModifications; + private static Configuration config; + + /* + * spotless:off + * + * NOTE: There are three different representations for server list entries: + * - String array: The entries are in the format ip|name. This is used for the new configs. + * - Map: The key is the name, the value is the ip. This is used for remote server lists. + * - List: This is used by Minecraft. + * + * spotless:on + */ + + public static void init(File configDir) { + + // Setup + File configFile = new File(configDir, "defaultserverlist.cfg"); + Path legacyConfigPath = configDir.toPath().resolve("defaultserverlist.json"); + boolean migrate = !configFile.exists() && Files.exists(legacyConfigPath); + Gson gson = new Gson(); + config = new Configuration(configFile); + + // Migrate to new config file if needed. + if (migrate) { + LoadingPlugin.LOGGER.info("Found legacy config, attempting to migrate..."); + try (Reader fileReader = Files.newBufferedReader(legacyConfigPath)) { + ConfigObj legacyConfig = gson.fromJson(fileReader, ConfigObj.class); + config.get(CATEGORY_GENERAL, "useURL", false).set(legacyConfig.useURL); + config.get(CATEGORY_GENERAL, "allowModifications", true).set(legacyConfig.allowModifications); + config.get(CATEGORY_GENERAL, "url", "").set(legacyConfig.url); + config.get(CATEGORY_GENERAL, "servers", new String[0]).set(toArray(legacyConfig.servers)); + config.get(CATEGORY_GENERAL, "prevDefaultServers", new String[0]) + .set(legacyConfig.prevDefaultServers.toArray(new String[0])); + Files.delete(legacyConfigPath); + LoadingPlugin.LOGGER.info("Migration successful!"); + } catch (Exception e) { + LoadingPlugin.LOGGER.error("Migration failed!", e); } + } - if (config.useURL) { - try { - // servers that are currently at the remote location - Map remoteDefaultServers = GSON.fromJson( - IOUtils.toString(new URL(config.url), StandardCharsets.UTF_8), - new TypeToken>() { - - private static final long serialVersionUID = -1786059589535074931L; - }.getType()); - - if (config.allowModifications) { - // servers that were added to the remote location since the last time the list was fetched - Map diff = new LinkedHashMap<>(); - - // calculate diff - remoteDefaultServers.forEach((name, ip) -> { - if (!config.prevDefaultServers.contains(ip)) { - diff.put(name, ip); - } - }); - - // save if the remote location was updated - if (!diff.isEmpty()) { - config.servers.putAll(diff); - config.prevDefaultServers = remoteDefaultServers.values(); - saveConfig(config); + // get config values and convert them to a usable format. This also adds comments to the properties. + boolean useURL = config.getBoolean( + "useURL", + CATEGORY_GENERAL, + false, + "Whether or not the default servers should be fetched from a remote location."); + allowModifications = config.getBoolean( + "allowModifications", + CATEGORY_GENERAL, + true, + "Whether or not the user should be able to delete, modify or change the order of the default servers."); + String url = config.getString( + "url", + CATEGORY_GENERAL, + "", + "The remote location to fetch the default servers from. The returned content must be in JSON format (formatted as a map where the keys are the server names and the values the corresponding ip-adresses)."); + Map servers = toMap( + config.getStringList( + "servers", + CATEGORY_GENERAL, + new String[0], + "The default servers. Format: ip|name")); + String[] prevDefaultServersArray = config + .getStringList("prevDefaultServers", CATEGORY_GENERAL, new String[0], "DO NOT EDIT!"); + Collection prevDefaultServers = new ArrayList<>(prevDefaultServersArray.length); + Arrays.stream(prevDefaultServersArray).forEachOrdered(prevDefaultServers::add); + + // Fetch servers from the specified remote location. + if (useURL) { + try { + // servers that are currently at the remote location + Map remoteDefaultServers = gson.fromJson( + IOUtils.toString(new URL(url), StandardCharsets.UTF_8), + new TypeToken>() { + + private static final long serialVersionUID = -1786059589535074931L; + }.getType()); + + if (allowModifications) { + // servers that were added to the remote location since the last time the list was fetched + Map diff = new LinkedHashMap<>(); + + // calculate diff + for (Map.Entry entry : remoteDefaultServers.entrySet()) { + String ip = entry.getValue(); + if (!prevDefaultServers.contains(ip)) { + diff.put(entry.getKey(), ip); } + } - } else { - config.servers = remoteDefaultServers; - saveConfig(config); + // save if the remote location was updated + if (!diff.isEmpty()) { + servers.putAll(diff); + prevDefaultServers = remoteDefaultServers.values(); + setStringList("servers", toArray(servers)); + setStringList("prevDefaultServers", prevDefaultServers.toArray(new String[0])); } - } catch (Exception e) { - FMLLog.warning( - "Could not get default server list from default location! Are you connected to the internet?"); - e.printStackTrace(); + + } else { + servers = remoteDefaultServers; + setStringList("servers", toArray(servers)); } + } catch (Exception e) { + LoadingPlugin.LOGGER + .error("Could not get default server list from {}! Are you connected to the internet?", url, e); } + } + + // save the config if it changed. + if (config.hasChanged()) { + config.save(); + } - config.servers.forEach((name, ip) -> SERVERS.add(new ServerData(name, ip))); + // Convert from Map to List + servers.forEach((name, ip) -> SERVERS.add(new ServerData(name, ip))); + } - } catch (Exception e) { - FMLLog.severe("Could not parse default server list!"); - e.printStackTrace(); - } finally { - IOUtils.closeQuietly(fileReader); + private static String[] toArray(Map map) { + String[] array = new String[map.size()]; + int i = 0; + for (Map.Entry entry : map.entrySet()) { + array[i] = entry.getValue() + '|' + entry.getKey(); + i++; } + return array; } - public static void saveConfig(ConfigObj config) throws IOException { - File f = configPath.toFile(); - if (f.exists()) { - f.delete(); + private static Map toMap(String[] array) { + Map map = new LinkedHashMap<>(array.length); + for (String entry : array) { + String[] parts = entry.split("\\|", 2); + if (parts.length < 2) { + LoadingPlugin.LOGGER.warn("Could not parse entry {} because not '|' was found!", entry); + continue; + } + map.put(parts[1], parts[0]); } - Files.write(configPath, Arrays.asList(GSON.toJson(config)), StandardCharsets.UTF_8); + return map; + } + + public static void saveServers(String[] servers) { + setStringList("servers", servers); + config.save(); + } + + private static void setStringList(String key, String[] values) { + // config.get(CATEGORY_GENERAL, key, new String[0]).set(values); resets the comment so we can't use that here + config.getCategory(CATEGORY_GENERAL).get(key).set(values); } + @Deprecated public static final class ConfigObj { public boolean useURL = false; @@ -110,11 +188,5 @@ public static final class ConfigObj { @SerializedName("DO_NOT_EDIT_prevDefaultServers") public Collection prevDefaultServers = new ArrayList<>(); - - public ConfigObj() {} - - public ConfigObj(Map servers) { - this.servers = servers; - } } } diff --git a/src/main/java/glowredman/defaultserverlist/LoadingPlugin.java b/src/main/java/glowredman/defaultserverlist/LoadingPlugin.java index a065685..31e60e2 100644 --- a/src/main/java/glowredman/defaultserverlist/LoadingPlugin.java +++ b/src/main/java/glowredman/defaultserverlist/LoadingPlugin.java @@ -6,6 +6,9 @@ import java.util.Map; import java.util.Set; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import com.gtnewhorizon.gtnhmixins.IEarlyMixinLoader; import cpw.mods.fml.relauncher.FMLLaunchHandler; @@ -16,10 +19,14 @@ import cpw.mods.fml.relauncher.Side; @MCVersion("1.7.10") -@Name("DefaultServerList") +@Name("DefaultServerList Core") @TransformerExclusions("glowredman.defaultserverlist.LoadingPlugin") public class LoadingPlugin implements IFMLLoadingPlugin, IEarlyMixinLoader { + public static final Logger LOGGER = LogManager.getLogger("DefaultServerList"); + + static File location; + @Override public String getMixinConfig() { return "mixins.defaultserverlist.early.json"; @@ -40,6 +47,9 @@ public String[] getASMTransformerClass() { @Override public String getModContainerClass() { + if (FMLLaunchHandler.side() == Side.CLIENT) { + return ModContainer.class.getName(); + } return null; } @@ -50,8 +60,9 @@ public String getSetupClass() { @Override public void injectData(Map data) { - if (FMLLaunchHandler.side() == Side.CLIENT) { - Config.preInit(new File((File) data.get("mcLocation"), "config")); + location = (File) data.get("coremodLocation"); + if (location == null) { + location = new File(this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath()); } } diff --git a/src/main/java/glowredman/defaultserverlist/ModContainer.java b/src/main/java/glowredman/defaultserverlist/ModContainer.java new file mode 100644 index 0000000..2fbfc2d --- /dev/null +++ b/src/main/java/glowredman/defaultserverlist/ModContainer.java @@ -0,0 +1,59 @@ +package glowredman.defaultserverlist; + +import java.io.File; +import java.util.Collections; +import java.util.Set; + +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; + +import cpw.mods.fml.common.DummyModContainer; +import cpw.mods.fml.common.LoadController; +import cpw.mods.fml.common.ModMetadata; +import cpw.mods.fml.common.event.FMLPreInitializationEvent; +import cpw.mods.fml.common.versioning.ArtifactVersion; +import cpw.mods.fml.common.versioning.VersionParser; +import cpw.mods.fml.common.versioning.VersionRange; + +public class ModContainer extends DummyModContainer { + + public ModContainer() { + super(new ModMetadata()); + ModMetadata md = this.getMetadata(); + + // NOTE: If you change this, change mcmod.info too! + md.authorList = Collections.singletonList("glowredman"); + md.description = "Add default servers to the multiplayer screen."; + md.modId = "defaultserverlist"; + md.name = "DefaultServerList"; + md.updateUrl = "https://files.data-hole.de/mods/defaultserverlist/updates.json"; + md.url = "https://github.com/glowredman/DefaultServerList"; + md.version = Tags.VERSION; + } + + @Override + public VersionRange acceptableMinecraftVersionRange() { + return VersionParser.parseRange("[1.7.10]"); + } + + @Override + public Set getRequirements() { + return Collections.singleton(VersionParser.parseVersionReference("gtnhmixins")); + } + + @Override + public File getSource() { + return LoadingPlugin.location; + } + + @Override + public boolean registerBus(EventBus bus, LoadController controller) { + bus.register(this); + return true; + } + + @Subscribe + public void preInit(FMLPreInitializationEvent event) { + Config.init(event.getModConfigurationDirectory()); + } +} diff --git a/src/main/java/glowredman/defaultserverlist/mixins/early/ServerListMixin.java b/src/main/java/glowredman/defaultserverlist/mixins/early/ServerListMixin.java index 7855297..94bcb53 100644 --- a/src/main/java/glowredman/defaultserverlist/mixins/early/ServerListMixin.java +++ b/src/main/java/glowredman/defaultserverlist/mixins/early/ServerListMixin.java @@ -1,8 +1,6 @@ package glowredman.defaultserverlist.mixins.early; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import net.minecraft.client.multiplayer.ServerData; import net.minecraft.client.multiplayer.ServerList; @@ -18,12 +16,15 @@ import glowredman.defaultserverlist.Config; @Mixin(ServerList.class) -public class ServerListMixin { +public abstract class ServerListMixin { @Shadow @Final private List servers; + @Shadow + public abstract void saveServerList(); + /** * Removes all servers from servers.dat that are already in the default list * @@ -50,15 +51,13 @@ private void removeDuplicateServers(CallbackInfo ci) { */ @Inject(at = @At("TAIL"), method = "saveServerList()V") private void saveDefaultServerList(CallbackInfo ci) { - if (Config.config.allowModifications) { - try { - Map newServers = new LinkedHashMap<>(); - Config.SERVERS.forEach(serverData -> newServers.put(serverData.serverName, serverData.serverIP)); - Config.config.servers = newServers; - Config.saveConfig(Config.config); - } catch (Exception e) { - e.printStackTrace(); + if (Config.allowModifications) { + String[] newServers = new String[Config.SERVERS.size()]; + for (int i = 0; i < newServers.length; i++) { + ServerData data = Config.SERVERS.get(i); + newServers[i] = data.serverIP + '|' + data.serverName; } + Config.saveServers(newServers); } } @@ -86,7 +85,7 @@ public ServerData getServerData(int index) { public void removeServerData(int index) { if (index < servers.size()) { servers.remove(index); - } else if (Config.config.allowModifications) { + } else if (Config.allowModifications) { Config.SERVERS.remove(index - servers.size()); } } @@ -123,9 +122,6 @@ public void swapServers(int index1, int index2) { } } - @Shadow - public void saveServerList() {} - /** * Sets the ServerData instance stored for the given index in the list. * diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info index ad45d44..0a4904b 100644 --- a/src/main/resources/mcmod.info +++ b/src/main/resources/mcmod.info @@ -1,5 +1,6 @@ [ { + "__comment": "NOTE: If you change this, change ModContainer.java too!", "modid": "${modId}", "name": "${modName}", "description": "Add default servers to the multiplayer screen.",