diff --git a/sources/src/main/java/io/akarin/server/core/AkarinGlobalConfig.java b/sources/src/main/java/io/akarin/server/core/AkarinGlobalConfig.java new file mode 100644 index 000000000..6cb973913 --- /dev/null +++ b/sources/src/main/java/io/akarin/server/core/AkarinGlobalConfig.java @@ -0,0 +1,151 @@ +package io.akarin.server.core; + +import com.google.common.base.Throwables; +import com.google.common.collect.Lists; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; +import io.akarin.api.LogWrapper; + +public class AkarinGlobalConfig { + + private static File CONFIG_FILE; + private static final String HEADER = "This is the global configuration file for Akarin.\n" + + "Some options may impact gameplay, so use with caution,\n" + + "and make sure you know what each option does before configuring.\n" + + "\n" + + "Akarin forums: https://akarin.io/ \n"; + /*========================================================================*/ + public static YamlConfiguration config; + static int version; + /*========================================================================*/ + public static void init(File configFile) { + CONFIG_FILE = configFile; + config = new YamlConfiguration(); + try { + config.load(CONFIG_FILE); + } catch (IOException ex) { + } catch (InvalidConfigurationException ex) { + LogWrapper.logger.error("Could not load akarin.yml, please correct your syntax errors", ex); + throw Throwables.propagate(ex); + } + config.options().header(HEADER); + config.options().copyDefaults(true); + + version = getInt("config-version", 1); + set("config-version", 1); + readConfig(AkarinGlobalConfig.class, null); + } + + static void readConfig(Class clazz, Object instance) { + for (Method method : clazz.getDeclaredMethods()) { + if (Modifier.isPrivate(method.getModifiers())) { + if (method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE) { + try { + method.setAccessible(true); + method.invoke(instance); + } catch (InvocationTargetException ex) { + throw Throwables.propagate(ex.getCause()); + } catch (Exception ex) { + LogWrapper.logger.error("Error invoking " + method, ex); + } + } + } + } + + try { + config.save(CONFIG_FILE); + } catch (IOException ex) { + LogWrapper.logger.error("Could not save " + CONFIG_FILE, ex); + } + } + + private static final Pattern SPACE = Pattern.compile(" "); + private static final Pattern NOT_NUMERIC = Pattern.compile("[^-\\d.]"); + public static int getSeconds(String str) { + str = SPACE.matcher(str).replaceAll(""); + final char unit = str.charAt(str.length() - 1); + str = NOT_NUMERIC.matcher(str).replaceAll(""); + double num; + try { + num = Double.parseDouble(str); + } catch (Exception e) { + num = 0D; + } + switch (unit) { + case 'd': num *= (double) 60*60*24; break; + case 'h': num *= (double) 60*60; break; + case 'm': num *= 60; break; + default: case 's': break; + } + return (int) num; + } + + protected static String timeSummary(int seconds) { + String time = ""; + + if (seconds > 60 * 60 * 24) { + time += TimeUnit.SECONDS.toDays(seconds) + "d"; + seconds %= 60 * 60 * 24; + } + + if (seconds > 60 * 60) { + time += TimeUnit.SECONDS.toHours(seconds) + "h"; + seconds %= 60 * 60; + } + + if (seconds > 0) { + time += TimeUnit.SECONDS.toMinutes(seconds) + "m"; + } + return time; + } + + private static void set(String path, Object val) { + config.set(path, val); + } + + private static boolean getBoolean(String path, boolean def) { + config.addDefault(path, def); + return config.getBoolean(path, config.getBoolean(path)); + } + + private static double getDouble(String path, double def) { + config.addDefault(path, def); + return config.getDouble(path, config.getDouble(path)); + } + + private static float getFloat(String path, float def) { + // TODO: Figure out why getFloat() always returns the default value. + return (float) getDouble(path, def); + } + + private static int getInt(String path, int def) { + config.addDefault(path, def); + return config.getInt(path, config.getInt(path)); + } + + private static List getList(String path, T def) { + config.addDefault(path, def); + return config.getList(path, config.getList(path)); + } + + private static String getString(String path, String def) { + config.addDefault(path, def); + return config.getString(path, config.getString(path)); + } + /*========================================================================*/ + public static List extraAddress; + private static void extraAddress() { + extraAddress = getList("network.extra-local-address", Lists.newArrayList()); + } + +} diff --git a/sources/src/main/java/io/akarin/server/mixin/core/Bootstrap.java b/sources/src/main/java/io/akarin/server/mixin/core/Bootstrap.java index 0df273a7a..1e3ba36b6 100644 --- a/sources/src/main/java/io/akarin/server/mixin/core/Bootstrap.java +++ b/sources/src/main/java/io/akarin/server/mixin/core/Bootstrap.java @@ -1,5 +1,6 @@ package io.akarin.server.mixin.core; +import java.io.File; import java.io.PrintStream; import org.bukkit.craftbukkit.Main; @@ -10,12 +11,13 @@ import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import io.akarin.api.LogWrapper; +import io.akarin.server.core.AkarinGlobalConfig; @Mixin(value = Main.class, remap = false) public class Bootstrap { @Inject(method = "main([Ljava/lang/String;)V", at = @At("HEAD")) - private static void configureMixin(CallbackInfo info) { - ; + private static void premain(CallbackInfo info) { + AkarinGlobalConfig.init(new File("akarin.yml")); } /* diff --git a/sources/src/main/java/io/akarin/server/mixin/core/MixinTileEntityEnchantTable.java b/sources/src/main/java/io/akarin/server/mixin/core/MixinTileEntityEnchantTable.java new file mode 100644 index 000000000..2a11d0572 --- /dev/null +++ b/sources/src/main/java/io/akarin/server/mixin/core/MixinTileEntityEnchantTable.java @@ -0,0 +1,11 @@ +package io.akarin.server.mixin.core; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import net.minecraft.server.TileEntityEnchantTable; + +@Mixin(value = TileEntityEnchantTable.class, remap = false) +public class MixinTileEntityEnchantTable { + @Overwrite + public void e() {} // No tickable +} diff --git a/sources/src/main/java/io/akarin/server/mixin/core/MixinVersionCommand.java b/sources/src/main/java/io/akarin/server/mixin/core/MixinVersionCommand.java index 3a865aec4..efec5f4cd 100644 --- a/sources/src/main/java/io/akarin/server/mixin/core/MixinVersionCommand.java +++ b/sources/src/main/java/io/akarin/server/mixin/core/MixinVersionCommand.java @@ -45,8 +45,8 @@ public class MixinVersionCommand { private volatile boolean versionObtaining; private long lastCheckMillis; - - private CommandSender currentSender; + + private CommandSender currentSender; private boolean customVersion; // The name can lead to misunderstand, @@ -122,7 +122,7 @@ public class MixinVersionCommand { break; case -2: setVersionMessage("Unknown version"); - customVersion = true; + customVersion = true; break; default: setVersionMessage("You are " + distance + " version(s) behind"); diff --git a/sources/src/main/java/io/akarin/server/mixin/core/NonblockingServerConnection.java b/sources/src/main/java/io/akarin/server/mixin/core/NonblockingServerConnection.java index 325b17a44..0c46e9695 100644 --- a/sources/src/main/java/io/akarin/server/mixin/core/NonblockingServerConnection.java +++ b/sources/src/main/java/io/akarin/server/mixin/core/NonblockingServerConnection.java @@ -2,6 +2,7 @@ package io.akarin.server.mixin.core; import java.io.IOException; import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -20,6 +21,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import com.google.common.collect.Lists; import io.akarin.api.LocalAddress; import io.akarin.api.WrappedCollections; +import io.akarin.server.core.AkarinGlobalConfig; import io.akarin.server.core.ChannelAdapter; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; @@ -76,7 +78,7 @@ public class NonblockingServerConnection { */ @Overwrite public void a(InetAddress address, int port) throws IOException { - registerChannels(Collections.singleton(LocalAddress.create(address, port))); + registerChannels(Lists.newArrayList(LocalAddress.create(address, port))); } public void registerChannels(Collection data) throws IOException { @@ -95,6 +97,16 @@ public class NonblockingServerConnection { ServerBootstrap bootstrap = new ServerBootstrap().channel(channelClass).childHandler(ChannelAdapter.create(h)).group(loopGroup); synchronized (g) { + data.addAll(Lists.transform(AkarinGlobalConfig.extraAddress, s -> { + String[] info = s.split(":"); + try { + logger.info("Attempt to bind server on " + s); + return LocalAddress.create(InetAddress.getByName(info[0]), Integer.valueOf(info[1])); + } catch (NumberFormatException | UnknownHostException ex) { + logger.error("Error on lookup additional host, wrong format?", ex); + return null; + } + })); data.forEach(address -> g.add(bootstrap.localAddress(address.host(), address.port()).bind().syncUninterruptibly())); // supports multi-port bind } } @@ -106,7 +118,7 @@ public class NonblockingServerConnection { public void b() { this.d = false; try { - synchronized (g) { + synchronized (g) { // safe fixes for (ChannelFuture channel : g) channel.channel().close().sync(); } } catch (InterruptedException ex) { diff --git a/sources/src/main/resources/mixins.akarin.core.json b/sources/src/main/resources/mixins.akarin.core.json index 7e70238ed..f672ed16a 100644 --- a/sources/src/main/resources/mixins.akarin.core.json +++ b/sources/src/main/resources/mixins.akarin.core.json @@ -15,6 +15,7 @@ "MixinPaperConfig", "MixinCraftServer", "MixinVersionCommand", - "MixinMinecraftServer" + "MixinMinecraftServer", + "MixinTileEntityEnchantTable" ] } \ No newline at end of file