You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

251 lines
8.7 KiB

/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
//
// plDiffBuffer - A utility class for writing and applying a difference
// buffer--i.e. a buffer containing a series of modifications
// (specifically, adds and copys) that will modify an old
// data buffer to match a new one. It's a useful utility
// class when doing binary file patching, for example, as you
// can write out the changes to this class, get back a data
// buffer suitable for writing, then use this class again
// later to reconstruct the new buffer.
//
//// History /////////////////////////////////////////////////////////////////
//
// 7.24.2002 mcn - Created (Happy late b-day to me!)
//
//////////////////////////////////////////////////////////////////////////////
#include "hsTypes.h"
#include "plDiffBuffer.h"
#include "plBSDiffBuffer.h"
#include "hsUtils.h"
#include "hsStream.h"
//////////////////////////////////////////////////////////////////////////////
//// Constructor/Destructors /////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//// Creation Constructor ////////////////////////////////////////////////////
// Use this constructor when creating a new diff buffer. Pass in the length
// of the final new buffer. You don't need to pass in the length of the old
// buffer, but if you do, it'll help this class do some internal space
// optimization.
plDiffBuffer::plDiffBuffer( UInt32 newLength, UInt32 oldLength )
: fBSDiffBuffer( nil )
, fIsBSDiff( false )
{
// Basically, if the new and old lengths can both fit into 16 bits
// (not including a potential negation), then we can store all the
// segment info as 16-bit values instead of 32.
if( oldLength > 0 && oldLength < 32767 && newLength < 32767 )
f16BitMode = true;
else
f16BitMode = false;
fNewLength = newLength;
fStream = TRACKED_NEW hsRAMStream();
fStream->WriteSwap32( fNewLength );
fStream->WriteBool( f16BitMode );
fWriting = true;
}
//// Application Constructor /////////////////////////////////////////////////
// Use this constructor when taking a diff buffer and applying it to an old
// buffer. Pass in a pointer to the diff buffer and its length. The buffer
// will be copied, so you don't need to keep it around after you construct
// this object.
plDiffBuffer::plDiffBuffer( void *buffer, UInt32 length )
: fBSDiffBuffer( nil )
, fStream( nil )
, fIsBSDiff( false )
, fWriting( false )
{
// Check to see if this uses the newer BSDiff format
if ( buffer && length > 32 &&
memcmp(buffer,"BSDIFF40",8)==0 )
{
// This is a bsdiff buffer. Use plBSDiffBuffer to handle it.
fBSDiffBuffer = TRACKED_NEW plBSDiffBuffer(buffer, length);
fIsBSDiff = true;
}
else
{
fStream = TRACKED_NEW hsRAMStream();
fStream->Write( length, buffer );
fStream->Rewind();
fNewLength = fStream->ReadSwap32();
f16BitMode = fStream->ReadBool();
}
}
plDiffBuffer::~plDiffBuffer()
{
if (fStream)
delete fStream;
if (fBSDiffBuffer)
delete fBSDiffBuffer;
}
//////////////////////////////////////////////////////////////////////////////
//// Creation/Write Functions ////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//// Add /////////////////////////////////////////////////////////////////////
// Add() appends an Add-New-Data operation to the diff buffer. The data
// supplied will be copied internally, so you can discard it after you call
// this function.
void plDiffBuffer::Add( Int32 length, void *newData )
{
hsAssert( fWriting, "Trying to Add() to a difference buffer that's reading" );
// We flag our two different op types by the sign of the length. Negative
// lengths are an add operation, positive ones are copy ops.
if( f16BitMode )
fStream->WriteSwap16( -( (Int16)length ) );
else
fStream->WriteSwap32( -length );
fStream->Write( length, newData );
}
//// Copy ////////////////////////////////////////////////////////////////////
// Copy() appends a Copy-Data-From-Old operation to the diff buffer.
void plDiffBuffer::Copy( Int32 length, UInt32 oldOffset )
{
hsAssert( fWriting, "Trying to Copy() to a difference buffer that's reading" );
// We flag our two different op types by the sign of the length. Negative
// lengths are an add operation, positive ones are copy ops.
if( f16BitMode )
{
fStream->WriteSwap16( (Int16)length );
fStream->WriteSwap16( (UInt16)oldOffset );
}
else
{
fStream->WriteSwap32( length );
fStream->WriteSwap32( oldOffset );
}
}
//// GetBuffer ///////////////////////////////////////////////////////////////
// GetBuffer() will copy the diff stream into a new buffer and return it.
// You are responsible for freeing the buffer. Call this once you're done
// adding ops and want the raw data to write out somewhere. Note: this
// function will rewind the diff stream, so once you call it, you can't do
// anything else on the object.
void plDiffBuffer::GetBuffer( UInt32 &length, void *&bufferPtr )
{
hsAssert( fWriting, "Trying to GetBuffer() on a difference buffer that's reading" );
length = fStream->GetPosition();
bufferPtr = (void *)TRACKED_NEW UInt8[ length ];
fStream->Rewind();
fStream->Read( length, bufferPtr );
}
//////////////////////////////////////////////////////////////////////////////
//// Application Functions ///////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//// Apply ///////////////////////////////////////////////////////////////////
// Apply() will take this diff buffer and apply it to the given old buffer,
// allocating and producing a new buffer. You are responsible for freeing
// the new buffer.
#define hsAssertAndBreak( cond, msg ) { if( cond ) { hsAssert( false, msg ); break; } }
void plDiffBuffer::Apply( UInt32 oldLength, void *oldBuffer, UInt32 &newLength, void *&newBuffer )
{
hsAssert( !fWriting, "Trying to Apply() a difference buffer that's writing" );
// Is this is a BSDiff patch, use plBSDiffBuffer and return.
if (fIsBSDiff)
{
fBSDiffBuffer->Apply(oldLength, oldBuffer, newLength, newBuffer);
return;
}
/// Step 1: Allocate the new buffer
newLength = fNewLength;
UInt8 *new8Buffer = TRACKED_NEW UInt8[ newLength ];
UInt8 *old8Buffer = (UInt8 *)oldBuffer;
newBuffer = (void *)new8Buffer;
/// Step 2: Loop through the difference stream
Int32 opLength;
UInt32 newBufferPos = 0;
while( newBufferPos < newLength )
{
// Read in the op length
if( f16BitMode )
{
Int16 opLen16 = fStream->ReadSwap16();
if( opLen16 < 0 )
opLength = -( (Int32)( -opLen16 ) );
else
opLength = (UInt32)opLen16;
}
else
opLength = fStream->ReadSwap32();
// As defined, negative ops are add ops, positive ones are copys
if( opLength < 0 )
{
hsAssertAndBreak( newBufferPos - opLength > newLength, "Destination buffer offset in plDiffBuffer() is out of range!" );
// Add op, read in the added data
fStream->Read( -opLength, &new8Buffer[ newBufferPos ] );
newBufferPos += -opLength;
}
else
{
// Copy op, so get the old offset and copy from there
UInt32 oldOffset = f16BitMode ? fStream->ReadSwap16() : fStream->ReadSwap32();
hsAssertAndBreak( newBufferPos + opLength > newLength, "Destination buffer offset in plDiffBuffer() is out of range!" );
hsAssertAndBreak( oldOffset + opLength > oldLength, "Difference buffer offset in plDiffBuffer() is out of range of the old buffer!" );
memcpy( &new8Buffer[ newBufferPos ], old8Buffer + oldOffset, opLength );
newBufferPos += opLength;
}
}
hsAssert( newBufferPos == newLength, "Invalid sequence of difference ops in plDiffBuffer::Apply()" );
}