Fix RS2 disk sync: use save() return value + codec reflection fallback

Save side:
- save() returns data in a NEW CompoundTag (fixed in previous commit)
- Now logs full NBT structure for debugging (describeNbtStructure)
- If UUID not found in save() NBT, falls back to reflection on
  internal entries map + codec.encodeStart() to serialize directly

Restore side:
- Rewritten to use raw Codec types to avoid generic compilation issues
- Decodes stored NBT via the same map codec, then repo.set() to inject

Both sides now have comprehensive logging to diagnose any remaining
format issues in production.

Vyrriox
This commit is contained in:
laforetbrut 2026-03-26 20:14:26 +01:00
parent 4e2574a147
commit bce7a73cb8

View File

@ -443,6 +443,7 @@ public class ModsSupport {
* This avoids stale .dat file issues and doesn't call dataStorage.save() which crashes
* with fastasyncworldsave.
*/
@SuppressWarnings("unchecked")
public static void storeRefinedStorageDisks(Player player) {
if (!ModList.get().isLoaded("refinedstorage")) return;
if (!(player instanceof net.minecraft.server.level.ServerPlayer sp)) return;
@ -453,29 +454,65 @@ public class ModsSupport {
try {
com.refinedmods.refinedstorage.common.api.storage.StorageRepository repo =
com.refinedmods.refinedstorage.common.api.RefinedStorageApi.INSTANCE.getStorageRepository(sp.serverLevel());
// Use save() to serialize the in-memory state to a CompoundTag (does NOT touch disk)
if (!(repo instanceof net.minecraft.world.level.saveddata.SavedData sd)) return;
// FIX: save() RETURNS the data in a new CompoundTag, it does NOT fill the input parameter
// STRATEGY: Use save() to get the full serialized NBT, search for UUID entries.
// If save() format doesn't match our parsing, fall back to reflection on the
// internal entries map + codec to serialize individual entries.
net.minecraft.nbt.CompoundTag fullNbt = sd.save(new net.minecraft.nbt.CompoundTag(), sp.getServer().registryAccess());
// Log the top-level structure once for debugging
PlayerSync.LOGGER.debug("RS2 save() NBT keys: {}", fullNbt.getAllKeys());
// Log structure for debugging
PlayerSync.LOGGER.info("RS2 save() NBT: {} keys, types: {}", fullNbt.getAllKeys().size(), describeNbtStructure(fullNbt));
for (UUID uuid : diskUuids) {
String uuidStr = uuid.toString();
// Search in the full NBT (try direct, then nested under any key)
net.minecraft.nbt.CompoundTag entryNbt = findRS2EntryInNbt(fullNbt, uuidStr);
if (entryNbt != null && !entryNbt.isEmpty()) {
saveStorageContents(uuid, entryNbt);
PlayerSync.LOGGER.info("Saved RS2 disk data for UUID {}", uuid);
} else {
// Fallback: check if repo.get() returns data (means codec format is different)
if (repo.get(uuid).isPresent()) {
PlayerSync.LOGGER.warn("RS2 disk UUID {} exists in repo but NOT found in save() NBT. Keys at top: {}", uuid, fullNbt.getAllKeys());
PlayerSync.LOGGER.info("Saved RS2 disk data for UUID {} via save() NBT", uuid);
continue;
}
// Fallback: use reflection to get the codec and serialize the single entry
if (!repo.get(uuid).isPresent()) {
PlayerSync.LOGGER.debug("RS2 disk UUID {} has no storage data (empty disk)", uuid);
continue;
}
PlayerSync.LOGGER.info("RS2 UUID {} not in save() NBT, using codec fallback", uuid);
try {
// Get the map codec from StorageRepositoryImpl
java.lang.reflect.Method getMapCodecMethod =
repo.getClass().getDeclaredMethod("getMapCodec", Runnable.class);
getMapCodecMethod.setAccessible(true);
@SuppressWarnings("rawtypes")
com.mojang.serialization.Codec codec = (com.mojang.serialization.Codec)
getMapCodecMethod.invoke(null, (Runnable) () -> {});
// Get the entries map via reflection
java.lang.reflect.Field entriesField = repo.getClass().getDeclaredField("entries");
entriesField.setAccessible(true);
java.util.Map<UUID, ?> entries = (java.util.Map<UUID, ?>) entriesField.get(repo);
Object storageEntry = entries.get(uuid);
if (storageEntry == null) continue;
// Encode a single-entry map to NBT using the codec
java.util.Map<UUID, Object> singleEntry = java.util.Map.of(uuid, storageEntry);
var ops = sp.getServer().registryAccess().createSerializationContext(
net.minecraft.nbt.NbtOps.INSTANCE);
var encodeResult = codec.encodeStart(ops, singleEntry);
if (encodeResult.result().isPresent()) {
net.minecraft.nbt.Tag encodedTag = (net.minecraft.nbt.Tag) encodeResult.result().get();
if (encodedTag instanceof net.minecraft.nbt.CompoundTag encodedCompound) {
saveStorageContents(uuid, encodedCompound);
PlayerSync.LOGGER.info("Saved RS2 disk data for UUID {} via codec reflection", uuid);
}
} else {
PlayerSync.LOGGER.debug("RS2 disk UUID {} has no storage data (empty disk)", uuid);
PlayerSync.LOGGER.error("RS2 codec encode failed for UUID {}: {}", uuid, encodeResult.error());
}
} catch (Exception reflectEx) {
PlayerSync.LOGGER.error("RS2 reflection fallback failed for UUID {}", uuid, reflectEx);
}
}
} catch (Exception e) {
@ -483,10 +520,28 @@ public class ModsSupport {
}
}
/** Describes the top-level NBT structure for debugging */
private static String describeNbtStructure(net.minecraft.nbt.CompoundTag tag) {
StringBuilder sb = new StringBuilder("{");
for (String key : tag.getAllKeys()) {
net.minecraft.nbt.Tag val = tag.get(key);
sb.append(key).append("=").append(val != null ? val.getType().getName() : "null");
if (val instanceof net.minecraft.nbt.CompoundTag ct) {
sb.append("(").append(ct.getAllKeys().size()).append(" keys)");
} else if (val instanceof net.minecraft.nbt.ListTag lt) {
sb.append("[").append(lt.size()).append(" entries]");
}
sb.append(", ");
}
sb.append("}");
return sb.toString();
}
/**
* Restores RS2 disk storage using the codec to decode entries and set() to inject them.
* Uses in-memory API only - no .dat file manipulation.
* Restores RS2 disk storage using the codec to decode entries and repo.set() to inject.
* The saved data was encoded via the map codec during save, so we decode with the same codec.
*/
@SuppressWarnings("unchecked")
public static void restoreRefinedStorageDisks(Player player) {
if (!ModList.get().isLoaded("refinedstorage")) return;
if (!(player instanceof net.minecraft.server.level.ServerPlayer sp)) return;
@ -497,65 +552,43 @@ public class ModsSupport {
try {
com.refinedmods.refinedstorage.common.api.storage.StorageRepository repo =
com.refinedmods.refinedstorage.common.api.RefinedStorageApi.INSTANCE.getStorageRepository(sp.serverLevel());
if (!(repo instanceof net.minecraft.world.level.saveddata.SavedData sd)) return;
// Get the map codec via reflection (same codec used for save)
@SuppressWarnings("rawtypes")
com.mojang.serialization.Codec mapCodec;
try {
java.lang.reflect.Method getMapCodecMethod =
repo.getClass().getDeclaredMethod("getMapCodec", Runnable.class);
getMapCodecMethod.setAccessible(true);
mapCodec = (com.mojang.serialization.Codec) getMapCodecMethod.invoke(null, (Runnable) () -> {});
} catch (Exception e) {
PlayerSync.LOGGER.error("Cannot get RS2 map codec, disk restore will fail", e);
return;
}
var ops = sp.getServer().registryAccess().createSerializationContext(
net.minecraft.nbt.NbtOps.INSTANCE);
@SuppressWarnings("rawtypes")
final com.mojang.serialization.Codec fCodec = mapCodec;
for (UUID uuid : diskUuids) {
restoreStorageContents(uuid, (entryNbt) -> {
restoreStorageContents(uuid, (storedNbt) -> {
try {
// Strategy: create a full-format CompoundTag with just this entry,
// then use the codec (via a temp load) to decode and set
// Wrap the entry in the same format that save() produces
net.minecraft.nbt.CompoundTag singleEntry = new net.minecraft.nbt.CompoundTag();
singleEntry.put(uuid.toString(), entryNbt);
// Try to decode using the repo's codec via reflection
try {
java.lang.reflect.Method getMapCodecMethod =
repo.getClass().getDeclaredMethod("getMapCodec", Runnable.class);
getMapCodecMethod.setAccessible(true);
@SuppressWarnings("rawtypes")
com.mojang.serialization.Codec codec = (com.mojang.serialization.Codec)
getMapCodecMethod.invoke(null, (Runnable) () -> {});
var ops = sp.getServer().registryAccess().createSerializationContext(
net.minecraft.nbt.NbtOps.INSTANCE);
var result = codec.decode(ops, singleEntry);
java.util.Optional<?> opt = result.result();
if (opt.isPresent()) {
com.mojang.datafixers.util.Pair<?, ?> pair =
(com.mojang.datafixers.util.Pair<?, ?>) opt.get();
@SuppressWarnings("unchecked")
java.util.Map<UUID, ?> decoded = (java.util.Map<UUID, ?>) pair.getFirst();
for (java.util.Map.Entry<UUID, ?> entry : decoded.entrySet()) {
repo.set(entry.getKey(),
(com.refinedmods.refinedstorage.common.api.storage.SerializableStorage)
entry.getValue());
PlayerSync.LOGGER.info("Restored RS2 disk data for UUID {} via codec", entry.getKey());
}
return;
@SuppressWarnings("unchecked")
com.mojang.serialization.DataResult<?> dataResult = fCodec.decode(ops, storedNbt);
Optional<?> opt = dataResult.result();
if (opt.isPresent()) {
com.mojang.datafixers.util.Pair<?, ?> pair = (com.mojang.datafixers.util.Pair<?, ?>) opt.get();
@SuppressWarnings("unchecked")
java.util.Map<UUID, ?> decoded = (java.util.Map<UUID, ?>) pair.getFirst();
for (java.util.Map.Entry<UUID, ?> entry : decoded.entrySet()) {
repo.set(entry.getKey(),
(com.refinedmods.refinedstorage.common.api.storage.SerializableStorage) entry.getValue());
PlayerSync.LOGGER.info("Restored RS2 disk data for UUID {}", entry.getKey());
}
} catch (Exception codecEx) {
PlayerSync.LOGGER.debug("RS2 codec restore failed, falling back to direct NBT injection", codecEx);
} else {
PlayerSync.LOGGER.error("RS2 codec decode failed for UUID {}", uuid);
}
// Fallback: inject directly into the SavedData's internal state via save/load cycle
// Get current full data, inject our entry, then reload
net.minecraft.nbt.CompoundTag fullNbt = new net.minecraft.nbt.CompoundTag();
sd.save(fullNbt, sp.getServer().registryAccess());
fullNbt.put(uuid.toString(), entryNbt); // inject at top level
// Use reflection to call the load method
try {
java.lang.reflect.Method loadMethod = repo.getClass().getDeclaredMethod(
"load", net.minecraft.nbt.CompoundTag.class,
net.minecraft.core.HolderLookup.Provider.class);
loadMethod.setAccessible(true);
// Create a new instance and copy entries
// Actually, just reload from the modified NBT
} catch (Exception loadEx) {
PlayerSync.LOGGER.debug("RS2 load reflection failed", loadEx);
}
PlayerSync.LOGGER.warn("RS2 disk UUID {} - could not restore via any method", uuid);
} catch (Exception e) {
PlayerSync.LOGGER.error("Error restoring RS2 disk data for UUID {}", uuid, e);
}