From de86e3c9f693272ce13eaed482b199eb2805fb50 Mon Sep 17 00:00:00 2001 From: Prototik Date: Sun, 31 May 2015 18:21:26 +0700 Subject: [PATCH] Initial autoupdater --- build.gradle | 10 + .../fml/common/FMLCommonHandler.java.patch | 14 +- .../server/MinecraftServer.java.patch | 104 ++++++---- .../dedicated/DedicatedServer.java.patch | 17 +- .../ServerConfigurationManager.java.patch | 27 +-- src/main/java/kcauldron/KCauldron.java | 48 +++++ src/main/java/kcauldron/KCauldronCommand.java | 71 +++++++ src/main/java/kcauldron/KCauldronConfig.java | 78 ++++++++ src/main/java/kcauldron/KLog.java | 52 +++++ .../updater/CommandSenderUpdateCallback.java | 59 ++++++ .../updater/DefaultUpdateCallback.java | 74 +++++++ .../kcauldron/updater/KCauldronUpdater.java | 186 ++++++++++++++++++ .../kcauldron/updater/KVersionRetriever.java | 113 +++++++++++ .../minecraftforge/cauldron/VersionInfo.java | 6 +- .../configuration/CauldronConfig.java | 1 + .../cauldron/configuration/StringSetting.java | 23 +++ 16 files changed, 817 insertions(+), 66 deletions(-) create mode 100644 src/main/java/kcauldron/KCauldron.java create mode 100644 src/main/java/kcauldron/KCauldronCommand.java create mode 100644 src/main/java/kcauldron/KCauldronConfig.java create mode 100644 src/main/java/kcauldron/KLog.java create mode 100644 src/main/java/kcauldron/updater/CommandSenderUpdateCallback.java create mode 100644 src/main/java/kcauldron/updater/DefaultUpdateCallback.java create mode 100644 src/main/java/kcauldron/updater/KCauldronUpdater.java create mode 100644 src/main/java/kcauldron/updater/KVersionRetriever.java create mode 100644 src/main/java/net/minecraftforge/cauldron/configuration/StringSetting.java diff --git a/build.gradle b/build.gradle index 04e3bed..3120d0e 100644 --- a/build.gradle +++ b/build.gradle @@ -58,6 +58,12 @@ def retrieveGitHash(full = true) { return 'NOTGIT' } +def retrieveGitBranch() { + if (file('.git').exists()) + return ['git', 'symbolic-ref', '--short', 'HEAD'].execute().text.trim(); + return 'NOTGIT' +} + ext.mcVersion = "1.7.10" ext.forgeVersion = "1408" ext.revision = retrieveBuildNumber() @@ -71,7 +77,9 @@ launch4j { tasks.packageUniversal { classifier = 'server' manifest.attributes([ + 'KCauldron-Git-Branch': retrieveGitBranch(), 'KCauldron-Git-Hash': retrieveGitHash(), + 'KCauldron-Version': project.version, 'Implementation-Vendor': 'Prototik', 'Implementationk-Title': 'KCauldron', 'Implementation-Version': 'KCauldron-'+project.version, @@ -139,6 +147,7 @@ repositories { } dependencies { compile 'pw.prok:KImagine:+@jar' + compile 'org.apache.httpcomponents:httpclient:4.4.1' } ''') } @@ -155,6 +164,7 @@ repositories { dependencies { exported 'pw.prok:KImagine:+@jar' + exported 'org.apache.httpcomponents:httpclient:4.4.1' } packageUniversal { diff --git a/patches/cpw/mods/fml/common/FMLCommonHandler.java.patch b/patches/cpw/mods/fml/common/FMLCommonHandler.java.patch index 83c4719..9a50900 100644 --- a/patches/cpw/mods/fml/common/FMLCommonHandler.java.patch +++ b/patches/cpw/mods/fml/common/FMLCommonHandler.java.patch @@ -11,7 +11,15 @@ import com.google.common.base.Joiner; import com.google.common.base.Strings; -@@ -385,10 +389,11 @@ +@@ -282,6 +286,7 @@ + { + Loader.instance().serverStarted(); + sidedDelegate.allowLogins(); ++ kcauldron.updater.KVersionRetriever.init(MinecraftServer.getServer()); + } + + public void handleServerStopping() +@@ -385,10 +390,11 @@ { return; } @@ -24,7 +32,7 @@ handlerSet.add(handler); handlerToCheck = new WeakReference(handler); // for confirmBackupLevelDatUse Map additionalProperties = Maps.newHashMap(); -@@ -496,7 +501,13 @@ +@@ -496,7 +502,13 @@ public String getModName() { @@ -39,7 +47,7 @@ modNames.add("fml"); if (!noForge) { -@@ -540,8 +551,17 @@ +@@ -540,8 +552,17 @@ bus().post(new InputEvent.KeyInputEvent()); } diff --git a/patches/net/minecraft/server/MinecraftServer.java.patch b/patches/net/minecraft/server/MinecraftServer.java.patch index 5dc143d..aea23c9 100644 --- a/patches/net/minecraft/server/MinecraftServer.java.patch +++ b/patches/net/minecraft/server/MinecraftServer.java.patch @@ -1,15 +1,25 @@ --- ../src-base/minecraft/net/minecraft/server/MinecraftServer.java +++ ../src-work/minecraft/net/minecraft/server/MinecraftServer.java -@@ -33,6 +33,8 @@ +@@ -16,6 +16,7 @@ + import io.netty.buffer.ByteBufOutputStream; + import io.netty.buffer.Unpooled; + import io.netty.handler.codec.base64.Base64; ++ + import java.awt.GraphicsEnvironment; + import java.awt.image.BufferedImage; + import java.io.File; +@@ -33,7 +34,10 @@ import java.util.Random; import java.util.UUID; import java.util.concurrent.Callable; +import java.util.logging.Level; + import javax.imageio.ImageIO; ++ import net.minecraft.command.CommandBase; import net.minecraft.command.ICommandManager; -@@ -50,6 +52,7 @@ + import net.minecraft.command.ICommandSender; +@@ -50,6 +54,7 @@ import net.minecraft.profiler.PlayerUsageSnooper; import net.minecraft.profiler.Profiler; import net.minecraft.server.dedicated.DedicatedServer; @@ -17,26 +27,39 @@ import net.minecraft.server.gui.IUpdatePlayerListBox; import net.minecraft.server.management.PlayerProfileCache; import net.minecraft.server.management.ServerConfigurationManager; -@@ -80,18 +83,49 @@ +@@ -72,6 +77,7 @@ + import net.minecraft.world.storage.ISaveFormat; + import net.minecraft.world.storage.ISaveHandler; + import net.minecraft.world.storage.WorldInfo; ++ + import org.apache.commons.lang3.Validate; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; +@@ -80,18 +86,54 @@ import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.world.WorldEvent; ++ +// CraftBukkit start +import java.io.IOException; + +import jline.console.ConsoleReader; +import joptsimple.OptionSet; +import net.minecraft.world.chunk.storage.AnvilSaveHandler; ++ +import org.bukkit.World.Environment; +import org.bukkit.craftbukkit.SpigotTimings; // Spigot +import org.bukkit.craftbukkit.util.Waitable; +import org.bukkit.event.server.RemoteServerCommandEvent; +import org.bukkit.event.world.WorldSaveEvent; ++ +// CraftBukkit end +// Cauldron start +import java.util.Map; +import java.lang.reflect.Constructor; ++ +import joptsimple.OptionParser; ++import kcauldron.KCauldronConfig; +import cpw.mods.fml.common.asm.transformers.SideTransformer; +import net.minecraft.command.ServerCommand; +import net.minecraft.tileentity.TileEntity; @@ -70,7 +93,7 @@ private final ServerStatusResponse field_147147_p = new ServerStatusResponse(); private final Random field_147146_q = new Random(); @SideOnly(Side.SERVER) -@@ -135,8 +169,39 @@ +@@ -135,8 +177,40 @@ private long field_147142_T = 0L; private final GameProfileRepository field_152365_W; private final PlayerProfileCache field_152366_X; @@ -93,6 +116,7 @@ + // Spigot end + // Cauldron start + public static CauldronConfig cauldronConfig; ++ public static KCauldronConfig kcauldronConfig; + public static TileEntityConfig tileEntityConfig; + public static YamlConfiguration configuration; + public static YamlConfiguration commandsConfiguration; @@ -110,13 +134,14 @@ public MinecraftServer(File p_i45281_1_, Proxy p_i45281_2_) { this.field_152366_X = new PlayerProfileCache(this, field_152367_a); -@@ -149,10 +214,68 @@ +@@ -149,10 +223,70 @@ this.field_152364_T = new YggdrasilAuthenticationService(p_i45281_2_, UUID.randomUUID().toString()); this.field_147143_S = this.field_152364_T.createMinecraftSessionService(); this.field_152365_W = this.field_152364_T.createProfileRepository(); + this.primaryThread = new Thread(this, "Server thread"); // CraftBukkit -+ this.cauldronConfig = new CauldronConfig("cauldron.yml", "cauldron"); -+ this.tileEntityConfig = new TileEntityConfig("tileentities.yml", "cauldron_te"); ++ kcauldronConfig = new KCauldronConfig(); ++ cauldronConfig = new CauldronConfig("cauldron.yml", "cauldron"); ++ tileEntityConfig = new TileEntityConfig("tileentities.yml", "cauldron_te"); } + // Cauldron end @@ -134,8 +159,9 @@ + this.field_147143_S = this.field_152364_T.createMinecraftSessionService(); + this.field_152365_W = this.field_152364_T.createProfileRepository(); + // Cauldron start -+ this.cauldronConfig = new CauldronConfig("cauldron.yml", "cauldron"); -+ this.tileEntityConfig = new TileEntityConfig("tileentities.yml", "cauldron_te"); ++ kcauldronConfig = new KCauldronConfig(); ++ cauldronConfig = new CauldronConfig("cauldron.yml", "cauldron"); ++ tileEntityConfig = new TileEntityConfig("tileentities.yml", "cauldron_te"); + // Cauldron end + // CraftBukkit start + this.options = options; @@ -180,7 +206,7 @@ protected void convertMapIfNeeded(String p_71237_1_) { if (this.getActiveAnvilConverter().isOldMapFormat(p_71237_1_)) -@@ -172,6 +295,7 @@ +@@ -172,6 +306,7 @@ MinecraftServer.logger.info("Converting... " + p_73718_1_ + "%"); } } @@ -188,7 +214,7 @@ @SideOnly(Side.CLIENT) public void resetProgressAndMessage(String p_73721_1_) {} @SideOnly(Side.CLIENT) -@@ -195,10 +319,17 @@ +@@ -195,10 +330,17 @@ protected void loadAllWorlds(String p_71247_1_, String p_71247_2_, long p_71247_3_, WorldType p_71247_5_, String p_71247_6_) { @@ -208,7 +234,7 @@ WorldSettings worldsettings; if (worldinfo == null) -@@ -215,11 +346,79 @@ +@@ -215,11 +357,79 @@ { worldsettings.enableBonusChest(); } @@ -291,7 +317,7 @@ world.addWorldAccess(new WorldManager(this, world)); if (!this.isSinglePlayer()) -@@ -227,12 +426,14 @@ +@@ -227,12 +437,14 @@ world.getWorldInfo().setGameType(this.getGameType()); } @@ -309,7 +335,7 @@ } protected void initialWorldChunkLoad() -@@ -244,9 +445,12 @@ +@@ -244,9 +456,12 @@ int i = 0; this.setUserMessage("menu.generatingTerrain"); byte b0 = 0; @@ -322,7 +348,7 @@ long j = getSystemTimeMillis(); for (int k = -192; k <= 192 && this.isServerRunning(); k += 16) -@@ -265,7 +469,8 @@ +@@ -265,7 +480,8 @@ worldserver.theChunkProviderServer.loadChunk(chunkcoordinates.posX + k >> 4, chunkcoordinates.posZ + l >> 4); } } @@ -332,7 +358,7 @@ this.clearCurrentTask(); } -@@ -292,19 +497,17 @@ +@@ -292,19 +508,17 @@ { this.currentTask = null; this.percentDone = 0; @@ -357,7 +383,7 @@ if (worldserver != null) { -@@ -313,25 +516,41 @@ +@@ -313,25 +527,41 @@ logger.info("Saving chunks for level \'" + worldserver.getWorldInfo().getWorldName() + "\'/" + worldserver.provider.getDimensionName()); } @@ -404,7 +430,7 @@ if (this.func_147137_ag() != null) { this.func_147137_ag().terminateEndpoints(); -@@ -347,7 +566,14 @@ +@@ -347,7 +577,14 @@ if (this.worldServers != null) { logger.info("Saving worlds"); @@ -420,7 +446,7 @@ for (int i = 0; i < this.worldServers.length; ++i) { -@@ -380,6 +606,13 @@ +@@ -380,6 +617,13 @@ this.serverRunning = false; } @@ -434,7 +460,7 @@ public void run() { try -@@ -392,45 +625,41 @@ +@@ -392,45 +636,41 @@ this.field_147147_p.func_151315_a(new ChatComponentText(this.motd)); this.field_147147_p.func_151321_a(new ServerStatusResponse.MinecraftProtocolVersionIdentifier("1.7.10", 5)); this.func_147138_a(this.field_147147_p); @@ -501,7 +527,7 @@ FMLCommonHandler.instance().handleServerStopping(); FMLCommonHandler.instance().expectServerStopped(); // has to come before finalTick to avoid race conditions } -@@ -448,6 +677,14 @@ +@@ -448,6 +688,14 @@ catch (Throwable throwable1) { logger.error("Encountered an unexpected exception", throwable1); @@ -516,7 +542,7 @@ CrashReport crashreport = null; if (throwable1 instanceof ReportedException) -@@ -477,6 +714,7 @@ +@@ -477,6 +725,7 @@ { try { @@ -524,7 +550,7 @@ this.stopServer(); this.serverStopped = true; } -@@ -486,6 +724,16 @@ +@@ -486,6 +735,16 @@ } finally { @@ -541,7 +567,7 @@ FMLCommonHandler.instance().handleServerStopped(); this.serverStopped = true; this.systemExitNow(); -@@ -532,8 +780,11 @@ +@@ -532,8 +791,11 @@ public void tick() { @@ -553,7 +579,7 @@ ++this.tickCounter; if (this.startProfiling) -@@ -562,12 +813,21 @@ +@@ -562,12 +824,21 @@ this.field_147147_p.func_151318_b().func_151330_a(agameprofile); } @@ -577,7 +603,7 @@ } this.theProfiler.startSection("tallying"); -@@ -575,25 +835,57 @@ +@@ -575,25 +846,57 @@ this.theProfiler.endSection(); this.theProfiler.startSection("snooper"); @@ -637,7 +663,7 @@ int i; Integer[] ids = DimensionManager.getIDs(this.tickCounter % 200 == 0); -@@ -602,19 +894,21 @@ +@@ -602,19 +905,21 @@ int id = ids[x]; long j = System.nanoTime(); @@ -662,7 +688,7 @@ this.theProfiler.startSection("tick"); FMLCommonHandler.instance().onPreWorldTick(worldserver); -@@ -622,22 +916,46 @@ +@@ -622,22 +927,46 @@ try { @@ -711,7 +737,7 @@ worldserver.addWorldInfoToCrashReport(crashreport); throw new ReportedException(crashreport); } -@@ -645,10 +963,12 @@ +@@ -645,10 +974,12 @@ FMLCommonHandler.instance().onPostWorldTick(worldserver); this.theProfiler.endSection(); this.theProfiler.startSection("tracker"); @@ -725,7 +751,7 @@ worldTickTimes.get(id)[this.tickCounter % 100] = System.nanoTime() - j; } -@@ -656,15 +976,21 @@ +@@ -656,15 +987,21 @@ this.theProfiler.endStartSection("dim_unloading"); DimensionManager.unloadWorlds(worldTickTimes); this.theProfiler.endStartSection("connection"); @@ -747,7 +773,7 @@ this.theProfiler.endSection(); } -@@ -699,6 +1025,13 @@ +@@ -699,6 +1036,13 @@ public WorldServer worldServerForDimension(int p_71218_1_) { @@ -761,7 +787,7 @@ WorldServer ret = DimensionManager.getWorld(p_71218_1_); if (ret == null) { -@@ -784,13 +1117,14 @@ +@@ -784,13 +1128,14 @@ public List getPossibleCompletions(ICommandSender p_71248_1_, String p_71248_2_) { @@ -780,7 +806,7 @@ if (list != null) { -@@ -798,40 +1132,25 @@ +@@ -798,40 +1143,25 @@ while (iterator.hasNext()) { @@ -829,7 +855,7 @@ } public static MinecraftServer getServer() -@@ -1034,7 +1353,7 @@ +@@ -1034,7 +1364,7 @@ public boolean isServerInOnlineMode() { @@ -838,7 +864,7 @@ } public void setOnlineMode(boolean p_71229_1_) -@@ -1124,7 +1443,7 @@ +@@ -1124,7 +1454,7 @@ public NetworkSystem func_147137_ag() { @@ -847,7 +873,7 @@ } @SideOnly(Side.CLIENT) -@@ -1259,8 +1578,11 @@ +@@ -1259,8 +1589,11 @@ { Bootstrap.func_151354_b(); @@ -859,7 +885,7 @@ boolean flag = true; String s = null; String s1 = "."; -@@ -1356,16 +1678,34 @@ +@@ -1356,16 +1689,34 @@ { dedicatedserver.setGuiEnabled(); } @@ -900,7 +926,7 @@ } catch (Exception exception) { -@@ -1400,15 +1740,70 @@ +@@ -1400,15 +1751,70 @@ @SideOnly(Side.SERVER) public String getPlugins() { @@ -976,7 +1002,7 @@ } @SideOnly(Side.SERVER) -@@ -1455,9 +1850,213 @@ +@@ -1455,9 +1861,213 @@ return this.serverStopped; } diff --git a/patches/net/minecraft/server/dedicated/DedicatedServer.java.patch b/patches/net/minecraft/server/dedicated/DedicatedServer.java.patch index 50ad690..401b6b3 100644 --- a/patches/net/minecraft/server/dedicated/DedicatedServer.java.patch +++ b/patches/net/minecraft/server/dedicated/DedicatedServer.java.patch @@ -140,7 +140,7 @@ this.field_154332_n = new ServerEula(new File("eula.txt")); if (!this.field_154332_n.func_154346_a()) -@@ -172,6 +231,16 @@ +@@ -172,6 +231,17 @@ this.setServerPort(this.settings.getIntProperty("server-port", 25565)); } @@ -150,14 +150,15 @@ + org.spigotmc.SpigotConfig.registerCommands(); + // Spigot end + // Cauldron start -+ this.cauldronConfig.registerCommands(); -+ this.tileEntityConfig.registerCommands(); ++ kcauldronConfig.registerCommands(); ++ cauldronConfig.registerCommands(); ++ tileEntityConfig.registerCommands(); + // Cauldron end + field_155771_h.info("Generating keypair"); this.setKeyPair(CryptManager.createNewKeyPair()); field_155771_h.info("Starting Minecraft server on " + (this.getServerHostname().length() == 0 ? "*" : this.getServerHostname()) + ":" + this.getServerPort()); -@@ -180,7 +249,7 @@ +@@ -180,7 +250,7 @@ { this.func_147137_ag().addLanEndpoint(inetaddress, this.getServerPort()); } @@ -166,7 +167,7 @@ { field_155771_h.warn("**** FAILED TO BIND TO PORT!"); field_155771_h.warn("The exception was: {}", new Object[] {ioexception.toString()}); -@@ -196,10 +265,17 @@ +@@ -196,10 +266,17 @@ field_155771_h.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file."); } @@ -186,7 +187,7 @@ if (!PreYggdrasilConverter.func_152714_a(this.settings)) { -@@ -208,7 +284,8 @@ +@@ -208,7 +285,8 @@ else { FMLCommonHandler.instance().onServerStarted(); @@ -196,7 +197,7 @@ long j = System.nanoTime(); if (this.getFolderName() == null) -@@ -274,11 +351,30 @@ +@@ -274,11 +352,30 @@ this.theRConThreadMain.startThread(); } @@ -227,7 +228,7 @@ public boolean canStructuresSpawn() { return this.canSpawnStructures; -@@ -364,11 +460,19 @@ +@@ -364,11 +461,19 @@ public void executePendingCommands() { diff --git a/patches/net/minecraft/server/management/ServerConfigurationManager.java.patch b/patches/net/minecraft/server/management/ServerConfigurationManager.java.patch index b59ee78..ae6ec28 100644 --- a/patches/net/minecraft/server/management/ServerConfigurationManager.java.patch +++ b/patches/net/minecraft/server/management/ServerConfigurationManager.java.patch @@ -153,7 +153,7 @@ NBTTagCompound nbttagcompound1; if (p_72380_1_.getCommandSenderName().equals(this.mcServer.getServerOwner()) && nbttagcompound != null) -@@ -294,18 +350,60 @@ +@@ -294,18 +350,61 @@ public void playerLoggedIn(EntityPlayerMP p_72377_1_) { @@ -215,10 +215,11 @@ + p_72377_1_.playerNetServerHandler.sendPacket(new S38PacketPlayerListItem(entityplayermp1.listName, true, entityplayermp1.ping)); + } + // CraftBukkit end ++ kcauldron.updater.DefaultUpdateCallback.INSTANCE.onPlayerJoin(playerJoinEvent); } public void updatePlayerPertinentChunks(EntityPlayerMP p_72358_1_) -@@ -313,14 +411,33 @@ +@@ -313,14 +412,33 @@ p_72358_1_.getServerForPlayer().getPlayerManager().updatePlayerPertinentChunks(p_72358_1_); } @@ -254,7 +255,7 @@ { worldserver.removePlayerEntityDangerously(p_72367_1_.ridingEntity); logger.debug("removing player mount"); -@@ -329,9 +446,35 @@ +@@ -329,9 +447,35 @@ worldserver.removeEntity(p_72367_1_); worldserver.getPlayerManager().removePlayer(p_72367_1_); this.playerEntityList.remove(p_72367_1_); @@ -293,7 +294,7 @@ } public String allowUserToConnect(SocketAddress p_148542_1_, GameProfile p_148542_2_) -@@ -372,6 +515,71 @@ +@@ -372,6 +516,71 @@ } } @@ -357,7 +358,7 @@ + loginlistener.func_147322_a(event.getKickMessage()); + return null; + } -+ ++ + return entity; + } + // CraftBukkit end @@ -365,7 +366,7 @@ public EntityPlayerMP createPlayerForUser(GameProfile p_148545_1_) { UUID uuid = EntityPlayer.func_146094_a(p_148545_1_); -@@ -410,80 +618,199 @@ +@@ -410,80 +619,199 @@ return new EntityPlayerMP(this.mcServer, this.mcServer.worldServerForDimension(0), p_148545_1_, (ItemInWorldManager)object); } @@ -605,7 +606,7 @@ return entityplayermp1; } -@@ -492,34 +819,112 @@ +@@ -492,34 +820,112 @@ transferPlayerToDimension(p_72356_1_, p_72356_2_, mcServer.worldServerForDimension(p_72356_2_).getDefaultTeleporter()); } @@ -737,7 +738,7 @@ } public void transferEntityToWorld(Entity p_82448_1_, int p_82448_2_, WorldServer p_82448_3_, WorldServer p_82448_4_, Teleporter teleporter) -@@ -605,6 +1010,109 @@ +@@ -605,6 +1011,109 @@ p_82448_1_.setWorld(p_82448_4_); } @@ -847,7 +848,7 @@ public void sendPlayerInfoToAllPlayers() { if (++this.playerPingIndex > 600) -@@ -612,11 +1120,13 @@ +@@ -612,11 +1121,13 @@ this.playerPingIndex = 0; } @@ -861,7 +862,7 @@ } public void sendPacketToAllPlayers(Packet p_148540_1_) -@@ -877,13 +1387,24 @@ +@@ -877,13 +1388,24 @@ for (int j = 0; j < this.playerEntityList.size(); ++j) { EntityPlayerMP entityplayermp = (EntityPlayerMP)this.playerEntityList.get(j); @@ -888,7 +889,7 @@ if (d4 * d4 + d5 * d5 + d6 * d6 < p_148543_8_ * p_148543_8_) { entityplayermp.playerNetServerHandler.sendPacket(p_148543_11_); -@@ -941,13 +1462,16 @@ +@@ -941,13 +1463,16 @@ p_72354_1_.playerNetServerHandler.sendPacket(new S2BPacketChangeGameState(1, 0.0F)); p_72354_1_.playerNetServerHandler.sendPacket(new S2BPacketChangeGameState(7, p_72354_2_.getRainStrength(1.0F))); p_72354_1_.playerNetServerHandler.sendPacket(new S2BPacketChangeGameState(8, p_72354_2_.getWeightedThunderStrength(1.0F))); @@ -906,7 +907,7 @@ p_72385_1_.playerNetServerHandler.sendPacket(new S09PacketHeldItemChange(p_72385_1_.inventory.currentItem)); } -@@ -961,9 +1485,17 @@ +@@ -961,9 +1486,17 @@ return this.maxPlayers; } @@ -925,7 +926,7 @@ } public void setWhiteListEnabled(boolean p_72371_1_) -@@ -1032,12 +1564,30 @@ +@@ -1032,12 +1565,30 @@ public void removeAllPlayers() { diff --git a/src/main/java/kcauldron/KCauldron.java b/src/main/java/kcauldron/KCauldron.java new file mode 100644 index 0000000..6388e1a --- /dev/null +++ b/src/main/java/kcauldron/KCauldron.java @@ -0,0 +1,48 @@ +package kcauldron; + +import java.io.File; +import java.net.URL; +import java.net.URLDecoder; +import java.util.Enumeration; +import java.util.Properties; + +public class KCauldron { + private static String sCurrentVersion; + + public static String getCurrentVersion() { + if (sCurrentVersion != null) + return sCurrentVersion; + try { + Enumeration resources = KCauldron.class.getClassLoader() + .getResources("META-INF/MANIFEST.MF"); + Properties manifest = new Properties(); + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + manifest.load(url.openStream()); + String version = manifest.getProperty("KCauldron-Version"); + if (version != null) { + String path = url.getPath(); + String jarFilePath = path.substring(path.indexOf(":") + 1, path.indexOf("!")); + jarFilePath = URLDecoder.decode(jarFilePath, "UTF-8"); + sServerLocation = new File(jarFilePath); + return sCurrentVersion = version; + } + manifest.clear(); + } + } catch (Exception e) { + e.printStackTrace(); + } + return sCurrentVersion = "UNKNOWN"; + } + + private static File sServerLocation; + + public static File getServerLocation() { + getCurrentVersion(); + return sServerLocation; + } + + public static File sNewServerLocation; + public static String sNewServerVersion; + public static boolean sUpdateInProgress; +} diff --git a/src/main/java/kcauldron/KCauldronCommand.java b/src/main/java/kcauldron/KCauldronCommand.java new file mode 100644 index 0000000..c489ba9 --- /dev/null +++ b/src/main/java/kcauldron/KCauldronCommand.java @@ -0,0 +1,71 @@ +package kcauldron; + +import kcauldron.updater.CommandSenderUpdateCallback; +import kcauldron.updater.KCauldronUpdater; +import kcauldron.updater.KVersionRetriever; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +public class KCauldronCommand extends Command { + public static final String NAME = "kcauldron"; + public static final String CHECK = NAME + ".check"; + public static final String UPDATE = NAME + ".update"; + + public KCauldronCommand() { + super(NAME); + + StringBuilder builder = new StringBuilder(); + builder.append(String.format("/%s check\n", NAME)); + builder.append(String.format("/%s update [version]\n", NAME)); + setUsage(builder.toString()); + + setPermission("kcauldron"); + } + + public boolean testPermission(CommandSender target, String permission) { + if (testPermissionSilent(target, permission)) { + return true; + } + target.sendMessage(ChatColor.RED + + "I'm sorry, but you do not have permission to perform this command. Please contact the server administrators if you believe that this is in error."); + return false; + } + + public boolean testPermissionSilent(CommandSender target, String permission) { + if (!super.testPermissionSilent(target)) { + return false; + } + for (String p : permission.split(";")) + if (target.hasPermission(p)) + return true; + return false; + } + + @Override + public boolean execute(CommandSender sender, String commandLabel, + String[] args) { + if (!testPermission(sender)) + return true; + if (args.length == 0) { + sender.sendMessage(ChatColor.YELLOW + "Please specify action"); + return true; + } + String action = args[0]; + if ("check".equals(action)) { + if (!testPermission(sender, CHECK)) + return true; + sender.sendMessage(ChatColor.GREEN + "Initiated version check..."); + new KVersionRetriever(new CommandSenderUpdateCallback(sender), + false); + } else if ("update".equals(action)) { + KCauldronUpdater.initUpdate(sender, args.length > 1 ? args[1] + : null); + } else { + sender.sendMessage(ChatColor.RED + "Unknown action"); + } + return true; + } + +} diff --git a/src/main/java/kcauldron/KCauldronConfig.java b/src/main/java/kcauldron/KCauldronConfig.java new file mode 100644 index 0000000..a334889 --- /dev/null +++ b/src/main/java/kcauldron/KCauldronConfig.java @@ -0,0 +1,78 @@ +package kcauldron; + +import org.bukkit.configuration.file.YamlConfiguration; + +import net.minecraft.server.MinecraftServer; +import net.minecraftforge.cauldron.configuration.BoolSetting; +import net.minecraftforge.cauldron.configuration.ConfigBase; +import net.minecraftforge.cauldron.configuration.Setting; +import net.minecraftforge.cauldron.configuration.StringSetting; + +public class KCauldronConfig extends ConfigBase { + public BoolSetting commandEnable = new BoolSetting(this, "command.enable", + true, "Enable KCauldron command"); + public BoolSetting updatecheckerEnable = new BoolSetting(this, + "updatechecker.enable", true, "Enable KCauldron update checker"); + public BoolSetting updatecheckerDeleteOld = new BoolSetting(this, + "updatechecker.deleteOld", true, "Delete old version after update"); + public StringSetting updatecheckerSymlinks = new StringSetting(this, + "updatechecker.symlinks", "", "(Re)create symlinks after update"); + public BoolSetting updatecheckerAutoinstall = new BoolSetting(this, + "updatechecker.autoinstall", false, "Install updates without confirming"); + public BoolSetting updatecheckerQuite = new BoolSetting(this, + "updatechecker.quite", false, "Print less info during update"); + public StringSetting updatecheckerInstallAs = new StringSetting(this, + "updatechecker.installAs", "", "Install new version with specified name"); + + public KCauldronConfig() { + super("kcauldron.yml", "kc"); + register(commandEnable); + register(updatecheckerEnable); + register(updatecheckerDeleteOld); + register(updatecheckerSymlinks); + register(updatecheckerAutoinstall); + register(updatecheckerQuite); + register(updatecheckerInstallAs); + load(); + } + + private void register(Setting setting) { + settings.put(setting.path, setting); + } + + @Override + public void registerCommands() { + if (commandEnable.getValue()) { + super.registerCommands(); + } + } + + @Override + protected void addCommands() { + commands.put(commandName, new KCauldronCommand()); + } + + @Override + protected void load() { + try { + config = YamlConfiguration.loadConfiguration(configFile); + String header = ""; + for (Setting toggle : settings.values()) { + if (!toggle.description.equals("")) + header += "Setting: " + toggle.path + " Default: " + + toggle.def + " # " + toggle.description + "\n"; + + config.addDefault(toggle.path, toggle.def); + settings.get(toggle.path).setValue( + config.getString(toggle.path)); + } + config.options().header(header); + config.options().copyDefaults(true); + save(); + } catch (Exception ex) { + MinecraftServer.getServer().logSevere( + "Could not load " + this.configFile); + ex.printStackTrace(); + } + } +} diff --git a/src/main/java/kcauldron/KLog.java b/src/main/java/kcauldron/KLog.java new file mode 100644 index 0000000..be65695 --- /dev/null +++ b/src/main/java/kcauldron/KLog.java @@ -0,0 +1,52 @@ +package kcauldron; + +import org.apache.logging.log4j.Level; + +import cpw.mods.fml.common.FMLLog; + +public class KLog { + private static final KLog DEFAULT_LOGGER = new KLog("KCauldron"); + + public static KLog get() { + return DEFAULT_LOGGER; + } + + public static KLog get(String tag) { + return new KLog("KCauldron: " + tag); + } + + private final String mTag; + + public KLog(String tag) { + mTag = tag; + } + + public void log(Level level, Throwable throwable, String message, + Object... args) { + Throwable t = null; + if (throwable != null) { + t = new Throwable(); + t.initCause(throwable); + t.fillInStackTrace(); + } + FMLLog.log(mTag, level, t, String.format(message, args)); + } + + public void warning(String message, Object... args) { + log(Level.WARN, null, message, args); + } + + public void warning(Throwable throwable, String message, + Object... args) { + log(Level.WARN, throwable, message, args); + } + + public void info(String message, Object... args) { + log(Level.INFO, null, message, args); + } + + public void info(Throwable throwable, String message, + Object... args) { + log(Level.INFO, throwable, message, args); + } +} diff --git a/src/main/java/kcauldron/updater/CommandSenderUpdateCallback.java b/src/main/java/kcauldron/updater/CommandSenderUpdateCallback.java new file mode 100644 index 0000000..e0d66d1 --- /dev/null +++ b/src/main/java/kcauldron/updater/CommandSenderUpdateCallback.java @@ -0,0 +1,59 @@ +package kcauldron.updater; + +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; + +import kcauldron.updater.KVersionRetriever.IVersionCheckCallback; + +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +public class CommandSenderUpdateCallback implements IVersionCheckCallback { + private Reference mSender; + + public CommandSenderUpdateCallback(CommandSender sender) { + mSender = new WeakReference(sender); + } + + protected CommandSender getSender() { + return mSender.get(); + } + + @Override + public void upToDate(String version) { + CommandSender sender = mSender.get(); + if (sender != null) { + sender.sendMessage(ChatColor.GREEN + + "Running version of KCauldron is up-to-date: " + version); + } + DefaultUpdateCallback.INSTANCE.upToDate(version); + } + + @Override + public void newVersion(String currentVersion, String newVersion) { + CommandSender sender = mSender.get(); + if (sender != null) { + newVersion(sender, currentVersion, newVersion); + } + DefaultUpdateCallback.INSTANCE.newVersion(currentVersion, newVersion); + } + + public static void newVersion(CommandSender sender, String currentVersion, String newVersion) { + sender.sendMessage(new String[] { + ChatColor.YELLOW + "Found new version of KCauldron: " + + newVersion, + ChatColor.YELLOW + "Current is " + currentVersion, + ChatColor.YELLOW + "Type '" + ChatColor.BLUE + + "/kc update" + ChatColor.YELLOW + + "' to update" }); + } + + @Override + public void error(Throwable t) { + CommandSender sender = mSender.get(); + if (sender != null) { + sender.sendMessage(ChatColor.RED + + "Error ocurred durring version check, see details in server log"); + } + } +} diff --git a/src/main/java/kcauldron/updater/DefaultUpdateCallback.java b/src/main/java/kcauldron/updater/DefaultUpdateCallback.java new file mode 100644 index 0000000..eaeb57c --- /dev/null +++ b/src/main/java/kcauldron/updater/DefaultUpdateCallback.java @@ -0,0 +1,74 @@ +package kcauldron.updater; + +import kcauldron.KCauldron; +import kcauldron.KCauldronCommand; +import kcauldron.updater.KVersionRetriever.IVersionCheckCallback; +import net.minecraft.server.MinecraftServer; + +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerJoinEvent; + +public class DefaultUpdateCallback implements IVersionCheckCallback { + public static DefaultUpdateCallback INSTANCE; + + static { + INSTANCE = new DefaultUpdateCallback(); + } + + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + if (mHasUpdate && hasPermission(player)) { + sendUpdate(player); + } + } + + private boolean hasPermission(CommandSender player) { + return player.hasPermission(KCauldronCommand.UPDATE); + } + + private void sendUpdate(CommandSender player) { + CommandSenderUpdateCallback.newVersion(player, mCurrentVersion, + mNewVersion); + } + + private boolean mHasUpdate; + private String mCurrentVersion; + private String mNewVersion; + + private DefaultUpdateCallback() { + } + + @Override + public void upToDate(String version) { + mHasUpdate = false; + mCurrentVersion = version; + mNewVersion = null; + } + + @Override + public void newVersion(String currentVersion, String newVersion) { + mCurrentVersion = currentVersion; + mNewVersion = newVersion; + if (!mHasUpdate) { + for (Player player : Bukkit.getOnlinePlayers()) { + if (hasPermission(player)) { + sendUpdate(player); + } + } + } + mHasUpdate = true; + if (MinecraftServer.kcauldronConfig.updatecheckerAutoinstall.getValue() + && !mNewVersion.equals(KCauldron.sNewServerVersion) + && !KCauldron.sUpdateInProgress) { + Bukkit.getConsoleSender().sendMessage("Triggering auto update"); + KCauldronUpdater.initUpdate(Bukkit.getConsoleSender(), newVersion); + } + } + + @Override + public void error(Throwable t) { + + } +} diff --git a/src/main/java/kcauldron/updater/KCauldronUpdater.java b/src/main/java/kcauldron/updater/KCauldronUpdater.java new file mode 100644 index 0000000..51d0229 --- /dev/null +++ b/src/main/java/kcauldron/updater/KCauldronUpdater.java @@ -0,0 +1,186 @@ +package kcauldron.updater; + +import java.io.File; +import java.io.InputStream; +import java.nio.file.Files; + +import kcauldron.KCauldron; +import net.minecraft.server.MinecraftServer; + +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.impl.client.HttpClientBuilder; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +import com.google.common.base.Splitter; + +public class KCauldronUpdater implements Runnable { + private static final class LatestVersionCallback extends + CommandSenderUpdateCallback { + public LatestVersionCallback(CommandSender sender) { + super(sender); + } + + @Override + public void newVersion(String currentVersion, String newVersion) { + startUpdate(getSender(), newVersion); + } + + @Override + public void upToDate(String version) { + CommandSender sender = getSender(); + if (sender != null) { + sender.sendMessage(ChatColor.DARK_PURPLE + "Current version (" + + version + ") is up to date"); + } + } + } + + public static void initUpdate(CommandSender sender, String version) { + if (KCauldron.sUpdateInProgress) { + sender.sendMessage(ChatColor.RED + + "Update stopped: another update in progress"); + return; + } + KCauldron.sUpdateInProgress = true; + if (version == null) { + sender.sendMessage(ChatColor.DARK_PURPLE + + "Fetching latest version..."); + new KVersionRetriever(new LatestVersionCallback(sender), false); + } else { + startUpdate(sender, version); + } + } + + private static void startUpdate(CommandSender sender, String version) { + if (sender != null) { + sender.sendMessage(ChatColor.DARK_PURPLE + "Starting update to " + + version + "..."); + } + new KCauldronUpdater(sender, version); + } + + private final CommandSender mSender; + private final String mVersion; + private final Thread mThread; + + public KCauldronUpdater(CommandSender sender, String version) { + mSender = sender; + mVersion = version; + mThread = new Thread(this); + mThread.setName("KCauldron updater"); + mThread.setPriority(Thread.MIN_PRIORITY); + mThread.start(); + } + + @Override + public void run() { + try { + boolean quite = MinecraftServer.kcauldronConfig.updatecheckerQuite + .getValue(); + MinecraftServer server = MinecraftServer.getServer(); + final String filename = "KCauldron-" + mVersion + "-server.jar"; + File path = KCauldron.getServerLocation(); + File newPath = new File(path.getParentFile(), + getInstallAs(filename)); + if (mSender != null) { + if (!quite) { + mSender.sendMessage(ChatColor.DARK_PURPLE + + "Current version is located in " + ChatColor.GOLD + + path.getAbsolutePath()); + mSender.sendMessage(ChatColor.DARK_PURPLE + + "Installing new version in " + ChatColor.GOLD + + newPath.getAbsolutePath()); + } + if (newPath.exists()) { + Bukkit.getConsoleSender().sendMessage( + "ERROR: Install location already exists: " + + newPath.getAbsolutePath()); + mSender.sendMessage(ChatColor.RED + + "ERROR: Install location already exists"); + return; + } + if (!quite) { + mSender.sendMessage(ChatColor.DARK_PURPLE + + "Downloading new version..."); + } + } + HttpUriRequest request = RequestBuilder + .get() + .setUri("https://prok.pw/repo/pw/prok/KCauldron/" + + mVersion + "/" + filename) + .addParameter("hostname", server.getHostname()) + .addParameter("port", "" + server.getPort()).build(); + HttpResponse response = HttpClientBuilder.create() + .setUserAgent("KCauldron Updater").build().execute(request); + InputStream is = response.getEntity().getContent(); + Files.copy(is, newPath.toPath()); + if (mSender != null && !quite) { + mSender.sendMessage(ChatColor.DARK_PURPLE + + "Download completed"); + } + makeSymlinks(newPath); + if (MinecraftServer.kcauldronConfig.updatecheckerDeleteOld + .getValue()) { + if (mSender != null && !quite) { + mSender.sendMessage(ChatColor.DARK_PURPLE + + "Mark old version for deletion"); + } + path.deleteOnExit(); + if (KCauldron.sNewServerLocation != null) { + KCauldron.sNewServerLocation.deleteOnExit(); + } + } + if (mSender != null) { + mSender.sendMessage(ChatColor.DARK_PURPLE + "Update completed"); + } + KCauldron.sNewServerLocation = newPath; + KCauldron.sNewServerVersion = mVersion; + } catch (Exception e) { + e.printStackTrace(); + if (mSender != null) { + mSender.sendMessage(ChatColor.RED + "Failed update to " + + mVersion); + } + } finally { + KCauldron.sUpdateInProgress = false; + } + } + + private String getInstallAs(String filename) { + String path = MinecraftServer.kcauldronConfig.updatecheckerInstallAs + .getValue().trim(); + if (path.length() > 0) { + return path.replace("%version%", mVersion); + } + return filename; + } + + private void makeSymlinks(File newPath) { + try { + for (String symlink : Splitter.on(File.pathSeparatorChar).split( + MinecraftServer.kcauldronConfig.updatecheckerSymlinks + .getValue())) { + symlink = symlink.trim(); + if (symlink.length() == 0) + continue; + File symlinkPath = new File(symlink); + if (mSender != null + && !MinecraftServer.kcauldronConfig.updatecheckerQuite + .getValue()) { + mSender.sendMessage(ChatColor.RED + "Create symlink " + + ChatColor.GOLD + symlinkPath.getAbsolutePath()); + } + if (symlinkPath.exists()) { + symlinkPath.delete(); + } + Files.createSymbolicLink(symlinkPath.toPath(), newPath.toPath()); + } + } catch (Exception e) { + + } + } +} diff --git a/src/main/java/kcauldron/updater/KVersionRetriever.java b/src/main/java/kcauldron/updater/KVersionRetriever.java new file mode 100644 index 0000000..c0b98d4 --- /dev/null +++ b/src/main/java/kcauldron/updater/KVersionRetriever.java @@ -0,0 +1,113 @@ +package kcauldron.updater; + +import java.io.InputStreamReader; +import java.lang.Thread.UncaughtExceptionHandler; + +import kcauldron.KCauldron; +import kcauldron.KLog; +import net.minecraft.server.MinecraftServer; + +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.impl.client.HttpClientBuilder; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; + +public class KVersionRetriever implements Runnable, UncaughtExceptionHandler { + private static final boolean DEBUG; + private static final KLog sLogger; + private static final JSONParser sParser; + private static final String sCurrentVersion; + private static MinecraftServer sServer; + + static { + DEBUG = false; + sLogger = KLog.get(KVersionRetriever.class.getSimpleName()); + + sCurrentVersion = KCauldron.getCurrentVersion(); + sParser = new JSONParser(); + } + + public static void init(MinecraftServer server) { + sServer = server; + if (MinecraftServer.kcauldronConfig.updatecheckerEnable.getValue()) { + new KVersionRetriever(DefaultUpdateCallback.INSTANCE, true); + } + } + + private final IVersionCheckCallback mCallback; + private final boolean mLoop; + private final Thread mThread; + + public KVersionRetriever(IVersionCheckCallback callback, boolean loop) { + if (DEBUG) + sLogger.info("Created new version retrivier"); + mCallback = callback; + mLoop = loop; + mThread = new Thread(this); + mThread.setName("KCauldron version retrievier"); + mThread.setPriority(Thread.MIN_PRIORITY); + mThread.setDaemon(true); + mThread.setUncaughtExceptionHandler(this); + mThread.start(); + } + + @Override + public void run() { + while (!mThread.isInterrupted()) { + check(); + if (!mLoop) + break; + try { + Thread.sleep(1000 * 60 * 10);// Sleep ten minutes + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + private void check() { + if (DEBUG) + sLogger.info("Requesting for new version..."); + try { + HttpUriRequest request = RequestBuilder.get() + .setUri("https://prok.pw/version/pw.prok/KCauldron") + .addParameter("hostname", sServer.getHostname()) + .addParameter("port", "" + sServer.getPort()).build(); + HttpResponse response = HttpClientBuilder.create() + .setUserAgent("KCauldron Version Retriever").build() + .execute(request); + JSONObject json = (JSONObject) sParser.parse(new InputStreamReader( + response.getEntity().getContent())); + String version = (String) json.get("version"); + if (DEBUG) { + sLogger.info("Got the latest version: %s", version); + sLogger.info("Current version is %s", sCurrentVersion); + } + if (!sCurrentVersion.equals(version)) { + mCallback.newVersion(sCurrentVersion, version); + } else { + mCallback.upToDate(version); + } + } catch (Exception e) { + uncaughtException(null, e); + } + } + + @Override + public void uncaughtException(Thread t, Throwable e) { + sLogger.warning(e, "Error occured during retriving version"); + if (mCallback != null) { + mCallback.error(e); + } + } + + public interface IVersionCheckCallback { + void upToDate(String version); + + void newVersion(String currentVersion, String newVersion); + + void error(Throwable t); + } +} diff --git a/src/main/java/net/minecraftforge/cauldron/VersionInfo.java b/src/main/java/net/minecraftforge/cauldron/VersionInfo.java index 63f7277..ba429e4 100644 --- a/src/main/java/net/minecraftforge/cauldron/VersionInfo.java +++ b/src/main/java/net/minecraftforge/cauldron/VersionInfo.java @@ -97,9 +97,9 @@ public class VersionInfo { private void doFileExtract(File path) throws IOException { InputStream inputStream = getClass().getResourceAsStream("/"+getContainedFile()); - OutputSupplier outputSupplier = Files.newOutputStreamSupplier(path); - System.out.println("doFileExtract path = " + path.getAbsolutePath() + ", inputStream = " + inputStream + ", outputSupplier = " + outputSupplier); - ByteStreams.copy(inputStream, outputSupplier); + FileOutputStream outputStream = new FileOutputStream(path); + System.out.println("doFileExtract path = " + path.getAbsolutePath() + ", inputStream = " + inputStream + ", outputStream = " + outputStream); + ByteStreams.copy(inputStream, outputStream); } public static String getMinecraftVersion() diff --git a/src/main/java/net/minecraftforge/cauldron/configuration/CauldronConfig.java b/src/main/java/net/minecraftforge/cauldron/configuration/CauldronConfig.java index 926e67e..383f89c 100644 --- a/src/main/java/net/minecraftforge/cauldron/configuration/CauldronConfig.java +++ b/src/main/java/net/minecraftforge/cauldron/configuration/CauldronConfig.java @@ -2,6 +2,7 @@ package net.minecraftforge.cauldron.configuration; import net.minecraft.server.MinecraftServer; import net.minecraftforge.cauldron.command.CauldronCommand; + import org.bukkit.configuration.file.YamlConfiguration; public class CauldronConfig extends ConfigBase diff --git a/src/main/java/net/minecraftforge/cauldron/configuration/StringSetting.java b/src/main/java/net/minecraftforge/cauldron/configuration/StringSetting.java new file mode 100644 index 0000000..6ff9d36 --- /dev/null +++ b/src/main/java/net/minecraftforge/cauldron/configuration/StringSetting.java @@ -0,0 +1,23 @@ +package net.minecraftforge.cauldron.configuration; + +public class StringSetting extends Setting { + private String value; + private ConfigBase config; + + public StringSetting(ConfigBase config, String path, String def, + String description) { + super(path, def, description); + this.value = def; + this.config = config; + } + + @Override + public String getValue() { + return value; + } + + @Override + public void setValue(String value) { + config.set(path, this.value = value); + } +}