Skip to content

Commit d77301a

Browse files
committed
Merge branch '26.1.2/dev' into 26.1.2/stable
2 parents ca5868b + 2e85c25 commit d77301a

10 files changed

Lines changed: 264 additions & 53 deletions

File tree

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ val FABRIC_API_VERSION by extra { "0.153.0+26.1.2" }
1111
// https://semver.org/
1212
val MAVEN_GROUP by extra { "me.flashyreese.mods" }
1313
val ARCHIVE_NAME by extra { "sodium-extra" }
14-
val MOD_VERSION by extra { "0.9.0" }
14+
val MOD_VERSION by extra { "0.9.1" }
1515
val SODIUM_VERSION by extra { "0.9.1-beta.2+mc26.1.2" }
1616

1717
allprojects {

common/src/main/java/me/flashyreese/mods/sodiumextra/client/SodiumExtraClientMod.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public static CaffeineConfig mixinConfig() {
8686
}
8787

8888
private static SodiumExtraGameOptions loadConfig() {
89-
return SodiumExtraGameOptions.load(PlatformRuntimeInformation.getInstance().getConfigDirectory().resolve(SodiumExtraConfigKeys.FILE_NAME).toFile());
89+
return SodiumExtraGameOptions.load(PlatformRuntimeInformation.getInstance().getConfigDirectory().resolve(SodiumExtraConfigKeys.FILE_NAME));
9090
}
9191

9292
public static void armWaylandFullscreenResolutionRecovery() {
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package me.flashyreese.mods.sodiumextra.client.config;
2+
3+
import java.io.IOException;
4+
import java.nio.ByteBuffer;
5+
import java.nio.channels.FileChannel;
6+
import java.nio.charset.StandardCharsets;
7+
import java.nio.file.Files;
8+
import java.nio.file.Path;
9+
import java.nio.file.StandardCopyOption;
10+
import java.nio.file.StandardOpenOption;
11+
12+
public final class ConfigFileIO {
13+
private ConfigFileIO() {
14+
}
15+
16+
public static void writeStringAtomically(Path path, String contents) throws IOException {
17+
writeBytesAtomically(path, contents.getBytes(StandardCharsets.UTF_8));
18+
}
19+
20+
public static void writeLinesAtomically(Path path, Iterable<String> lines) throws IOException {
21+
StringBuilder builder = new StringBuilder();
22+
for (String line : lines) {
23+
builder.append(line).append(System.lineSeparator());
24+
}
25+
26+
writeStringAtomically(path, builder.toString());
27+
}
28+
29+
public static void writeBytesAtomically(Path path, byte[] bytes) throws IOException {
30+
Path targetPath = path.toAbsolutePath();
31+
Path directory = targetPath.getParent();
32+
Path fileName = targetPath.getFileName();
33+
34+
if (directory == null || fileName == null) {
35+
throw new IOException("Path has no parent directory: " + path);
36+
}
37+
38+
Path tempPath = null;
39+
IOException failure = null;
40+
41+
try {
42+
Files.createDirectories(directory);
43+
tempPath = Files.createTempFile(directory, fileName.toString(), ".tmp");
44+
45+
writeBytesToTempFile(tempPath, bytes);
46+
Files.move(tempPath, targetPath, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
47+
tempPath = null;
48+
49+
forceDirectory(directory);
50+
} catch (IOException e) {
51+
failure = e;
52+
throw e;
53+
} finally {
54+
if (tempPath != null) {
55+
try {
56+
Files.deleteIfExists(tempPath);
57+
} catch (IOException e) {
58+
if (failure != null) {
59+
failure.addSuppressed(e);
60+
} else {
61+
throw e;
62+
}
63+
}
64+
}
65+
}
66+
}
67+
68+
public static Path moveCorruptFile(Path path) throws IOException {
69+
Path corruptPath = nextCorruptFilePath(path);
70+
Files.move(path, corruptPath);
71+
return corruptPath;
72+
}
73+
74+
private static void writeBytesToTempFile(Path tempPath, byte[] bytes) throws IOException {
75+
try (FileChannel channel = FileChannel.open(tempPath, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
76+
ByteBuffer buffer = ByteBuffer.wrap(bytes);
77+
while (buffer.hasRemaining()) {
78+
channel.write(buffer);
79+
}
80+
81+
channel.force(true);
82+
}
83+
}
84+
85+
private static Path nextCorruptFilePath(Path path) {
86+
Path basePath = path.resolveSibling(path.getFileName() + ".corrupt");
87+
if (!Files.exists(basePath)) {
88+
return basePath;
89+
}
90+
91+
for (int i = 1; ; i++) {
92+
Path candidate = path.resolveSibling(path.getFileName() + ".corrupt." + i);
93+
if (!Files.exists(candidate)) {
94+
return candidate;
95+
}
96+
}
97+
}
98+
99+
private static void forceDirectory(Path directory) {
100+
try (FileChannel channel = FileChannel.open(directory, StandardOpenOption.READ)) {
101+
channel.force(true);
102+
} catch (IOException ignored) {
103+
}
104+
}
105+
}

common/src/main/java/me/flashyreese/mods/sodiumextra/client/config/SodiumExtraConfig.java

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ private static Identifier id(String path) {
4646
private static final Identifier SODIUM_FULLSCREEN_MODE_OPTION_ID = Identifier.parse("sodium:general.fullscreen_mode");
4747
private static final Identifier SODIUM_FULLSCREEN_RESOLUTION_OPTION_ID = Identifier.parse("sodium:general.fullscreen_resolution");
4848
private static final Identifier SODIUM_VSYNC_OPTION_ID = Identifier.parse("sodium:general.vsync");
49-
private static final List<Identifier> DEFAULT_DIMENSION_IDS = List.of(
49+
private static final List<Identifier> DEFAULT_DIMENSION_EFFECT_IDS = List.of(
5050
Level.OVERWORLD.identifier(),
5151
Level.NETHER.identifier(),
5252
Level.END.identifier()
@@ -107,6 +107,36 @@ private static SodiumExtraGameOptions.AtmosphericFogSettings createDimensionFogS
107107
return settings;
108108
}
109109

110+
private static List<Identifier> getDimensionFogEffectIds(SodiumExtraGameOptions.FogSettings fogSettings) {
111+
Set<Identifier> identifiers = new LinkedHashSet<>(DEFAULT_DIMENSION_EFFECT_IDS);
112+
addKnownWorldDimensionEffectIds(identifiers);
113+
identifiers.addAll(fogSettings.dimensionOverrides.keySet());
114+
115+
identifiers.forEach(identifier -> fogSettings.dimensionOverrides.computeIfAbsent(identifier, ignored -> createDimensionFogSettings()));
116+
117+
return identifiers.stream()
118+
.sorted(Comparator.comparing(Identifier::toString))
119+
.toList();
120+
}
121+
122+
private static void addKnownWorldDimensionEffectIds(Set<Identifier> identifiers) {
123+
Minecraft minecraft = Minecraft.getInstance();
124+
if (minecraft == null) {
125+
return;
126+
}
127+
128+
if (minecraft.level != null) {
129+
identifiers.add(getDimensionFogEffectId(minecraft.level));
130+
}
131+
}
132+
133+
private static Identifier getDimensionFogEffectId(Level level) {
134+
return level.dimensionTypeRegistration()
135+
.unwrapKey()
136+
.map(key -> key.identifier())
137+
.orElseGet(() -> level.dimension().identifier());
138+
}
139+
110140
private static void setAtmosphericFogStart(int value) {
111141
SodiumExtraGameOptions.FogSettings fogSettings = fogSettings();
112142
int clampedValue = Math.clamp(value, 0, 100);
@@ -517,9 +547,7 @@ private OptionPageBuilder createRenderPage(ConfigBuilder builder) {
517547
.setName(Component.translatable("sodium-extra.option.render"));
518548

519549
SodiumExtraGameOptions.FogSettings fogSettings = fogSettings();
520-
DEFAULT_DIMENSION_IDS.stream()
521-
.filter(identifier -> !fogSettings.dimensionOverrides.containsKey(identifier))
522-
.forEach(identifier -> fogSettings.dimensionOverrides.put(identifier, createDimensionFogSettings()));
550+
List<Identifier> dimensionFogEffectIds = getDimensionFogEffectIds(fogSettings);
523551

524552
page.addOptionGroup(builder.createOptionGroup()
525553
.addOption(builder.createBooleanOption(ADVANCED_FOG_OPTION_ID)
@@ -607,9 +635,7 @@ private OptionPageBuilder createRenderPage(ConfigBuilder builder) {
607635
);
608636

609637
OptionGroupBuilder dimensionFogGroup = builder.createOptionGroup();
610-
fogSettings.dimensionOverrides.keySet().stream()
611-
.sorted(Comparator.comparing(Identifier::toString))
612-
.forEach(identifier -> dimensionFogGroup.addOption(builder.createIntegerOption(id("fog." + identifier.toLanguageKey("options.dimensions")))
638+
dimensionFogEffectIds.forEach(identifier -> dimensionFogGroup.addOption(builder.createIntegerOption(id("fog." + identifier.toLanguageKey("options.dimensions")))
613639
.setEnabledProvider(
614640
SodiumExtraConfig::isDimensionFogOptionEnabled,
615641
ADVANCED_FOG_OPTION_ID,
@@ -627,7 +653,9 @@ private OptionPageBuilder createRenderPage(ConfigBuilder builder) {
627653
)
628654
.setDefaultValue(0)
629655
));
630-
page.addOptionGroup(dimensionFogGroup);
656+
if (!dimensionFogEffectIds.isEmpty()) {
657+
page.addOptionGroup(dimensionFogGroup);
658+
}
631659

632660
page.addOptionGroup(builder.createOptionGroup()
633661
.addOption(builder.createBooleanOption(PROTECTED_GAMEPLAY_FOG_OPTION_ID)

common/src/main/java/me/flashyreese/mods/sodiumextra/client/config/SodiumExtraGameOptions.java

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.google.gson.FieldNamingPolicy;
44
import com.google.gson.Gson;
55
import com.google.gson.GsonBuilder;
6+
import com.google.gson.JsonParseException;
67
import com.google.gson.annotations.SerializedName;
78
import com.mojang.blaze3d.opengl.GlBackend;
89
import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
@@ -18,10 +19,11 @@
1819
import org.lwjgl.glfw.GLFW;
1920

2021
import java.io.File;
21-
import java.io.FileReader;
22-
import java.io.FileWriter;
2322
import java.io.IOException;
2423
import java.lang.reflect.Modifier;
24+
import java.nio.charset.StandardCharsets;
25+
import java.nio.file.Files;
26+
import java.nio.file.Path;
2527
import java.util.Arrays;
2628
import java.util.EnumSet;
2729
import java.util.Map;
@@ -39,30 +41,37 @@ public class SodiumExtraGameOptions implements StorageEventHandler {
3941
public RenderSettings renderSettings = new RenderSettings();
4042
@SerializedName(SodiumExtraConfigKeys.EXTRA_SETTINGS)
4143
public ExtraSettings extraSettings = new ExtraSettings();
42-
private File file;
44+
private Path path;
4345

4446
public static SodiumExtraGameOptions load(File file) {
47+
return load(file.toPath());
48+
}
49+
50+
public static SodiumExtraGameOptions load(Path path) {
4551
SodiumExtraGameOptions config;
52+
boolean shouldWriteChanges = true;
4653

47-
if (file.exists()) {
48-
try (FileReader reader = new FileReader(file)) {
54+
if (Files.exists(path)) {
55+
try (var reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
4956
config = gson.fromJson(reader, SodiumExtraGameOptions.class);
50-
} catch (Exception e) {
51-
SodiumExtraClientMod.logger().error("Could not parse config, falling back to defaults!", e);
57+
if (config == null) {
58+
throw new JsonParseException("Root element must be a JSON object");
59+
}
60+
} catch (IOException | JsonParseException | IllegalStateException e) {
61+
SodiumExtraClientMod.logger().warn("Could not read config, falling back to defaults", e);
5262
config = new SodiumExtraGameOptions();
63+
shouldWriteChanges = moveCorruptConfig(path);
5364
}
5465
} else {
5566
config = new SodiumExtraGameOptions();
5667
}
5768

58-
if (config == null) {
59-
SodiumExtraClientMod.logger().error("Could not parse config, falling back to defaults!");
60-
config = new SodiumExtraGameOptions();
61-
}
62-
6369
config.sanitize();
64-
config.file = file;
65-
config.writeChanges();
70+
config.path = path;
71+
72+
if (shouldWriteChanges) {
73+
config.writeChanges();
74+
}
6675

6776
return config;
6877
}
@@ -93,20 +102,27 @@ private void sanitize() {
93102
}
94103

95104
public void writeChanges() {
96-
File dir = this.file.getParentFile();
105+
if (this.path == null) {
106+
SodiumExtraClientMod.logger().warn("Could not save configuration file because no path was set");
107+
return;
108+
}
97109

98-
if (!dir.exists()) {
99-
if (!dir.mkdirs()) {
100-
throw new RuntimeException("Could not create parent directories");
101-
}
102-
} else if (!dir.isDirectory()) {
103-
throw new RuntimeException("The parent file is not a directory");
110+
try {
111+
this.sanitize();
112+
ConfigFileIO.writeStringAtomically(this.path, gson.toJson(this) + System.lineSeparator());
113+
} catch (IOException e) {
114+
SodiumExtraClientMod.logger().warn("Could not save configuration file", e);
104115
}
116+
}
105117

106-
try (FileWriter writer = new FileWriter(this.file)) {
107-
gson.toJson(this, writer);
118+
private static boolean moveCorruptConfig(Path path) {
119+
try {
120+
Path corruptPath = ConfigFileIO.moveCorruptFile(path);
121+
SodiumExtraClientMod.logger().warn("Moved corrupt configuration file to {}", corruptPath);
122+
return true;
108123
} catch (IOException e) {
109-
throw new RuntimeException("Could not save configuration file", e);
124+
SodiumExtraClientMod.logger().warn("Could not move corrupt configuration file", e);
125+
return false;
110126
}
111127
}
112128

@@ -337,14 +353,23 @@ public void sanitize() {
337353
if (this.dimensionOverrides == null) {
338354
this.dimensionOverrides = new Object2ObjectArrayMap<>();
339355
}
340-
this.dimensionOverrides.replaceAll((identifier, settings) -> {
356+
357+
Map<Identifier, AtmosphericFogSettings> sanitizedDimensionOverrides = new Object2ObjectArrayMap<>(this.dimensionOverrides.size());
358+
for (Map.Entry<Identifier, AtmosphericFogSettings> entry : this.dimensionOverrides.entrySet()) {
359+
Identifier identifier = entry.getKey();
360+
if (identifier == null) {
361+
continue;
362+
}
363+
364+
AtmosphericFogSettings settings = entry.getValue();
341365
if (settings == null) {
342366
settings = new AtmosphericFogSettings();
343367
}
344368

345369
settings.sanitize();
346-
return settings;
347-
});
370+
sanitizedDimensionOverrides.put(identifier, settings);
371+
}
372+
this.dimensionOverrides = sanitizedDimensionOverrides;
348373

349374
if (this.protectedGameplay == null) {
350375
this.protectedGameplay = new ProtectedFogSettings();

common/src/main/java/me/flashyreese/mods/sodiumextra/client/fog/FogDistanceHelper.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,11 @@ public enum ProtectedFogType {
4242

4343
public static SodiumExtraGameOptions.AtmosphericFogSettings getAtmosphericSettings(ClientLevel level) {
4444
SodiumExtraGameOptions.FogSettings fogSettings = getFogSettings();
45-
Identifier dimensionId = level.dimension().identifier();
46-
return fogSettings.getAtmospheric(dimensionId);
45+
Identifier dimensionEffectsId = level.dimensionTypeRegistration()
46+
.unwrapKey()
47+
.map(key -> key.identifier())
48+
.orElseGet(() -> level.dimension().identifier());
49+
return fogSettings.getAtmospheric(dimensionEffectsId);
4750
}
4851

4952
public static int getFogDistance(ClientLevel level) {

common/src/main/java/me/flashyreese/mods/sodiumextra/client/recovery/WaylandFullscreenResolutionRecovery.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.google.gson.JsonElement;
66
import com.google.gson.JsonObject;
77
import com.google.gson.JsonParser;
8+
import me.flashyreese.mods.sodiumextra.client.config.ConfigFileIO;
89
import me.flashyreese.mods.sodiumextra.client.config.SodiumExtraConfigKeys;
910
import org.slf4j.Logger;
1011
import org.slf4j.LoggerFactory;
@@ -83,7 +84,7 @@ private static boolean recoverMinecraftOptions(Path optionsFile) {
8384
}
8485

8586
if (changed) {
86-
Files.write(optionsFile, output, StandardCharsets.UTF_8);
87+
ConfigFileIO.writeLinesAtomically(optionsFile, output);
8788
}
8889

8990
return changed;
@@ -109,8 +110,7 @@ private static JsonObject readJsonObject(Path path) {
109110

110111
private static void writeJson(Path path, JsonObject object) {
111112
try {
112-
Files.createDirectories(path.getParent());
113-
Files.writeString(path, GSON.toJson(object) + System.lineSeparator(), StandardCharsets.UTF_8);
113+
ConfigFileIO.writeStringAtomically(path, GSON.toJson(object) + System.lineSeparator());
114114
} catch (IOException e) {
115115
LOGGER.error("Failed to write Sodium Extra fullscreen recovery state", e);
116116
}

0 commit comments

Comments
 (0)