--- a/net/minecraft/server/dedicated/Settings.java
+++ b/net/minecraft/server/dedicated/Settings.java
@@ -14,42 +14,59 @@
 import java.util.function.Supplier;
 import java.util.function.UnaryOperator;
 import javax.annotation.Nullable;
-import net.minecraft.core.RegistryAccess;
 import org.slf4j.Logger;
 
+import joptsimple.OptionSet; // CraftBukkit
+import net.minecraft.core.RegistryAccess;
+
 public abstract class Settings<T extends Settings<T>> {
+
     private static final Logger LOGGER = LogUtils.getLogger();
     public final Properties properties;
+    private static final boolean skipComments = Boolean.getBoolean("Paper.skipServerPropertiesComments"); // Paper - allow skipping server.properties comments
+    // CraftBukkit start
+    private OptionSet options = null;
 
-    public Settings(Properties properties) {
+    public Settings(Properties properties, final OptionSet options) {
         this.properties = properties;
+
+        this.options = options;
     }
 
+    private String getOverride(String name, String value) {
+        if ((this.options != null) && (this.options.has(name)) && !name.equals( "online-mode")) { // Spigot
+            return String.valueOf(this.options.valueOf(name));
+        }
+
+        return value;
+    }
+    // CraftBukkit end
+
     public static Properties loadFromFile(Path path) {
         Properties properties = new Properties();
 
         try {
-            InputStream inputStream = Files.newInputStream(path);
+            InputStream inputstream = Files.newInputStream(path);
 
             try {
-                properties.load(inputStream);
-            } catch (Throwable var6) {
-                if (inputStream != null) {
+                properties.load(inputstream);
+            } catch (Throwable throwable) {
+                if (inputstream != null) {
                     try {
-                        inputStream.close();
-                    } catch (Throwable var5) {
-                        var6.addSuppressed(var5);
+                        inputstream.close();
+                    } catch (Throwable throwable1) {
+                        throwable.addSuppressed(throwable1);
                     }
                 }
 
-                throw var6;
+                throw throwable;
             }
 
-            if (inputStream != null) {
-                inputStream.close();
+            if (inputstream != null) {
+                inputstream.close();
             }
-        } catch (IOException var7) {
-            LOGGER.error("Failed to load properties from file: {}", (Object)path);
+        } catch (IOException ioexception) {
+            Settings.LOGGER.error("Failed to load properties from file: {}", path);
         }
 
         return properties;
@@ -57,85 +74,133 @@
 
     public void store(Path path) {
         try {
-            OutputStream outputStream = Files.newOutputStream(path);
+            // CraftBukkit start - Don't attempt writing to file if it's read only
+            if (path.toFile().exists() && !path.toFile().canWrite()) {
+                Settings.LOGGER.warn("Can not write to file {}, skipping.", path); // Paper
+                return;
+            }
+            // CraftBukkit end
+            OutputStream outputstream = Files.newOutputStream(path);
+            // Paper start - disable writing comments to properties file
+            java.io.BufferedOutputStream bufferedOutputStream =  !skipComments ? new java.io.BufferedOutputStream(outputstream) : new java.io.BufferedOutputStream(outputstream) {
+                private boolean isRightAfterNewline = true; // If last written char was newline
+                private boolean isComment = false; // Are we writing comment currently?
+
+                @Override
+                public void write(@org.jetbrains.annotations.NotNull byte[] b) throws IOException {
+                    this.write(b, 0, b.length);
+                }
+
+                @Override
+                public void write(@org.jetbrains.annotations.NotNull byte[] bbuf, int off, int len) throws IOException {
+                    int latest_offset = off; // The latest offset, updated when comment ends
+                    for (int index = off; index < off + len; ++index ) {
+                        byte c = bbuf[index];
+                        boolean isNewline = (c == '\n' || c == '\r');
+                        if (isNewline && isComment) {
+                            // Comment has ended
+                            isComment = false;
+                            latest_offset = index+1;
+                        }
+                        if (c == '#' && isRightAfterNewline) {
+                            isComment = true;
+                            if (index != latest_offset) {
+                                // We got some non-comment data earlier
+                                super.write(bbuf, latest_offset, index-latest_offset);
+                            }
+                        }
+                        isRightAfterNewline = isNewline; // Store for next iteration
+
+                    }
+                    if (latest_offset < off+len && !isComment) {
+                        // We have some unwritten data, that isn't part of a comment
+                        super.write(bbuf, latest_offset, (off + len) - latest_offset);
+                    }
+                }
+            };
+            // Paper end
 
             try {
-                this.properties.store(outputStream, "Minecraft server properties");
-            } catch (Throwable var6) {
-                if (outputStream != null) {
+                this.properties.store(bufferedOutputStream, "Minecraft server properties"); // Paper - use bufferedOutputStream
+            } catch (Throwable throwable) {
+                if (outputstream != null) {
                     try {
-                        outputStream.close();
-                    } catch (Throwable var5) {
-                        var6.addSuppressed(var5);
+                        outputstream.close();
+                    } catch (Throwable throwable1) {
+                        throwable.addSuppressed(throwable1);
                     }
                 }
 
-                throw var6;
+                throw throwable;
             }
 
-            if (outputStream != null) {
-                outputStream.close();
+            if (outputstream != null) {
+                outputstream.close();
             }
-        } catch (IOException var7) {
-            LOGGER.error("Failed to store properties to file: {}", (Object)path);
+        } catch (IOException ioexception) {
+            Settings.LOGGER.error("Failed to store properties to file: {}", path);
         }
 
     }
 
     private static <V extends Number> Function<String, V> wrapNumberDeserializer(Function<String, V> parser) {
-        return (string) -> {
+        return (s) -> {
             try {
-                return parser.apply(string);
-            } catch (NumberFormatException var3) {
-                return (V)null;
+                return (V) parser.apply(s); // CraftBukkit - decompile error
+            } catch (NumberFormatException numberformatexception) {
+                return null;
             }
         };
     }
 
     protected static <V> Function<String, V> dispatchNumberOrString(IntFunction<V> intParser, Function<String, V> fallbackParser) {
-        return (string) -> {
+        return (s) -> {
             try {
-                return intParser.apply(Integer.parseInt(string));
-            } catch (NumberFormatException var4) {
-                return fallbackParser.apply(string);
+                return intParser.apply(Integer.parseInt(s));
+            } catch (NumberFormatException numberformatexception) {
+                return fallbackParser.apply(s);
             }
         };
     }
 
     @Nullable
     public String getStringRaw(String key) {
-        return (String)this.properties.get(key);
+        return (String) this.getOverride(key, this.properties.getProperty(key)); // CraftBukkit
     }
 
     @Nullable
     protected <V> V getLegacy(String key, Function<String, V> stringifier) {
-        String string = this.getStringRaw(key);
-        if (string == null) {
-            return (V)null;
+        String s1 = this.getStringRaw(key);
+
+        if (s1 == null) {
+            return null;
         } else {
             this.properties.remove(key);
-            return stringifier.apply(string);
+            return stringifier.apply(s1);
         }
     }
 
     protected <V> V get(String key, Function<String, V> parser, Function<V, String> stringifier, V fallback) {
-        String string = this.getStringRaw(key);
-        V object = MoreObjects.firstNonNull((V)(string != null ? parser.apply(string) : null), fallback);
-        this.properties.put(key, stringifier.apply(object));
-        return object;
+        String s1 = this.getStringRaw(key);
+        V v1 = MoreObjects.firstNonNull(s1 != null ? parser.apply(s1) : null, fallback);
+
+        this.properties.put(key, stringifier.apply(v1));
+        return v1;
     }
 
     protected <V> Settings<T>.MutableValue<V> getMutable(String key, Function<String, V> parser, Function<V, String> stringifier, V fallback) {
-        String string = this.getStringRaw(key);
-        V object = MoreObjects.firstNonNull((V)(string != null ? parser.apply(string) : null), fallback);
-        this.properties.put(key, stringifier.apply(object));
-        return new Settings.MutableValue<>(key, object, stringifier);
+        String s1 = this.getStringRaw(key);
+        V v1 = MoreObjects.firstNonNull(s1 != null ? parser.apply(s1) : null, fallback);
+
+        this.properties.put(key, stringifier.apply(v1));
+        return new Settings.MutableValue(key, v1, stringifier); // CraftBukkit - decompile error
     }
 
     protected <V> V get(String key, Function<String, V> parser, UnaryOperator<V> parsedTransformer, Function<V, String> stringifier, V fallback) {
-        return this.get(key, (value) -> {
-            V object = parser.apply(value);
-            return (V)(object != null ? parsedTransformer.apply(object) : null);
+        return this.get(key, (s1) -> {
+            V v1 = parser.apply(s1);
+
+            return v1 != null ? parsedTransformer.apply(v1) : null;
         }, stringifier, fallback);
     }
 
@@ -148,32 +213,32 @@
     }
 
     protected String get(String key, String fallback) {
-        return this.get(key, Function.identity(), Function.identity(), fallback);
+        return (String) this.get(key, Function.identity(), Function.identity(), fallback);
     }
 
     @Nullable
     protected String getLegacyString(String key) {
-        return this.getLegacy(key, Function.identity());
+        return (String) this.getLegacy(key, Function.identity());
     }
 
     protected int get(String key, int fallback) {
-        return this.get(key, wrapNumberDeserializer(Integer::parseInt), Integer.valueOf(fallback));
+        return (Integer) this.get(key, Settings.wrapNumberDeserializer(Integer::parseInt), fallback);
     }
 
     protected Settings<T>.MutableValue<Integer> getMutable(String key, int fallback) {
-        return this.getMutable(key, wrapNumberDeserializer(Integer::parseInt), fallback);
+        return this.getMutable(key, Settings.wrapNumberDeserializer(Integer::parseInt), fallback);
     }
 
     protected int get(String key, UnaryOperator<Integer> transformer, int fallback) {
-        return this.get(key, wrapNumberDeserializer(Integer::parseInt), transformer, Objects::toString, fallback);
+        return (Integer) this.get(key, Settings.wrapNumberDeserializer(Integer::parseInt), transformer, Objects::toString, fallback);
     }
 
     protected long get(String key, long fallback) {
-        return this.get(key, wrapNumberDeserializer(Long::parseLong), fallback);
+        return (Long) this.get(key, Settings.wrapNumberDeserializer(Long::parseLong), fallback);
     }
 
     protected boolean get(String key, boolean fallback) {
-        return this.get(key, Boolean::valueOf, fallback);
+        return (Boolean) this.get(key, Boolean::valueOf, fallback);
     }
 
     protected Settings<T>.MutableValue<Boolean> getMutable(String key, boolean fallback) {
@@ -182,37 +247,39 @@
 
     @Nullable
     protected Boolean getLegacyBoolean(String key) {
-        return this.getLegacy(key, Boolean::valueOf);
+        return (Boolean) this.getLegacy(key, Boolean::valueOf);
     }
 
     protected Properties cloneProperties() {
         Properties properties = new Properties();
+
         properties.putAll(this.properties);
         return properties;
     }
 
-    protected abstract T reload(RegistryAccess registryManager, Properties properties);
+    protected abstract T reload(RegistryAccess iregistrycustom, Properties properties, OptionSet optionset); // CraftBukkit
 
     public class MutableValue<V> implements Supplier<V> {
+
         private final String key;
         private final V value;
         private final Function<V, String> serializer;
 
-        MutableValue(String key, V value, Function<V, String> stringifier) {
-            this.key = key;
-            this.value = value;
-            this.serializer = stringifier;
+        MutableValue(String s, V object, Function function) { // CraftBukkit - decompile error
+            this.key = s;
+            this.value = object;
+            this.serializer = function;
         }
 
-        @Override
         public V get() {
             return this.value;
         }
 
         public T update(RegistryAccess registryManager, V value) {
             Properties properties = Settings.this.cloneProperties();
+
             properties.put(this.key, this.serializer.apply(value));
-            return Settings.this.reload(registryManager, properties);
+            return Settings.this.reload(registryManager, properties, Settings.this.options); // CraftBukkit
         }
     }
 }
