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
- Rewrite the whole container/stream instead of updating the version header in place (e.g. recreate the package).
- Ensure the same .NET/WPF version and transform that originally produced the file is used to update it.
- Verify the file was not truncated or edited by external tools; restore from a known-good copy.
- 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
- Pin the runtime/tool version used to write and update compound-file packages
- Never hand-edit version headers with external tools
- Regenerate packages wholesale rather than patching headers in place
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
- SR.PublishLicenseStreamCorrupt
- DataSpace map entry is not valid.
- Document does not contain a package.
- Document does not contain any rights management-protected…
- File contains corrupted data.
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 itView on GitHub (pinned to 81131a70a4)