// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
// Copyright 2006 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it.
// =================================================================================================
#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
#include "public/include/XMP_Const.h"
#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp"
#include "source/EndianUtils.hpp"
#include "source/XIO.hpp"
#include <string.h>
// =================================================================================================
/// \file PSIR_FileWriter.cpp
/// \brief Implementation of the file-based or read-write form of PSIR_Manager.
// =================================================================================================
// =================================================================================================
// IsMetadataImgRsrc
// =================
static inline bool IsMetadataImgRsrc ( XMP_Uns16 id )
{
if ( id == 0 ) return false;
int i;
for ( i = 0; id < kPSIR_MetadataIDs[i]; ++i ) {}
if ( id == kPSIR_MetadataIDs[i] ) return true;
return false;
} // IsMetadataImgRsrc
// =================================================================================================
// PSIR_FileWriter::DeleteExistingInfo
// ===================================
//
// Delete all existing info about image resources.
void PSIR_FileWriter::DeleteExistingInfo()
{
XMP_Assert ( ! (this->memParsed && this->fileParsed) );
if ( this->memParsed ) {
if ( this->ownedContent ) free ( this->memContent );
} else if ( this->fileParsed ) {
InternalRsrcMap::iterator irPos = this->imgRsrcs.begin();
InternalRsrcMap::iterator irEnd = this->imgRsrcs.end();
for ( ; irPos != irEnd; ++irPos ) irPos->second.changed = true; // Fool the InternalRsrcInfo destructor.
}
this->imgRsrcs.clear();
this->memContent = 0;
this->memLength = 0;
this->changed = false;
this->legacyDeleted = false;
this->memParsed = false;
this->fileParsed = false;
this->ownedContent = false;
} // PSIR_FileWriter::DeleteExistingInfo
// =================================================================================================
// PSIR_FileWriter::~PSIR_FileWriter
// =================================
PSIR_FileWriter::~PSIR_FileWriter()
{
XMP_Assert ( ! (this->memParsed && this->fileParsed) );
if ( this->ownedContent ) {
XMP_Assert ( this->memContent != 0 );
free ( this->memContent );
}
} // PSIR_FileWriter::~PSIR_FileWriter
// =================================================================================================
// PSIR_FileWriter::GetImgRsrc
// ===========================
bool PSIR_FileWriter::GetImgRsrc ( XMP_Uns16 id, ImgRsrcInfo* info ) const
{
InternalRsrcMap::const_iterator rsrcPos = this->imgRsrcs.find ( id );
if ( rsrcPos == this->imgRsrcs.end() ) return false;
const InternalRsrcInfo & rsrcInfo = rsrcPos->second;
if ( info != 0 ) {
info->id = rsrcInfo.id;
info->dataLen = rsrcInfo.dataLen;
info->dataPtr = rsrcInfo.dataPtr;
info->origOffset = rsrcInfo.origOffset;
}
return true;
} // PSIR_FileWriter::GetImgRsrc
// =================================================================================================
// PSIR_FileWriter::SetImgRsrc
// ===========================
void PSIR_FileWriter::SetImgRsrc ( XMP_Uns16 id, const void* clientPtr, XMP_Uns32 length )
{
InternalRsrcInfo* rsrcPtr = 0;
InternalRsrcMap::iterator rsrcPos = this->imgRsrcs.find ( id );
if ( rsrcPos == this->imgRsrcs.end() ) {
// This resource is not yet in the map, create the map entry.
InternalRsrcMap::value_type mapValue ( id, InternalRsrcInfo ( id, length, this->fileParsed ) );
rsrcPos = this->imgRsrcs.insert ( rsrcPos, mapValue );
rsrcPtr = &rsrcPos->second;
} else {
rsrcPtr = &rsrcPos->second;
// The resource already exists, make sure the value is actually changing.
if ( (length == rsrcPtr->dataLen) &&
(memcmp ( rsrcPtr->dataPtr, clientPtr, length ) == 0) ) {
return;
}
rsrcPtr->FreeData(); // Release any existing data allocation.
rsrcPtr->dataLen = length; // And this might be changing.
}
rsrcPtr->changed = true;
rsrcPtr->dataPtr = malloc ( length );
if ( rsrcPtr->dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
memcpy ( rsrcPtr->dataPtr, clientPtr, length ); // AUDIT: Safe, malloc'ed length bytes above.
this->changed = true;
} // PSIR_FileWriter::SetImgRsrc
// =================================================================================================
// PSIR_FileWriter::DeleteImgRsrc
// ==============================
void PSIR_FileWriter::DeleteImgRsrc ( XMP_Uns16 id )
{
InternalRsrcMap::iterator rsrcPos = this->imgRsrcs.find ( id );
if ( rsrcPos == this->imgRsrcs.end() ) return; // Nothing to delete.
this->imgRsrcs.erase ( id );
this->changed = true;
if ( id != kPSIR_XMP ) this->legacyDeleted = true;
} // PSIR_FileWriter::DeleteImgRsrc
// =================================================================================================
// PSIR_FileWriter::IsLegacyChanged
// ================================
bool PSIR_FileWriter::IsLegacyChanged()
{
if ( ! this->changed ) return false;
if ( this->legacyDeleted ) return true;
InternalRsrcMap::iterator irPos = this->imgRsrcs.begin();
InternalRsrcMap::iterator irEnd = this->imgRsrcs.end();
for ( ; irPos != irEnd; ++irPos ) {
const InternalRsrcInfo & rsrcInfo = irPos->second;
if ( rsrcInfo.changed && (rsrcInfo.id != kPSIR_XMP) ) return true;
}
return false; // Can get here if the XMP is the only thing changed.
} // PSIR_FileWriter::IsLegacyChanged
// =================================================================================================
// PSIR_FileWriter::ParseMemoryResources
// =====================================
void PSIR_FileWriter::ParseMemoryResources ( const void* data, XMP_Uns32 length, bool copyData /* = true */ )
{
this->DeleteExistingInfo();
this->memParsed = true;
if ( length == 0 ) return;
// Allocate space for the full in-memory data and copy it.
if ( ! copyData ) {
this->memContent = (XMP_Uns8*) data;
XMP_Assert ( ! this->ownedContent );
} else {
if ( length > 100*1024*1024 ) XMP_Throw ( "Outrageous length for memory-based PSIR", kXMPErr_BadPSIR );
this->memContent = (XMP_Uns8*) malloc ( length );
if ( this->memContent == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
memcpy ( this->memContent, data, length ); // AUDIT: Safe, malloc'ed length bytes above.
this->ownedContent = true;
}
this->memLength = length;
// Capture the info for all of the resources. We're using a map keyed by ID, so only one
// resource of each ID is recognized. Redundant resources are not legit, but have been seen in
// the field. In particular, one case has been seen of a duplicate IIM block with one empty.
// In general we keep the first seen copy to be compatible with Photoshop. A later non-empty
// copy will be taken though if the current one is empty.
// ! Don't use map[id] to lookup, that creates a default entry if none exists!
XMP_Uns8* psirPtr = this->memContent;
XMP_Uns8* psirEnd = psirPtr + length;
XMP_Uns8* psirLimit = psirEnd - kMinImgRsrcSize;
while ( psirPtr <= psirLimit ) {
XMP_Uns8* origin = psirPtr; // The beginning of this resource.
XMP_Uns32 type = GetUns32BE(psirPtr);
XMP_Uns16 id = GetUns16BE(psirPtr+4);
psirPtr += 6; // Advance to the resource name.
XMP_Uns8* namePtr = psirPtr;
XMP_Uns16 nameLen = namePtr[0]; // ! The length for the Pascal string, w/ room for "+2".
psirPtr += ((nameLen + 2) & 0xFFFE); // ! Round up to an even offset. Yes, +2!
if ( psirPtr > psirEnd-4 ) break; // Bad image resource. Throw instead?
XMP_Uns32 dataLen = GetUns32BE(psirPtr);
psirPtr += 4; // Advance to the resource data.
XMP_Uns32 dataOffset = (XMP_Uns32) ( psirPtr - this->memContent );
XMP_Uns8* nextRsrc = psirPtr + ((dataLen + 1) & 0xFFFFFFFEUL); // ! Round up to an even offset.
if ( (dataLen > length) || (psirPtr > psirEnd-dataLen) ) break; // Bad image resource. Throw instead?
if ( type != k8BIM ) {
XMP_Uns32 rsrcOffset = XMP_Uns32( origin - this->memContent );
XMP_Uns32 rsrcLength = XMP_Uns32( nextRsrc - origin ); // Includes trailing pad.
XMP_Assert ( (rsrcLength & 1) == 0 );
this->otherRsrcs.push_back ( OtherRsrcInfo ( rsrcOffset, rsrcLength ) );
} else {
InternalRsrcInfo newInfo ( id, dataLen, kIsMemoryBased );
newInfo.dataPtr = psirPtr;
newInfo.origOffset = dataOffset;
if ( nameLen != 0 ) newInfo.rsrcName = namePtr;
InternalRsrcMap::iterator rsrcPos = this->imgRsrcs.find ( id );
if ( rsrcPos == this->imgRsrcs.end() ) {
this->imgRsrcs.insert ( rsrcPos, InternalRsrcMap::value_type ( id, newInfo ) );
} else if ( (rsrcPos->second.dataLen == 0) && (newInfo.dataLen != 0) ) {
rsrcPos->second = newInfo;
}
}
psirPtr = nextRsrc;
}
} // PSIR_FileWriter::ParseMemoryResources
// =================================================================================================
// PSIR_FileWriter::ParseFileResources
// ===================================
void PSIR_FileWriter::ParseFileResources ( XMP_IO* fileRef, XMP_Uns32 length )
{
// Parse the image resource block. We're using a map keyed by ID, so only one resource of each
// ID is recognized. Redundant resources are not legit, but have been seen in the field. In
// particular, one case has been seen of a duplicate IIM block with one empty. In general we
// keep the first seen copy to be compatible with Photoshop. A later non-empty copy will be
// taken though if the current one is empty.
// ! Don't use map[id] to lookup, that creates a default entry if none exists!
// PSIR layout:
// - Uns32 type, usually '8BIM'
// - Uns16 ID
// - PString name
// - Uns8 optional pad for even alignment
// - Uns32 data size
// - data
// - Uns8 optional pad for even alignment
static const size_t kMinPSIRSize = 12; // 4+2+1+1+4
this->DeleteExistingInfo();
this->fileParsed = true;
if ( length == 0 ) return;
XMP_Int64 psirOrigin = fileRef->Offset(); // Need this to determine the resource data offsets.
XMP_Int64 fileEnd = psirOrigin + length;
char nameBuffer [260]; // The name is a PString, at 1+255+1 including length and pad.
while ( fileRef->Offset() < fileEnd ) {
if ( ! XIO::CheckFileSpace ( fileRef, kMinPSIRSize ) ) break; // Bad image resource.
XMP_Int64 thisRsrcPos = fileRef->Offset();
XMP_Uns32 type = XIO::ReadUns32_BE ( fileRef );
XMP_Uns16 id = XIO::ReadUns16_BE ( fileRef );
XMP_Uns8 nameLen = XIO::ReadUns8 ( fileRef ); // ! The length for the Pascal string.
XMP_Uns16 paddedLen = (nameLen + 2) & 0xFFFE; // ! Round up to an even total. Yes, +2!
if ( ! XIO::CheckFileSpace ( fileRef, paddedLen+4 ) ) break; // Bad image resource.
nameBuffer[0] = nameLen;
fileRef->ReadAll ( &nameBuffer[1], paddedLen-1 ); // Include the pad byte, present for zero nameLen.
XMP_Uns32 dataLen = XIO::ReadUns32_BE ( fileRef );
XMP_Uns32 dataTotal = ((dataLen + 1) & 0xFFFFFFFEUL); // Round up to an even total.
// See bug https://bugs.freedesktop.org/show_bug.cgi?id=105204
// If dataLen is 0xffffffff, then dataTotal might be 0
// and therefor make the CheckFileSpace test pass.
if (dataTotal < dataLen) {
break;
}
if ( ! XIO::CheckFileSpace ( fileRef, dataTotal ) ) break; // Bad image resource.
XMP_Int64 thisDataPos = fileRef->Offset();
XMP_Int64 nextRsrcPos = thisDataPos + dataTotal;
if ( type != k8BIM ) {
XMP_Uns32 fullRsrcLen = (XMP_Uns32) (nextRsrcPos - thisRsrcPos);
this->otherRsrcs.push_back ( OtherRsrcInfo ( (XMP_Uns32)thisRsrcPos, fullRsrcLen ) );
fileRef->Seek ( nextRsrcPos, kXMP_SeekFromStart );
continue;
}
InternalRsrcInfo newInfo ( id, dataLen, kIsFileBased );
InternalRsrcMap::iterator rsrcPos = this->imgRsrcs.find ( id );
if ( rsrcPos == this->imgRsrcs.end() ) {
rsrcPos = this->imgRsrcs.insert ( rsrcPos, InternalRsrcMap::value_type ( id, newInfo ) );
} else if ( (rsrcPos->second.dataLen == 0) && (newInfo.dataLen != 0) ) {
rsrcPos->second = newInfo;
} else {
fileRef->Seek ( nextRsrcPos, kXMP_SeekFromStart );
continue;
}
InternalRsrcInfo* rsrcPtr = &rsrcPos->second;
rsrcPtr->origOffset = (XMP_Uns32)thisDataPos;
if ( nameLen > 0 ) {
rsrcPtr->rsrcName = (XMP_Uns8*) malloc ( paddedLen );
if ( rsrcPtr->rsrcName == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
memcpy ( (void*)rsrcPtr->rsrcName, nameBuffer, paddedLen ); // AUDIT: Safe, allocated enough bytes above.
}
if ( ! IsMetadataImgRsrc ( id ) ) {
fileRef->Seek ( nextRsrcPos, kXMP_SeekFromStart );
continue;
}
rsrcPtr->dataPtr = malloc ( dataTotal ); // ! Allocate after the IsMetadataImgRsrc check.
if ( rsrcPtr->dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
fileRef->ReadAll ( (void*)rsrcPtr->dataPtr, dataTotal );
}
#if 0
{
printf ( "\nPSIR_FileWriter::ParseFileResources, count = %d\n", this->imgRsrcs.size() );
InternalRsrcMap::iterator irPos = this->imgRsrcs.begin();
InternalRsrcMap::iterator irEnd = this->imgRsrcs.end();
for ( ; irPos != irEnd; ++irPos ) {
InternalRsrcInfo& thisRsrc = irPos->second;
printf ( " #%d, dataLen %d, origOffset %d (0x%X)%s\n",
thisRsrc.id, thisRsrc.dataLen, thisRsrc.origOffset, thisRsrc.origOffset,
(thisRsrc.changed ? ", changed" : "") );
}
}
#endif
} // PSIR_FileWriter::ParseFileResources
// =================================================================================================
// PSIR_FileWriter::UpdateMemoryResources
// ======================================
XMP_Uns32 PSIR_FileWriter::UpdateMemoryResources ( void** dataPtr )
{
if ( this->fileParsed ) XMP_Throw ( "Not memory based", kXMPErr_EnforceFailure );
// Compute the size and allocate the new image resource block.
XMP_Uns32 newLength = 0;
InternalRsrcMap::iterator irPos = this->imgRsrcs.begin();
InternalRsrcMap::iterator irEnd = this->imgRsrcs.end();
for ( ; irPos != irEnd; ++irPos ) { // Add in the lengths for the 8BIM resources.
const InternalRsrcInfo & rsrcInfo = irPos->second;
newLength += 10;
newLength += ((rsrcInfo.dataLen + 1) & 0xFFFFFFFEUL);
if ( rsrcInfo.rsrcName == 0 ) {
newLength += 2;
} else {
XMP_Uns32 nameLen = rsrcInfo.rsrcName[0];
newLength += ((nameLen + 2) & 0xFFFFFFFEUL); // ! Yes, +2 for the length and rounding.
}
}
for ( size_t i = 0; i < this->otherRsrcs.size(); ++i ) { // Add in the non-8BIM resources.
newLength += this->otherRsrcs[i].rsrcLength;
}
XMP_Uns8* newContent = newLength ? (XMP_Uns8*) malloc ( newLength ) : NULL;
if ( newContent == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
// Fill in the new image resource block.
XMP_Uns8* rsrcPtr = newContent;
for ( irPos = this->imgRsrcs.begin(); irPos != irEnd; ++irPos ) { // Do the 8BIM resources.
const InternalRsrcInfo & rsrcInfo = irPos->second;
PutUns32BE ( k8BIM, rsrcPtr );
rsrcPtr += 4;
PutUns16BE ( rsrcInfo.id, rsrcPtr );
rsrcPtr += 2;
if ( rsrcInfo.rsrcName == 0 ) {
PutUns16BE ( 0, rsrcPtr );
rsrcPtr += 2;
} else {
XMP_Uns32 nameLen = rsrcInfo.rsrcName[0];
XMP_Assert ( nameLen > 0 );
if ( (nameLen+1) > (newLength - (rsrcPtr - newContent)) ) {
XMP_Throw ( "Buffer overrun", kXMPErr_InternalFailure );
}
memcpy ( rsrcPtr, rsrcInfo.rsrcName, nameLen+1 ); // AUDIT: Protected by the above check.
rsrcPtr += nameLen+1;
if ( (nameLen & 1) == 0 ) {
*rsrcPtr = 0; // Round to an even total.
++rsrcPtr;
}
}
PutUns32BE ( rsrcInfo.dataLen, rsrcPtr );
rsrcPtr += 4;
if ( rsrcInfo.dataLen > (newLength - (rsrcPtr - newContent)) ) {
XMP_Throw ( "Buffer overrun", kXMPErr_InternalFailure );
}
memcpy ( rsrcPtr, rsrcInfo.dataPtr, rsrcInfo.dataLen ); // AUDIT: Protected by the above check.
rsrcPtr += rsrcInfo.dataLen;
if ( (rsrcInfo.dataLen & 1) != 0 ) { // Pad to an even length if necessary.
*rsrcPtr = 0;
++rsrcPtr;
}
}
for ( size_t i = 0; i < this->otherRsrcs.size(); ++i ) { // Do the non-8BIM resources.
XMP_Uns8* srcPtr = this->memContent + this->otherRsrcs[i].rsrcOffset;
XMP_Uns32 srcLen = this->otherRsrcs[i].rsrcLength;
if ( srcLen > (newLength - (rsrcPtr - newContent)) ) {
XMP_Throw ( "Buffer overrun", kXMPErr_InternalFailure );
}
memcpy ( rsrcPtr, srcPtr, srcLen ); // AUDIT: Protected by the above check.
rsrcPtr += srcLen; // No need to pad, included in the original resource length.
}
XMP_Assert ( rsrcPtr == (newContent + newLength) );
// Parse the rebuilt image resource block. This is the easiest way to reconstruct the map.
this->ParseMemoryResources ( newContent, newLength, false );
this->ownedContent = (newLength > 0); // ! We really do own the new content, if not empty.
if ( dataPtr != 0 ) *dataPtr = newContent;
return newLength;
} // PSIR_FileWriter::UpdateMemoryResources
// =================================================================================================
// PSIR_FileWriter::UpdateFileResources
// ====================================
XMP_Uns32 PSIR_FileWriter::UpdateFileResources ( XMP_IO* sourceRef, XMP_IO* destRef,
XMP_AbortProc abortProc, void * abortArg,
XMP_ProgressTracker* progressTracker )
{
const XMP_Uns32 zero32 = 0;
const bool checkAbort = (abortProc != 0);
struct RsrcHeader {
XMP_Uns32 type;
XMP_Uns16 id;
};
XMP_Assert ( (offsetof(RsrcHeader,type) == 0) && (offsetof(RsrcHeader,id) == 4) );
if ( this->memParsed ) XMP_Throw ( "Not file based", kXMPErr_EnforceFailure );
InternalRsrcMap::const_iterator rsrcPos;
InternalRsrcMap::const_iterator rsrcEnd = this->imgRsrcs.end();
if ( progressTracker != 0 ) {
float totalLength = 8;
for ( rsrcPos = this->imgRsrcs.begin(); rsrcPos != rsrcEnd; ++rsrcPos ) {
const InternalRsrcInfo& currRsrc = rsrcPos->second;
totalLength += (currRsrc.dataLen + 12);
}
size_t sizeOtherRsrc = this->otherRsrcs.size();
for ( size_t i = 0; i < sizeOtherRsrc; ++i ) {
totalLength += this->otherRsrcs[i].rsrcLength;
}
XMP_Assert ( progressTracker->WorkInProgress() );
progressTracker->AddTotalWork ( totalLength );
}
XMP_Uns32 destLength = 0;
XMP_Int64 destLenOffset = destRef->Offset();
destRef->Write ( &destLength, 4 ); // Write a placeholder for the new PSIR section length.
#if 0
{
printf ( "\nPSIR_FileWriter::UpdateFileResources, count = %d\n", this->imgRsrcs.size() );
InternalRsrcMap::iterator irPos = this->imgRsrcs.begin();
InternalRsrcMap::iterator irEnd = this->imgRsrcs.end();
for ( ; irPos != irEnd; ++irPos ) {
InternalRsrcInfo& thisRsrc = irPos->second;
printf ( " #%d, dataLen %d, origOffset %d (0x%X)%s\n",
thisRsrc.id, thisRsrc.dataLen, thisRsrc.origOffset, thisRsrc.origOffset,
(thisRsrc.changed ? ", changed" : "") );
}
}
#endif
// First write all of the '8BIM' resources from the map. Use the internal data if present, else
// copy the data from the file.
RsrcHeader outHeader;
outHeader.type = MakeUns32BE ( k8BIM );
// printf ( "\nPSIR_FileWriter::UpdateFileResources - 8BIM resources\n" );
for ( rsrcPos = this->imgRsrcs.begin(); rsrcPos != rsrcEnd; ++rsrcPos ) {
const InternalRsrcInfo& currRsrc = rsrcPos->second;
outHeader.id = MakeUns16BE ( currRsrc.id );
destRef->Write ( &outHeader, 6 );
destLength += 6;
if ( currRsrc.rsrcName == 0 ) {
destRef->Write ( &zero32, 2 );
destLength += 2;
} else {
XMP_Uns16 nameLen = currRsrc.rsrcName[0]; // ! Include room for +1.
XMP_Assert ( nameLen > 0 );
XMP_Uns16 paddedLen = (nameLen + 2) & 0xFFFE; // ! Round up to an even total. Yes, +2!
destRef->Write ( currRsrc.rsrcName, paddedLen );
destLength += paddedLen;
}
XMP_Uns32 dataLen = MakeUns32BE ( currRsrc.dataLen );
destRef->Write ( &dataLen, 4 );
// printf ( " #%d, offset %d (0x%X), dataLen %d\n", currRsrc.id, destLength, destLength, currRsrc.dataLen );
if ( currRsrc.dataPtr != 0 ) {
destRef->Write ( currRsrc.dataPtr, currRsrc.dataLen );
} else {
sourceRef->Seek ( currRsrc.origOffset, kXMP_SeekFromStart );
XIO::Copy ( sourceRef, destRef, currRsrc.dataLen );
}
destLength += 4 + currRsrc.dataLen;
if ( (currRsrc.dataLen & 1) != 0 ) {
destRef->Write ( &zero32, 1 ); // ! Pad the data to an even length.
++destLength;
}
}
// Now write all of the non-8BIM resources. Copy the entire resource chunk from the source file.
// printf ( "\nPSIR_FileWriter::UpdateFileResources - other resources\n" );
for ( size_t i = 0; i < this->otherRsrcs.size(); ++i ) {
// printf ( " offset %d (0x%X), length %d",
// this->otherRsrcs[i].rsrcOffset, this->otherRsrcs[i].rsrcOffset, this->otherRsrcs[i].rsrcLength );
sourceRef->Seek ( this->otherRsrcs[i].rsrcOffset, kXMP_SeekFromStart );
XIO::Copy ( sourceRef, destRef, this->otherRsrcs[i].rsrcLength );
destLength += this->otherRsrcs[i].rsrcLength; // Alignment padding is already included.
}
// Write the final PSIR section length, seek back to the end of the file, return the length.
// printf ( "\nPSIR_FileWriter::UpdateFileResources - final length %d (0x%X)\n", destLength, destLength );
destRef->Seek ( destLenOffset, kXMP_SeekFromStart );
XMP_Uns32 outLen = MakeUns32BE ( destLength );
destRef->Write ( &outLen, 4 );
destRef->Seek ( 0, kXMP_SeekFromEnd );
// *** Not rebuilding the internal map - turns out we never want it, why pay for the I/O.
// *** Should probably add an option for all of these cases, memory and file based.
return destLength;
} // PSIR_FileWriter::UpdateFileResources