From 927efa7b5d0b53e9f5de90d59c1a86c94aea332e Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sun, 15 Mar 2026 22:22:02 +0000 Subject: [PATCH 1/4] Initial draft at a file-fix API --- fabric-serialization-api-v1/build.gradle | 2 + .../v1/filefix/FileFixHelpers.java | 54 +++++++++++++++++++ .../FileFixSchemaRegisterCallback.java | 35 ++++++++++++ .../filefix/CombinedFileFixOperation.java | 18 +++++++ .../filefix/FileFixHelpersImpl.java | 33 ++++++++++++ .../filefix/DataFixersMixin.java | 28 ++++++++++ .../filefix/FileFixerUpperAccessor.java | 15 ++++++ .../serialization/filefix/package-info.java | 4 ++ .../fabric-serialization-api-v1.mixins.json | 4 +- 9 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/api/serialization/v1/filefix/FileFixHelpers.java create mode 100644 fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/api/serialization/v1/filefix/FileFixSchemaRegisterCallback.java create mode 100644 fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/impl/serialization/filefix/CombinedFileFixOperation.java create mode 100644 fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/impl/serialization/filefix/FileFixHelpersImpl.java create mode 100644 fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/mixin/serialization/filefix/DataFixersMixin.java create mode 100644 fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/mixin/serialization/filefix/FileFixerUpperAccessor.java create mode 100644 fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/mixin/serialization/filefix/package-info.java diff --git a/fabric-serialization-api-v1/build.gradle b/fabric-serialization-api-v1/build.gradle index 714fb5d4d34..00d16f7e15f 100644 --- a/fabric-serialization-api-v1/build.gradle +++ b/fabric-serialization-api-v1/build.gradle @@ -3,3 +3,5 @@ version = getSubprojectVersion(project) loom { accessWidenerPath = file('src/main/resources/fabric-serialization-api-v1.classtweaker') } + +moduleDependencies(project, ['fabric-api-base']) diff --git a/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/api/serialization/v1/filefix/FileFixHelpers.java b/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/api/serialization/v1/filefix/FileFixHelpers.java new file mode 100644 index 00000000000..0e63f280383 --- /dev/null +++ b/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/api/serialization/v1/filefix/FileFixHelpers.java @@ -0,0 +1,54 @@ +package net.fabricmc.fabric.api.serialization.v1.filefix; + +import com.mojang.datafixers.schemas.Schema; + +import net.fabricmc.fabric.impl.serialization.filefix.CombinedFileFixOperation; +import net.fabricmc.fabric.impl.serialization.filefix.FileFixHelpersImpl; + +import net.fabricmc.fabric.mixin.serialization.filefix.FileFixerUpperAccessor; + +import net.minecraft.resources.Identifier; +import net.minecraft.util.filefix.FileFix; +import net.minecraft.util.filefix.operations.FileFixOperation; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +public interface FileFixHelpers { + + static FileFixOperation createDimensionDataMoveOperation(String oldSaveId, Identifier newSaveId) { + return createDimensionDataMoveOperation(Map.of(oldSaveId, newSaveId)); + } + + static FileFixOperation createDimensionDataMoveOperation(Map saveIdMap) { + List operations = new ArrayList<>(); + operations.addAll(FileFixHelpersImpl.createDimensionMoveOperations(saveIdMap, "", "dimensions/minecraft/overworld")); + operations.addAll(FileFixHelpersImpl.createDimensionMoveOperations(saveIdMap, "DIM-1", "dimensions/minecraft/the_nether")); + operations.addAll(FileFixHelpersImpl.createDimensionMoveOperations(saveIdMap, "DIM1", "dimensions/minecraft/the_end")); + operations.add(FileFixHelpersImpl.createCustomDimensionDataMoveOperation(saveIdMap)); + return new CombinedFileFixOperation(operations); + } + + static Function createDimensionDataMoveFileFix(String oldSaveId, Identifier newSaveId) { + return createDimensionDataMoveFileFix(Map.of(oldSaveId, newSaveId)); + } + + static Function createDimensionDataMoveFileFix(Map saveIdMap) { + return schema -> new FileFix(schema) { + @Override + public void makeFixer() { + addFileFixOperation(createDimensionDataMoveOperation(saveIdMap)); + } + }; + } + + static void registerDimensionDataMoveFileFix(String oldSaveId, Identifier newSaveId) { + registerDimensionDataMoveFileFix(Map.of(oldSaveId, newSaveId)); + } + + static void registerDimensionDataMoveFileFix(Map saveIdMap) { + FileFixSchemaRegisterCallback.registerFileFixes(FileFixerUpperAccessor.getFileFixerIntroductionVersion(), createDimensionDataMoveFileFix(saveIdMap)); + } +} diff --git a/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/api/serialization/v1/filefix/FileFixSchemaRegisterCallback.java b/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/api/serialization/v1/filefix/FileFixSchemaRegisterCallback.java new file mode 100644 index 00000000000..7afcec09965 --- /dev/null +++ b/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/api/serialization/v1/filefix/FileFixSchemaRegisterCallback.java @@ -0,0 +1,35 @@ +package net.fabricmc.fabric.api.serialization.v1.filefix; + +import com.mojang.datafixers.schemas.Schema; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +import net.minecraft.util.filefix.FileFix; +import net.minecraft.util.filefix.FileFixerUpper; + +import java.util.function.Function; + +@FunctionalInterface +public interface FileFixSchemaRegisterCallback { + + Event EVENT = EventFactory.createArrayBacked(FileFixSchemaRegisterCallback.class, + listeners -> (fileFixerUpper, schema, version) -> { + for (FileFixSchemaRegisterCallback callback : listeners) { + callback.schemaRegistered(fileFixerUpper, schema, version); + } + }); + + void schemaRegistered(FileFixerUpper.Builder fileFixerUpper, Schema schema, int version); + + @SafeVarargs + static void registerFileFixes(int version, Function... fixes) { + EVENT.register((fileFixerUpper, schema, schemaVersion) -> { + if (schemaVersion == version) { + for (Function fix : fixes) { + fileFixerUpper.addFixer(fix.apply(schema)); + } + } + }); + } +} diff --git a/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/impl/serialization/filefix/CombinedFileFixOperation.java b/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/impl/serialization/filefix/CombinedFileFixOperation.java new file mode 100644 index 00000000000..8458a6b4218 --- /dev/null +++ b/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/impl/serialization/filefix/CombinedFileFixOperation.java @@ -0,0 +1,18 @@ +package net.fabricmc.fabric.impl.serialization.filefix; + +import net.minecraft.util.filefix.operations.FileFixOperation; +import net.minecraft.util.worldupdate.UpgradeProgress; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +public record CombinedFileFixOperation(List operations) implements FileFixOperation { + + @Override + public void fix(Path baseDirectory, UpgradeProgress upgradeProgress) throws IOException { + for (FileFixOperation operation : operations) { + operation.fix(baseDirectory, upgradeProgress); + } + } +} diff --git a/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/impl/serialization/filefix/FileFixHelpersImpl.java b/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/impl/serialization/filefix/FileFixHelpersImpl.java new file mode 100644 index 00000000000..7dcb819806c --- /dev/null +++ b/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/impl/serialization/filefix/FileFixHelpersImpl.java @@ -0,0 +1,33 @@ +package net.fabricmc.fabric.impl.serialization.filefix; + +import net.minecraft.resources.Identifier; +import net.minecraft.util.filefix.access.FileRelation; +import net.minecraft.util.filefix.operations.FileFixOperation; +import net.minecraft.util.filefix.operations.FileFixOperations; + +import java.util.List; +import java.util.Map; + +public interface FileFixHelpersImpl { + + static List createDimensionMoveOperations(Map saveIdMap, + String oldDimensionPath, + String newDimensionPath) { + return saveIdMap.entrySet().stream() + .map(entry -> (FileFixOperation) + FileFixOperations.move(oldDimensionPath + "/data/" + entry.getKey() + ".dat", + newDimensionPath + "/data/" + entry.getValue().getNamespace() + "/" + entry.getValue().getPath() + ".dat")) + .toList(); + } + + static FileFixOperation createCustomDimensionDataMoveOperation(Map saveIdMap) { + return FileFixOperations.applyInFolders( + FileRelation.DIMENSIONS_DATA, + saveIdMap.entrySet().stream() + // example.dat -> example_mod/example.dat + .map(entry -> (FileFixOperation) + FileFixOperations.move(entry.getKey() + ".dat", entry.getValue().getNamespace() + "/" + entry.getValue().getPath() + ".dat")) + .toList() + ); + } +} diff --git a/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/mixin/serialization/filefix/DataFixersMixin.java b/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/mixin/serialization/filefix/DataFixersMixin.java new file mode 100644 index 00000000000..fa5a5bf8ba5 --- /dev/null +++ b/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/mixin/serialization/filefix/DataFixersMixin.java @@ -0,0 +1,28 @@ +package net.fabricmc.fabric.mixin.serialization.filefix; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.mojang.datafixers.DataFixerBuilder; +import com.mojang.datafixers.schemas.Schema; + +import net.fabricmc.fabric.api.serialization.v1.filefix.FileFixSchemaRegisterCallback; + +import net.minecraft.util.datafix.DataFixers; + +import net.minecraft.util.filefix.FileFixerUpper; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.function.BiFunction; + +@Mixin(DataFixers.class) +public abstract class DataFixersMixin { + + @WrapOperation(method = "addFixers", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/filefix/FileFixerUpper$Builder;addSchema(Lcom/mojang/datafixers/DataFixerBuilder;ILjava/util/function/BiFunction;)Lcom/mojang/datafixers/schemas/Schema;")) + private static Schema runSchemaRegisterCallback(FileFixerUpper.Builder instance, DataFixerBuilder fixerUpper, int version, BiFunction factory, Operation original) { + Schema schema = original.call(instance, fixerUpper, version, factory); + FileFixSchemaRegisterCallback.EVENT.invoker().schemaRegistered(instance, schema, version); + return schema; + } +} diff --git a/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/mixin/serialization/filefix/FileFixerUpperAccessor.java b/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/mixin/serialization/filefix/FileFixerUpperAccessor.java new file mode 100644 index 00000000000..37fc083f1e5 --- /dev/null +++ b/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/mixin/serialization/filefix/FileFixerUpperAccessor.java @@ -0,0 +1,15 @@ +package net.fabricmc.fabric.mixin.serialization.filefix; + +import net.minecraft.util.filefix.FileFixerUpper; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(FileFixerUpper.class) +public interface FileFixerUpperAccessor { + + @Accessor("FILE_FIXER_INTRODUCTION_VERSION") + static int getFileFixerIntroductionVersion() { + throw new AssertionError(); + } +} diff --git a/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/mixin/serialization/filefix/package-info.java b/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/mixin/serialization/filefix/package-info.java new file mode 100644 index 00000000000..9ee669110c7 --- /dev/null +++ b/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/mixin/serialization/filefix/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package net.fabricmc.fabric.mixin.serialization.filefix; + +import org.jspecify.annotations.NullMarked; diff --git a/fabric-serialization-api-v1/src/main/resources/fabric-serialization-api-v1.mixins.json b/fabric-serialization-api-v1/src/main/resources/fabric-serialization-api-v1.mixins.json index 9c8101feae0..431cca0b2bd 100644 --- a/fabric-serialization-api-v1/src/main/resources/fabric-serialization-api-v1.mixins.json +++ b/fabric-serialization-api-v1/src/main/resources/fabric-serialization-api-v1.mixins.json @@ -6,7 +6,9 @@ "TagValueInputMixin", "TagValueOutputMixin", "ValueInputMixin", - "ValueOutputMixin" + "ValueOutputMixin", + "filefix.DataFixersMixin", + "filefix.FileFixerUpperAccessor" ], "injectors": { "defaultRequire": 1 From 3bf94cabd27f887212fc42d55b4575bbf5ccd168 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sun, 15 Mar 2026 22:39:36 +0000 Subject: [PATCH 2/4] Add utility methods for global saved-data file fixes --- .../v1/filefix/FileFixHelpers.java | 36 +++++++++++++++++++ .../filefix/FileFixHelpersImpl.java | 13 +++---- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/api/serialization/v1/filefix/FileFixHelpers.java b/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/api/serialization/v1/filefix/FileFixHelpers.java index 0e63f280383..bd6594ce0ab 100644 --- a/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/api/serialization/v1/filefix/FileFixHelpers.java +++ b/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/api/serialization/v1/filefix/FileFixHelpers.java @@ -9,7 +9,9 @@ import net.minecraft.resources.Identifier; import net.minecraft.util.filefix.FileFix; +import net.minecraft.util.filefix.access.FileRelation; import net.minecraft.util.filefix.operations.FileFixOperation; +import net.minecraft.util.filefix.operations.FileFixOperations; import java.util.ArrayList; import java.util.List; @@ -51,4 +53,38 @@ static void registerDimensionDataMoveFileFix(String oldSaveId, Identifier newSav static void registerDimensionDataMoveFileFix(Map saveIdMap) { FileFixSchemaRegisterCallback.registerFileFixes(FileFixerUpperAccessor.getFileFixerIntroductionVersion(), createDimensionDataMoveFileFix(saveIdMap)); } + + static FileFixOperation createGlobalDataMoveOperation(String oldSaveId, Identifier newSaveId) { + return createGlobalDataMoveOperation(Map.of(oldSaveId, newSaveId)); + } + + static FileFixOperation createGlobalDataMoveOperation(Map saveIdMap) { + return FileFixOperations.applyInFolders( + FileRelation.DATA, + saveIdMap.entrySet().stream() + .map(entry -> FileFixHelpersImpl.createNamespacedDataMoveOperation(entry.getKey(), entry.getValue(), "", "")) + .toList() + ); + } + + static Function createGlobalDataMoveFileFix(String oldSaveId, Identifier newSaveId) { + return createGlobalDataMoveFileFix(Map.of(oldSaveId, newSaveId)); + } + + static Function createGlobalDataMoveFileFix(Map saveIdMap) { + return schema -> new FileFix(schema) { + @Override + public void makeFixer() { + addFileFixOperation(createGlobalDataMoveOperation(saveIdMap)); + } + }; + } + + static void registerGlobalDataMoveFileFix(String oldSaveId, Identifier newSaveId) { + registerGlobalDataMoveFileFix(Map.of(oldSaveId, newSaveId)); + } + + static void registerGlobalDataMoveFileFix(Map saveIdMap) { + FileFixSchemaRegisterCallback.registerFileFixes(FileFixerUpperAccessor.getFileFixerIntroductionVersion(), createGlobalDataMoveFileFix(saveIdMap)); + } } diff --git a/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/impl/serialization/filefix/FileFixHelpersImpl.java b/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/impl/serialization/filefix/FileFixHelpersImpl.java index 7dcb819806c..9130e3e642e 100644 --- a/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/impl/serialization/filefix/FileFixHelpersImpl.java +++ b/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/impl/serialization/filefix/FileFixHelpersImpl.java @@ -14,9 +14,8 @@ static List createDimensionMoveOperations(Map (FileFixOperation) - FileFixOperations.move(oldDimensionPath + "/data/" + entry.getKey() + ".dat", - newDimensionPath + "/data/" + entry.getValue().getNamespace() + "/" + entry.getValue().getPath() + ".dat")) + .map(entry -> createNamespacedDataMoveOperation(entry.getKey(), entry.getValue(), + oldDimensionPath + "/data/", newDimensionPath + "/data/")) .toList(); } @@ -24,10 +23,12 @@ static FileFixOperation createCustomDimensionDataMoveOperation(Map example_mod/example.dat - .map(entry -> (FileFixOperation) - FileFixOperations.move(entry.getKey() + ".dat", entry.getValue().getNamespace() + "/" + entry.getValue().getPath() + ".dat")) + .map(entry -> createNamespacedDataMoveOperation(entry.getKey(), entry.getValue(), "", "")) .toList() ); } + + static FileFixOperation createNamespacedDataMoveOperation(String oldId, Identifier newId, String oldPrefix, String newPrefix) { + return FileFixOperations.move(oldPrefix + oldId + ".dat", newPrefix + newId.getNamespace() + "/" + newId.getPath() + ".dat"); + } } From f454f0d1841ed34095c493e817cd70f7fa64adf4 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 27 Mar 2026 10:22:23 +0000 Subject: [PATCH 3/4] Add documentation to FileFixSchemaRegisterCallback --- .../filefix/FileFixSchemaRegisterCallback.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/api/serialization/v1/filefix/FileFixSchemaRegisterCallback.java b/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/api/serialization/v1/filefix/FileFixSchemaRegisterCallback.java index 7afcec09965..6ca5f4df536 100644 --- a/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/api/serialization/v1/filefix/FileFixSchemaRegisterCallback.java +++ b/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/api/serialization/v1/filefix/FileFixSchemaRegisterCallback.java @@ -10,6 +10,14 @@ import java.util.function.Function; +/** + * Event for registering file fixes. This event is called for every {@link Schema} added to + * Minecraft's {@link FileFixerUpper}, before any of vanilla's fixes are added to it. + * + *

Please note that this event is called early during the game launch, before {@link net.fabricmc.api.ModInitializer}s + * are run. Therefore, make sure to register callbacks early during the mod loading process, using + * a {@link net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint}.

+ */ @FunctionalInterface public interface FileFixSchemaRegisterCallback { @@ -22,6 +30,16 @@ public interface FileFixSchemaRegisterCallback { void schemaRegistered(FileFixerUpper.Builder fileFixerUpper, Schema schema, int version); + /** + * Can be used to easily register file fixes for a data version. This method registers a callback + * at {@link FileFixSchemaRegisterCallback#EVENT}, which adds the given {@code fixes} to the + * game's {@link FileFixerUpper} at the given data version. + * + *

Please note that this method does not throw for invalid data versions.

+ * + * @param version the data version to register the file fixes for. + * @param fixes the file fixes to register. + */ @SafeVarargs static void registerFileFixes(int version, Function... fixes) { EVENT.register((fileFixerUpper, schema, schemaVersion) -> { From 4d443b4be37b7fedc724965a94180773edd83a24 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 27 Mar 2026 10:40:12 +0000 Subject: [PATCH 4/4] Clarify FileFixSchemaRegisterCallback#registerFileFixes documentation --- .../v1/filefix/FileFixSchemaRegisterCallback.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/api/serialization/v1/filefix/FileFixSchemaRegisterCallback.java b/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/api/serialization/v1/filefix/FileFixSchemaRegisterCallback.java index 6ca5f4df536..ef1d64e4881 100644 --- a/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/api/serialization/v1/filefix/FileFixSchemaRegisterCallback.java +++ b/fabric-serialization-api-v1/src/main/java/net/fabricmc/fabric/api/serialization/v1/filefix/FileFixSchemaRegisterCallback.java @@ -35,7 +35,9 @@ public interface FileFixSchemaRegisterCallback { * at {@link FileFixSchemaRegisterCallback#EVENT}, which adds the given {@code fixes} to the * game's {@link FileFixerUpper} at the given data version. * - *

Please note that this method does not throw for invalid data versions.

+ *

Please note that this method does not throw for invalid data versions, and that + * fixes can only be registered for a data version that has file fixes in vanilla, because + * there won't be a schema registered for those versions.

* * @param version the data version to register the file fixes for. * @param fixes the file fixes to register.