MetaFileSystem: also warn against reordering existing mounts

The positional serialization means swapping two Mount() calls breaks every old
savestate just as thoroughly as adding one does, so say so. Also note that the
mount order spans more than MountFileSystems() - the UMD mounts are added first,
by MountGameISO, before we get there.
This commit is contained in:
Henrik Rydgård
2026-08-31 00:23:53 +02:00
parent 7b409af50f
commit 1295512866
+15 -9
View File
@@ -645,18 +645,24 @@ void MetaFileSystem::DoState(PointerWrap &p) {
u32 n = (u32) fileSystems.size();
Do(p, n);
// The mounts are serialized positionally: one section per mount, in mount order, with no
// length to skip by. So a savestate from an older build simply lacks the sections for mounts
// that didn't exist yet, and we have to leave out exactly those to stay lined up. We only know
// how many are missing (n), not which, so the list below says which ones came last.
// The mounts are serialized positionally: one section per mount, in fileSystems order, with no
// length to skip by. That order is the order Mount() first saw each prefix during boot, which
// spans more than MountFileSystems() - booting an ISO, MountGameISO() has already added umd0:,
// umd1:, umd: and disc0: by the time we get there. An older build's savestate simply lacks the
// sections for mounts that didn't exist yet, and we have to leave out exactly those to stay
// lined up. We only know how many are missing (n), not which, hence the list below.
//
// ADDING A NEW MOUNT: prepend its prefix here, or every existing savestate stops loading with
// "Failure at <whatever section follows>". The list is newest first, because a state that is
// missing k mounts is missing the k most recently added ones. Where in the mount order the new
// mount goes doesn't matter - removing it by prefix restores the old relative order either way.
// "Failure at <whatever section follows>". Newest first, because a state missing k mounts is
// missing the k most recently added ones. Where the new mount falls in the order doesn't
// matter - skipping it by prefix leaves the relative order of all the others unchanged.
//
// RENAMING OR REMOVING A MOUNT is not covered by any of this: a section in the state that we
// have no mount for can't be skipped, since we can't know how long it is. That would need a
// DO NOT REORDER EXISTING MOUNTS. Sections are paired with filesystems purely by position, so
// swapping two Mount() calls feeds every old savestate's sections to the wrong filesystems,
// silently and with no version to catch it.
//
// RENAMING OR REMOVING A MOUNT isn't handled here either: a section in the state we have no
// mount for can't be skipped, because we can't know how long it is. Either of those needs a
// format change - storing the prefixes, or a length per section.
static const char * const mountsAddedOverTime[] = { "flash1:", "pfat0:" };