dotnet/wpf · error · FileFormatException

SR.VersionUpdateFailure

Error message

SR.VersionUpdateFailure

What it means

VersionedStreamOwner.PersistVersion writes the compression transform version header to the underlying compound-file stream. If a version header already exists (_dataOffset != 0) but the newly serialized header has a different size, the stream layout would shift and corrupt data, so a FileFormatException is thrown.

Solutions

  1. Rewrite the whole container/stream instead of updating the version header in place (e.g. recreate the package).
  2. Ensure the same .NET/WPF version and transform that originally produced the file is used to update it.
  3. Verify the file was not truncated or edited by external tools; restore from a known-good copy.
  4. If this recurs after a framework upgrade, regenerate all packages with the new runtime rather than patching old ones.

Example fix

// before: updating version header in place on an existing stream
versionedStream.Write(newHeaderData, 0, newHeaderData.Length);
// after: recreate the stream/package when the header layout may differ
using (var newPackage = Package.Open(newPath, FileMode.Create))
{
    CopyPackageContents(oldPackage, newPackage);
}
Defensive patterns

Strategy: validation

Validate before calling

// before updating, confirm the stream either has no version header or will keep its layout
if (existingDataOffset != 0)
{
    long newSize = version.SaveToStream(probeStream); // measure on a scratch stream
    if (newSize != existingDataOffset)
        throw new InvalidOperationException("Version header size changed; recreate the package instead of updating in place");
}

Try / catch

try { owner.WriteAttempt(buffer, 0, len); }
catch (FileFormatException e) { logger.Warn(e, "Header size changed; rebuilding package"); RecreatePackage(sourcePath, destPath); }

Prevention

When it happens

Trigger: Calling WriteAttempt on a VersionedStreamOwner whose stream already contains a version header, where version.SaveToStream(BaseStream) returns an offset different from the stored _dataOffset (header size changed between write and rewrite).

Common situations: Editing an existing compressed XPS/compound-file stream with a different framework or library version that emits a different-length version header; hand-modified or truncated package files; partial writes by another tool.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/878a808de151ad77. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/WindowsBase/MS/Internal/IO/Packaging/CompoundFile/VersionedStreamOwner.cs:353

        /// then only the Updater field is modified.
        /// </remarks>
        private void PersistVersion(FormatVersion version)
        {
            if (!BaseStream.CanWrite)
                throw new NotSupportedException(SR.WriteNotSupported);

            // normalize and save
            long tempPos = checked(BaseStream.Position - _dataOffset);
            BaseStream.Seek(0, SeekOrigin.Begin);

            // update _dataOffset
            long offset = version.SaveToStream(BaseStream);
            _fileVersion = version;     // we know what it is - no need to deserialize

            // existing value - ensure we didn't change sizes as this could lead to
            // data corruption
            if ((_dataOffset != 0) && (offset != _dataOffset))
                throw new FileFormatException(SR.VersionUpdateFailure);

            // at this point we know the offset
            _dataOffset = offset;

            // restore and shift
            checked { BaseStream.Position = tempPos + _dataOffset; }
        }

        /// <summary>
        /// Load and compare feature identifier
        /// </summary>
        /// <remarks>There is no need for this method to maintain any previous Seek pointer.
        /// This method only modifies the stream position when called for the first time with a non-empty
        /// stream.  It is always called from Seek() and set_Position, which subsequently modify the stream
        /// pointer as appropriate after the call.</remarks>
        private void EnsureParsed()
        {
            // empty stream cannot have a version in it

View on GitHub (pinned to 81131a70a4)