void CopyEntryDirect( BlubbZipFile workFile, BlubbUpdate update, ref long destinationPosition ) { bool skipOver = false; if( update.Entry.Offset == destinationPosition ) { skipOver = true; } if( !skipOver ) { baseStream_.Position = destinationPosition; workFile.WriteLocalEntryHeader( update ); destinationPosition = baseStream_.Position; } long sourcePosition = 0; const int NameLengthOffset = 26; // TODO: Add base for SFX friendly handling long entryDataOffset = update.Entry.Offset + NameLengthOffset; baseStream_.Seek( entryDataOffset, SeekOrigin.Begin ); // Clumsy way of handling retrieving the original name and extra data length for now. // TODO: Stop re-reading name and data length in CopyEntryDirect. uint nameLength = ReadLEUshort(); uint extraLength = ReadLEUshort(); sourcePosition = baseStream_.Position + nameLength + extraLength; if( skipOver ) { destinationPosition += ( sourcePosition - entryDataOffset ) + NameLengthOffset + // Header size update.Entry.CompressedSize + GetDescriptorSize( update ); } else { if( update.Entry.CompressedSize > 0 ) { CopyEntryDataDirect( update, baseStream_, false, ref destinationPosition, ref sourcePosition ); } CopyDescriptorBytesDirect( update, baseStream_, ref destinationPosition, sourcePosition ); } }
void CopyEntry( BlubbZipFile workFile, BlubbUpdate update ) { workFile.WriteLocalEntryHeader( update ); if( update.Entry.CompressedSize > 0 ) { const int NameLengthOffset = 26; long entryDataOffset = update.Entry.Offset + NameLengthOffset; // TODO: This wont work for SFX files! baseStream_.Seek( entryDataOffset, SeekOrigin.Begin ); uint nameLength = ReadLEUshort(); uint extraLength = ReadLEUshort(); baseStream_.Seek( nameLength + extraLength, SeekOrigin.Current ); CopyBytes( update, workFile.baseStream_, baseStream_, update.Entry.CompressedSize, false ); } CopyDescriptorBytes( update, workFile.baseStream_, baseStream_ ); }
void AddEntry( BlubbZipFile workFile, BlubbUpdate update ) { Stream source = null; if( update.Entry.IsFile ) { source = update.GetSource(); if( source == null ) { source = updateDataSource_.GetSource( update.Entry, update.Filename ); } } if( source != null ) { using( source ) { long sourceStreamLength = source.Length; if( update.OutEntry.Size < 0 ) { update.OutEntry.Size = sourceStreamLength; } else { // Check for errant entries. if( update.OutEntry.Size != sourceStreamLength ) { throw new BlubbZipException( "Entry size/stream size mismatch" ); } } workFile.WriteLocalEntryHeader( update ); long dataStart = workFile.baseStream_.Position; using( Stream output = workFile.GetOutputStream( update.OutEntry ) ) { CopyBytes( update, output, source, sourceStreamLength, true ); } long dataEnd = workFile.baseStream_.Position; update.OutEntry.CompressedSize = dataEnd - dataStart; if( ( update.OutEntry.Flags & (int)GeneralBitFlags.Descriptor ) == (int)GeneralBitFlags.Descriptor ) { BlubbZipHelperStream helper = new BlubbZipHelperStream( workFile.baseStream_ ); helper.WriteDataDescriptor( update.OutEntry ); } } } else { workFile.WriteLocalEntryHeader( update ); update.OutEntry.CompressedSize = 0; } }
void ModifyEntry( BlubbZipFile workFile, BlubbUpdate update ) { workFile.WriteLocalEntryHeader( update ); long dataStart = workFile.baseStream_.Position; // TODO: This is slow if the changes don't effect the data!! if( update.Entry.IsFile && ( update.Filename != null ) ) { using( Stream output = workFile.GetOutputStream( update.OutEntry ) ) { using( Stream source = this.GetInputStream( update.Entry ) ) { CopyBytes( update, output, source, source.Length, true ); } } } long dataEnd = workFile.baseStream_.Position; update.Entry.CompressedSize = dataEnd - dataStart; }
void CopyDescriptorBytesDirect( BlubbUpdate update, Stream stream, ref long destinationPosition, long sourcePosition ) { int bytesToCopy = GetDescriptorSize( update ); while( bytesToCopy > 0 ) { int readSize = (int)bytesToCopy; byte[] buffer = GetBuffer(); stream.Position = sourcePosition; int bytesRead = stream.Read( buffer, 0, readSize ); if( bytesRead > 0 ) { stream.Position = destinationPosition; stream.Write( buffer, 0, bytesRead ); bytesToCopy -= bytesRead; destinationPosition += bytesRead; sourcePosition += bytesRead; } else { throw new BlubbZipException( "Unxpected end of stream" ); } } }
void CopyEntryDataDirect( BlubbUpdate update, Stream stream, bool updateCrc, ref long destinationPosition, ref long sourcePosition ) { long bytesToCopy = update.Entry.CompressedSize; // NOTE: Compressed size is updated elsewhere. Crc32 crc = new Crc32(); byte[] buffer = GetBuffer(); long targetBytes = bytesToCopy; long totalBytesRead = 0; int bytesRead; do { int readSize = buffer.Length; if( bytesToCopy < readSize ) { readSize = (int)bytesToCopy; } stream.Position = sourcePosition; bytesRead = stream.Read( buffer, 0, readSize ); if( bytesRead > 0 ) { if( updateCrc ) { crc.Update( buffer, 0, bytesRead ); } stream.Position = destinationPosition; stream.Write( buffer, 0, bytesRead ); destinationPosition += bytesRead; sourcePosition += bytesRead; bytesToCopy -= bytesRead; totalBytesRead += bytesRead; } } while( ( bytesRead > 0 ) && ( bytesToCopy > 0 ) ); if( totalBytesRead != targetBytes ) { throw new BlubbZipException( string.Format( "Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead ) ); } if( updateCrc ) { update.OutEntry.Crc = crc.Value; } }
/// <summary> /// Get the size of the source descriptor for a <see cref="BlubbUpdate"/>. /// </summary> /// <param name="update">The update to get the size for.</param> /// <returns>The descriptor size, zero if there isnt one.</returns> int GetDescriptorSize( BlubbUpdate update ) { int result = 0; if( ( update.Entry.Flags & (int)GeneralBitFlags.Descriptor ) != 0 ) { result = BlubbZipConstants.DataDescriptorSize - 4; if( update.Entry.LocalHeaderRequiresBlubb64 ) { result = BlubbZipConstants.Blubb64DataDescriptorSize - 4; } } return result; }
void CopyBytes( BlubbUpdate update, Stream destination, Stream source, long bytesToCopy, bool updateCrc ) { if( destination == source ) { throw new InvalidOperationException( "Destination and source are the same" ); } // NOTE: Compressed size is updated elsewhere. Crc32 crc = new Crc32(); byte[] buffer = GetBuffer(); long targetBytes = bytesToCopy; long totalBytesRead = 0; int bytesRead; do { int readSize = buffer.Length; if( bytesToCopy < readSize ) { readSize = (int)bytesToCopy; } bytesRead = source.Read( buffer, 0, readSize ); if( bytesRead > 0 ) { if( updateCrc ) { crc.Update( buffer, 0, bytesRead ); } destination.Write( buffer, 0, bytesRead ); bytesToCopy -= bytesRead; totalBytesRead += bytesRead; } } while( ( bytesRead > 0 ) && ( bytesToCopy > 0 ) ); if( totalBytesRead != targetBytes ) { throw new BlubbZipException( string.Format( "Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead ) ); } if( updateCrc ) { update.OutEntry.Crc = crc.Value; } }
void CopyDescriptorBytes( BlubbUpdate update, Stream dest, Stream source ) { int bytesToCopy = GetDescriptorSize( update ); if( bytesToCopy > 0 ) { byte[] buffer = GetBuffer(); while( bytesToCopy > 0 ) { int readSize = Math.Min( buffer.Length, bytesToCopy ); int bytesRead = source.Read( buffer, 0, readSize ); if( bytesRead > 0 ) { dest.Write( buffer, 0, bytesRead ); bytesToCopy -= bytesRead; } else { throw new BlubbZipException( "Unxpected end of stream" ); } } } }
void WriteLocalEntryHeader( BlubbUpdate update ) { BlubbZipEntry entry = update.OutEntry; // TODO: Local offset will require adjusting for multi-disk blubb files. entry.Offset = baseStream_.Position; // TODO: Need to clear any entry flags that dont make sense or throw an exception here. if( update.Command != UpdateCommand.Copy ) { if( entry.CompressionMethod == CompressionMethod.Deflated ) { if( entry.Size == 0 ) { // No need to compress - no data. entry.CompressedSize = entry.Size; entry.Crc = 0; entry.CompressionMethod = CompressionMethod.Stored; } } else if( entry.CompressionMethod == CompressionMethod.Stored ) { entry.Flags &= ~(int)GeneralBitFlags.Descriptor; } if( HaveKeys ) { entry.IsCrypted = true; if( entry.Crc < 0 ) { entry.Flags |= (int)GeneralBitFlags.Descriptor; } } else { entry.IsCrypted = false; } switch( useBlubb64_ ) { case UseBlubb64.Dynamic: if( entry.Size < 0 ) { entry.ForceBlubb64(); } break; case UseBlubb64.On: entry.ForceBlubb64(); break; case UseBlubb64.Off: // Do nothing. The entry itself may be using Blubb64 independantly. break; } } // Write the local file header WriteLEInt( BlubbZipConstants.LocalHeaderSignature ); WriteLEShort( entry.Version ); WriteLEShort( entry.Flags ); WriteLEShort( (byte)entry.CompressionMethod ); WriteLEInt( (int)entry.DosTime ); if( !entry.HasCrc ) { // Note patch address for updating CRC later. update.CrcPatchOffset = baseStream_.Position; WriteLEInt( (int)0 ); } else { WriteLEInt( unchecked( (int)entry.Crc ) ); } if( entry.LocalHeaderRequiresBlubb64 ) { WriteLEInt( -1 ); WriteLEInt( -1 ); } else { if( ( entry.CompressedSize < 0 ) || ( entry.Size < 0 ) ) { update.SizePatchOffset = baseStream_.Position; } WriteLEInt( (int)entry.CompressedSize ); WriteLEInt( (int)entry.Size ); } byte[] name = BlubbZipConstants.ConvertToArray( entry.Flags, entry.Name ); if( name.Length > 0xFFFF ) { throw new BlubbZipException( "Entry name too long." ); } BlubbZipExtraData ed = new BlubbZipExtraData( entry.ExtraData ); if( entry.LocalHeaderRequiresBlubb64 ) { ed.StartNewEntry(); // Local entry header always includes size and compressed size. // NOTE the order of these fields is reversed when compared to the normal headers! ed.AddLeLong( entry.Size ); ed.AddLeLong( entry.CompressedSize ); ed.AddNewEntry( 1 ); } else { ed.Delete( 1 ); } entry.ExtraData = ed.GetEntryData(); WriteLEShort( name.Length ); WriteLEShort( entry.ExtraData.Length ); if( name.Length > 0 ) { baseStream_.Write( name, 0, name.Length ); } if( entry.LocalHeaderRequiresBlubb64 ) { if( !ed.Find( 1 ) ) { throw new BlubbZipException( "Internal error cannot find extra data" ); } update.SizePatchOffset = baseStream_.Position + ed.CurrentReadIndex; } if( entry.ExtraData.Length > 0 ) { baseStream_.Write( entry.ExtraData, 0, entry.ExtraData.Length ); } }
// Adding Entries void AddUpdate( BlubbUpdate update ) { contentsEdited_ = true; int index = FindExistingUpdate( update.Entry.Name ); if( index >= 0 ) { if( updates_[ index ] == null ) { updateCount_ += 1; } // Direct replacement is faster than delete and add. updates_[ index ] = update; } else { index = updates_.Add( update ); updateCount_ += 1; updateIndex_.Add( update.Entry.Name, index ); } }