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 * This avoids stale .dat file issues and doesn't call dataStorage.save() which crashes
* with fastasyncworldsave. * with fastasyncworldsave.
*/ */
@SuppressWarnings("unchecked")
public static void storeRefinedStorageDisks(Player player) { public static void storeRefinedStorageDisks(Player player) {
if (!ModList.get().isLoaded("refinedstorage")) return; if (!ModList.get().isLoaded("refinedstorage")) return;
if (!(player instanceof net.minecraft.server.level.ServerPlayer sp)) return; if (!(player instanceof net.minecraft.server.level.ServerPlayer sp)) return;
@ -453,29 +454,65 @@ public class ModsSupport {
try { try {
com.refinedmods.refinedstorage.common.api.storage.StorageRepository repo = com.refinedmods.refinedstorage.common.api.storage.StorageRepository repo =
com.refinedmods.refinedstorage.common.api.RefinedStorageApi.INSTANCE.getStorageRepository(sp.serverLevel()); 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; 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()); net.minecraft.nbt.CompoundTag fullNbt = sd.save(new net.minecraft.nbt.CompoundTag(), sp.getServer().registryAccess());
// Log the top-level structure once for debugging // Log structure for debugging
PlayerSync.LOGGER.debug("RS2 save() NBT keys: {}", fullNbt.getAllKeys()); PlayerSync.LOGGER.info("RS2 save() NBT: {} keys, types: {}", fullNbt.getAllKeys().size(), describeNbtStructure(fullNbt));
for (UUID uuid : diskUuids) { for (UUID uuid : diskUuids) {
String uuidStr = uuid.toString(); String uuidStr = uuid.toString();
// Search in the full NBT (try direct, then nested under any key)
net.minecraft.nbt.CompoundTag entryNbt = findRS2EntryInNbt(fullNbt, uuidStr); net.minecraft.nbt.CompoundTag entryNbt = findRS2EntryInNbt(fullNbt, uuidStr);
if (entryNbt != null && !entryNbt.isEmpty()) { if (entryNbt != null && !entryNbt.isEmpty()) {
saveStorageContents(uuid, entryNbt); saveStorageContents(uuid, entryNbt);
PlayerSync.LOGGER.info("Saved RS2 disk data for UUID {}", uuid); PlayerSync.LOGGER.info("Saved RS2 disk data for UUID {} via save() NBT", uuid);
} else { continue;
// 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()); // 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 { } 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) { } 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. * Restores RS2 disk storage using the codec to decode entries and repo.set() to inject.
* Uses in-memory API only - no .dat file manipulation. * 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) { public static void restoreRefinedStorageDisks(Player player) {
if (!ModList.get().isLoaded("refinedstorage")) return; if (!ModList.get().isLoaded("refinedstorage")) return;
if (!(player instanceof net.minecraft.server.level.ServerPlayer sp)) return; if (!(player instanceof net.minecraft.server.level.ServerPlayer sp)) return;
@ -497,65 +552,43 @@ public class ModsSupport {
try { try {
com.refinedmods.refinedstorage.common.api.storage.StorageRepository repo = com.refinedmods.refinedstorage.common.api.storage.StorageRepository repo =
com.refinedmods.refinedstorage.common.api.RefinedStorageApi.INSTANCE.getStorageRepository(sp.serverLevel()); 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) { for (UUID uuid : diskUuids) {
restoreStorageContents(uuid, (entryNbt) -> { restoreStorageContents(uuid, (storedNbt) -> {
try { try {
// Strategy: create a full-format CompoundTag with just this entry, @SuppressWarnings("unchecked")
// then use the codec (via a temp load) to decode and set com.mojang.serialization.DataResult<?> dataResult = fCodec.decode(ops, storedNbt);
// Wrap the entry in the same format that save() produces Optional<?> opt = dataResult.result();
net.minecraft.nbt.CompoundTag singleEntry = new net.minecraft.nbt.CompoundTag(); if (opt.isPresent()) {
singleEntry.put(uuid.toString(), entryNbt); com.mojang.datafixers.util.Pair<?, ?> pair = (com.mojang.datafixers.util.Pair<?, ?>) opt.get();
@SuppressWarnings("unchecked")
// Try to decode using the repo's codec via reflection java.util.Map<UUID, ?> decoded = (java.util.Map<UUID, ?>) pair.getFirst();
try { for (java.util.Map.Entry<UUID, ?> entry : decoded.entrySet()) {
java.lang.reflect.Method getMapCodecMethod = repo.set(entry.getKey(),
repo.getClass().getDeclaredMethod("getMapCodec", Runnable.class); (com.refinedmods.refinedstorage.common.api.storage.SerializableStorage) entry.getValue());
getMapCodecMethod.setAccessible(true); PlayerSync.LOGGER.info("Restored RS2 disk data for UUID {}", entry.getKey());
@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;
} }
} catch (Exception codecEx) { } else {
PlayerSync.LOGGER.debug("RS2 codec restore failed, falling back to direct NBT injection", codecEx); 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) { } catch (Exception e) {
PlayerSync.LOGGER.error("Error restoring RS2 disk data for UUID {}", uuid, e); PlayerSync.LOGGER.error("Error restoring RS2 disk data for UUID {}", uuid, e);
} }