//
// MEMSTORE.CPP
//
//  Source file for ArchiveLib 1.0
//
//  Copyright (c) Greenleaf Software, Inc. 1994
//  All Rights Reserved
//
// CONTENTS
//
//  ALMemory::operator new()
//  ALMemory::ALMemory()
//  ALMemory::~ALMemory()
//  ALMemory::LoadBuffer()
//  ALMemory::Delete()
//  ALMemory::Rename()
//  ALMemory::RenameToBackup()
//  ALMemory::UnRename()
//  ALMemory::Seek()
//  ALMemory::GrowUserBuffer()
//  ALMemory::FlushBuffer()
//  ALMemory::Close()
//  ALMemory::Create()
//  ALMemory::Open()
//
// DESCRIPTION
//
//  This file contains all the C++ member functions to support the
//  ALMemory class.  ALMemory is conceptually pretty simple, but it suffers
//  from a little bit of #ifdef'itis.  The reason for this is that
//  things change just a little bit when we are using Windows memory
//  management.  Not enough to create a new class, but enough to have
//  to make a lot of code conditional.
//
//  The big difference between the Windows and DOS code shows up in two
//  areas.  First, under Windows we have huge buffers that can support
//  up to either 16Mbytes or 4GBytes, depending.  Under MS-DOS real mode,
//  out biggest buffer is 64Kb.  Second, under MS-DOS we get new memory
//  with malloc/realloc/free.  Under Windows we use LocalAlloc etc.
//
//  Other than that, the whole class is pretty straightforward.  Try not to
//  let the issue of naming the buffers throw you, it is basically
//  irrelevant.
//
// REVISION HISTORY
//
//  May 24, 1994  1.0A  : First release
//
//

#include "arclib.h"
#pragma hdrstop

#include "memstore.h"

#include <stdlib.h>  // might be using malloc()!

//
// void * ALMemory::operator new( size_t size )
//
// ARGUMENTS:
//
//  size  :  The amount of storage that needs to be allocated for
//           this object.
//
// RETURNS
//
//  A pointer to the storage.
//
// DESCRIPTION
//
//  When using the DLL version of ArchiveLib, it is a good idea to
//  allocate the storage for objects from inside the DLL, since they
//  will be freed inside the DLL.  If we don't have the new operator
//  for a class, its storage will be allocated from the EXE before
//  the constructor code is called.  Then, when it is time to free
//  the storage, the delete operator will be called inside the DLL.
//  Not good, right?
//
//
//  Very important:  this new operator is called to allocate the
//  storage for the ALMemory object itself.  This has nothing to do
//  with the storage buffer that the memory object will be using
//  later on.  In other words, this new operator is responsible for
//  no more than a couple of dozen bytes, not potentially hundreds
//  of Kbytes.
//
// REVISION HISTORY
//
//   May 24, 1994  1.0A  : First release
//

#if defined( AL_BUILDING_DLL )
void AL_DLL_FAR * AL_PROTO ALMemory::operator new( size_t size )
{
    return ::new char[ size ];
}
#endif

// WINDOWS version:
//
// ALMemory::ALMemory( const char *buffer_name = "",
//                     char AL_HUGE *user_buffer = 0,
//                     DWORD user_buffer_size = 0,
//                     ALCase name_case = AL_MIXED )
//
// MS-DOS real mode version :
//
// ALMemory::ALMemory( const char *buffer_name = "",
//                     char *user_buffer = 0,
//                     int user_buffer_size = 0,
//                     ALCase name_case = AL_MIXED )
// 
// ARGUMENTS:
//
//  buffer_name  : An arbitrary name assigned to the buffer.  Buffer
//                 names don't have to be unique, because buffers aren't
//                 named at the operating system level.  But if you are
//                 going to insert the storage object into an archive, the
//                 name needs to be unique so that you will be able to
//                 extract it properly.
//
//  user_buffer  : If you want the ALMemory class to automatically allocate
//                 a buffer for you, and grow it as necessary, just leave
//                 this pointer set to 0.  If you want to use your own buffer,
//                 which won't have the ability to grow, pass a pointer to
//                 it in this parameter.  Note that under Windows 16 this
//                 is a huge pointer, meaning it can span segments, and
//                 access potentially 16 Mbytes of memory.
//
//  user_buffer_size : If you are passing a pointer to your own buffer,
//                     you need to indicate how large it is here.  Under
//                     Windows this is a DWORD instead of a size_t.
//
//  name_case    : This decides whether you want the file name to be 
//                 case sensitive when making comparisons.  MS-DOS
//                 file names are case-insensitive.  You can make memory
//                 buffers either mixed case, forced upper, or forced
//                 lower.  The default of mixed case means that comparisons
//                 will be case sensitive, which is fine.
// RETURNS
//
//  Nothing, it is a constructor.
//
// DESCRIPTION
//
//  This constructor calls the base class constructor in an initializer
//  list, which takes care of most of the dirty work right away.  After that
//  is done, all the constructor has to do is initialize a few data members.
//  That should be self-explanatory.  Remember that if the user doesn't
//  supply a buffer, we are going to allocate it for her, but not until
//  there is actually a demand for memory.
//
// REVISION HISTORY
//
//   May 22, 1994  1.0A  : First release
//
#if defined( AL_WINDOWS_MEMORY ) 
//
// The Windows and MS-DOS constructors are nearly identical.
//
AL_PROTO ALMemory::ALMemory( const char AL_DLL_FAR *buffer_name /* = "" */,
                             char AL_HUGE *user_buffer /* = 0 */,
                             DWORD user_buffer_size /* = 0 */,
                             ALCase name_case /* = AL_MIXED */)
    : ALStorage( buffer_name, 4096, AL_MEMORY_OBJECT, name_case )
{
    if ( user_buffer != 0 ) {
        mpcUserBuffer = user_buffer;
        mfUserOwnsBuffer = 1;
        muUserBufferSize = user_buffer_size;
    } else {
        mfUserOwnsBuffer = 0;
        mpcUserBuffer = 0;
        muUserBufferSize = 0;
    }
    mhUserMemoryHandle = 0;
}

#else // #if defined( AL_WINDOWS_MEMORY )

AL_PROTO ALMemory::ALMemory( const char AL_DLL_FAR *buffer_name /* = "" */,
                             char AL_DLL_FAR *user_buffer /* = 0 */ ,
                             int user_buffer_size /* = 0 */,
                             ALCase name_case /* = AL_MIXED */ )
    : ALStorage( buffer_name, 4096, AL_MEMORY_OBJECT, name_case )
{
    if ( user_buffer != 0 ) {
        mpcUserBuffer = user_buffer;
        mfUserOwnsBuffer = 1;
        muUserBufferSize = user_buffer_size;
    } else {
        mfUserOwnsBuffer = 0;
        mpcUserBuffer = 0;
        muUserBufferSize = 0;
    }
}
#endif

//
// ALMemory::~ALMemory()
//
// ARGUMENTS:
//
//  None, you don't get any for a destructor.
//
// RETURNS
//
//  Nothing.
//
// DESCRIPTION
//
//  The destructor has just one thing it has to do before this object
//  goes away.  If the buffer that it has been using all along doesn't
//  belong to the user, then it is the class's responsibility to get
//  rid of it.  We do so here, using one of two methods, depending on
//  whether we are under MS-DOS or Windows.
//
//  Note also that we check the GoodTag() function when in Debug mode.
//  That will help catch really bad mistakes, such as trying to delete
//  an object that is not even an ALMemory object, maybe a beer can.
//
// REVISION HISTORY
//
//   May 22, 1994  1.0A  : First release
//

AL_PROTO ALMemory::~ALMemory()
{
    AL_ASSERT( GoodTag(), "~ALMemory: attempting to delete invalid object" );
    if ( !mfUserOwnsBuffer ) {
        if ( mpcUserBuffer ) {
#if defined( AL_WINDOWS_MEMORY )
            GlobalUnlock( (HGLOBAL) mhUserMemoryHandle );
            GlobalFree( (HGLOBAL) mhUserMemoryHandle );
            mhUserMemoryHandle= 0;
#else
            free( mpcUserBuffer );
#endif
            mpcUserBuffer = 0;
        }
    }
    AL_ASSERT( GoodTag(), "~ALMemory: attempting to delete invalid object" );
}

//
// int ALMemory::LoadBuffer( long address )
//
// ARGUMENTS:
//
//  address  : An offset that we need to load data from.
//
// RETURNS
//
//  AL_SEEK_ERROR if we try to read past the end of file.  AL_END_OF_FILE
//  if we just run out of data.  Otherwise an int indicating how many bytes
//  are now in the buffer.
//
// DESCRIPTION
//
//  This is a virtual function the ALStorage functions rely on when reading
//  data.  Anytime someone tries to do a ReadChar() or ReadBuffer(), and
//  it turns out that the I/O buffer has been exhausted, this function
//  is called.
//
//  The simple job of this function is to read as many bytes as possible out
//  of the giant memory block allocated for the ALMemory object, and stick
//  it into the I/O buffer, which caches it for calls to ReadChar()
//  and friends.
//
//  This works fine unless you try to go past the end of the buffer,
//  since there is nothing there we flag that as an error.
//
// REVISION HISTORY
//
//   May 22, 1994  1.0A  : First release
//

int AL_PROTO ALMemory::LoadBuffer( long address )
{
    if ( mStatus < AL_SUCCESS )
        return mStatus;
    if ( mlFilePointer != address ) {
        if ( mlFilePointer > (long) muUserBufferSize )
            return mStatus.SetError( AL_SEEK_ERROR,
                                     "Attempt to read past end of the "
                                     "buffer in ALMemory %s",
                                     mName.GetName() );
    }
    long load = muUserBufferSize - address;
    if ( load > (long) muBufferSize )
        muBufferValidData = muBufferSize;
    else
        muBufferValidData = (size_t) load;
    if ( muBufferValidData <= 0 )
        return AL_END_OF_FILE;
#if defined( AL_WINDOWS_MEMORY )
//
// Some problems passing huge arrays to memcpy, got to do it inline instead
// I think Microsoft says memcpy() will work with huge pointers as long
// as you don't try to use the inline optimizations, but I say why take
// chances...
//
// Another note: AL_HUGE is _huge for win16, but blank for win32.
//
    char AL_HUGE *temp = mpcUserBuffer + address;
    for ( unsigned i = 0 ; i < muBufferValidData ; i++ )
        mpcBuffer[ i ] = *temp++;
//    memcpy( mpcBuffer, mpcUserBuffer +          address, muBufferValidData );
#else
    memcpy( mpcBuffer, mpcUserBuffer + (size_t) address, muBufferValidData );
#endif
    if ( miUpdateCrcFlag )
        UpdateCrc( muBufferValidData );
    muReadIndex = 0; //Reading can resume at this location
    mlFilePointer += muBufferValidData;
    YieldTime();
    return muBufferValidData;
}

//
// int ALMemory::Delete()
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  Always returns AL_SUCCESS.
//
// DESCRIPTION
//
//  This function is supposed to delete the underlying physical object.
//  This makes a lot of sense with files, because you are essentially
//  emulating the MS-DOS command line DEL function.  With memory
//  objects things aren't quite as clear.  So we destroy the buffer,
//  and that's that.
//
// REVISION HISTORY
//
//   May 22, 1994  1.0A  : First release
//

int AL_PROTO ALMemory::Delete()
{
    if ( !mfUserOwnsBuffer ) {
#if defined( AL_WINDOWS_MEMORY )
        GlobalUnlock( (HGLOBAL) mhUserMemoryHandle );
        GlobalFree( (HGLOBAL) mhUserMemoryHandle );
        mhUserMemoryHandle= 0;
#else
        free( mpcUserBuffer );
#endif
        mpcUserBuffer = 0;
    }
    return AL_SUCCESS;
}

//
// int ALMemory::Rename( const char *new_name /* = 0 */, 
//                       int /* delete_on_clash = 1 */ )
//
// ARGUMENTS:
//
//  new_name        : The new name of the buffer.
//
//  delete_on_clash : This argument makes sense with files.  What it says
//                    is that if you try to rename BOB.DAT to BOB.BAK,
//                    and it turns out that there is another BOB.BAK, should
//                    you delete the other one?  With memory buffers,
//                    there is no clash, cause the OS doesn't care about
//                    unique names.  So we ignore this parm.
//
// RETURNS
//
//  Always returns AL_SUCCESS.
//
// DESCRIPTION
//
//  This function is supposed to rename the underlying physical object.
//  But in the case of memory buffers, the underlying physical object
//  doesn't actually have a name, so this is really just a local rename.
//
// REVISION HISTORY
//
//   May 22, 1994  1.0A  : First release
//

int AL_PROTO ALMemory::Rename( const char AL_DLL_FAR *new_name /* = 0 */, 
                               int /* delete_on_clash = 1 */ )
{
    if ( new_name )
        mName = new_name;
    return AL_SUCCESS;
}

//
// int ALMemory::RenameToBackup( int delete_on_clash /* = 1 */ )
//
// ARGUMENTS:
//
//  delete_on_clash : This argument makes sense with files.  What it says
//                    is that if you try to rename BOB.DAT to BOB.BAK,
//                    and it turns out that there is another BOB.BAK, should
//                    you delete the other one?  With memory buffers,
//                    there is no clash, cause the OS doesn't care about
//                    unique names.  So it doesn't matter what value you
//                    pass to the Rename() function, it is going to be
//                    ignored.
//
// RETURNS
//
//  Always returns AL_SUCCESS.
//
// DESCRIPTION
//
//  This function renames an object.  But instead of making you sweat in
//  order to come up with a new name, it just uses the default name
//  that we use to assign a backup name.
//
// REVISION HISTORY
//
//   May 22, 1994  1.0A  : First release
//

int AL_PROTO ALMemory::RenameToBackup( int delete_on_clash /* = 1 */ )
{
    mName.ChangeExtension();
    return Rename( 0, delete_on_clash );
}

//
// int ALMemory::UnRename( int /* delete_on_clash = 1 */)
//
// ARGUMENTS:
//
//  delete_on_clash : This argument makes sense with files.  What it says
//                    is that if you try to rename BOB.DAT to BOB.BAK,
//                    and it turns out that there is another BOB.BAK, should
//                    you delete the other one?  With memory buffers,
//                    there is no clash, cause the OS doesn't care about
//                    unique names.  So we just ignore it here.
//
// RETURNS
//
//  Always returns AL_SUCCESS.
//
// DESCRIPTION
//
//  If you decide you didn't really want to rename an object after all, you
//  can call this function to get the old name back!
//
// REVISION HISTORY
//
//   May 22, 1994  1.0A  : First release
//

int AL_PROTO ALMemory::UnRename( int /* delete_on_clash = 1 */)
{
    AL_ASSERT( mName.GetName() != 0, "UnRename: trying to rename with a null name" );
    AL_ASSERT( mName.GetOldName() != 0, "UnRename: trying to rename with a null old name" );
    AL_ASSERT( strlen( mName ) > 0, "UnRename: trying to rename with a zero length name"  );
    AL_ASSERT( strlen( mName.GetOldName() ) > 0, "UnRename: trying to rename with a zero length old name" );

    ALStorage::mName = mName.GetOldName();
    return AL_SUCCESS;
}

//
// int ALMemory::Seek( long address )
//
// ARGUMENTS:
//
//  address  :  The address in the memory object to go to.  The read and
//              write pointers will now be repositioned to this point.
//
// RETURNS
//
//  AL_SEEK_ERROR if we can't get to that point in the buffer.  Otherwise
//  AL_SUCCESS.
//
// DESCRIPTION
//
//  This function acts just like the seek() function in the C runtime
//  library.  It flushes the current I/O buffers, and then moves the file
//  read and write pointers to a new spot, specified by the address.  if
//  there is no memory there, you will get an error.  Note that this
//  makes it not quite like the C run time library, since it can create
//  new space with a seek().  But I don't think we need that ability yet.
//
// REVISION HISTORY
//
//   May 22, 1994  1.0A  : First release
//

int AL_PROTO ALMemory::Seek( long address )
{
    FlushBuffer();
    if ( mStatus < 0 )
        return mStatus;

    if ( mlFilePointer != address ) {
        if ( mlFilePointer > (long) muUserBufferSize )
            return mStatus.SetError( AL_SEEK_ERROR,
                                     "Attempt to read past end of the "
                                     "buffer in ALMemory %s",
                                     mName.GetName() );
    }
    mlFilePointer = address;
    return AL_SUCCESS;
}

//
// int ALMemory::GrowUserBuffer( long minimum_new_size )
//
// ARGUMENTS:
//
//  minimum_new_size :    The absolute minimum new size you need the buffer
//                        to grow to.  This amount is usually determined by
//                        a pending I/O request.  For example, if the current
//                        size of the buffer is 1000, and you have a 1 byte
//                        data block to write at 1000, the minimum new size
//                        will be 1001.
//
// RETURNS
//
//  AL_CANT_ALLOCATE_MEMORY, if we just can't get it.  AL_SUCCESS if we can.
//
// DESCRIPTION
//
//  When you are trying to write to the memory object, and you have hit
//  the end of the currently allocated area, it would seem like a good
//  time to allocate more.  When that situation occurs, this function is
//  called.  If the user owns the buffer, we don't have the option of asking
//  the O/S or RTL for more memory, because we don't even know if the user
//  memory is on the heap or what.  But if we own the memory we know how
//  to ask for more.
//
//  The strategy for asking for more memory is pretty simple.  Normally,
//  we ask for another 16K.  If that fails, we fall back to asking for
//  just enough memory to cover our current I/O request.  Asking for
//  this memory is sufficiently different under real mode dos and protected
//  mode windows that we have two completely different routines, separated
//  only by #ifdefs.
//
// REVISION HISTORY
//
//   May 22, 1994  1.0A  : First release
//

#if defined( AL_WINDOWS_MEMORY )
int AL_PROTO ALMemory::GrowUserBuffer( long minimum_new_size )
{
    if ( mStatus < AL_SUCCESS )
        return mStatus;
    if ( mfUserOwnsBuffer )
        return mStatus.SetError( AL_CANT_ALLOCATE_MEMORY,
                                   "Attempt to write past the end of a "
                                   "user owned buffer for ALMemory "
                                   "%s",
                                   mName.GetSafeName() );
    long trial_size = muUserBufferSize + 16384;
    GlobalUnlock( (HGLOBAL) mhUserMemoryHandle );
    HGLOBAL new_handle = GlobalReAlloc( (HGLOBAL) mhUserMemoryHandle, trial_size, GMEM_MOVEABLE );
    if ( new_handle == 0 ) {
        trial_size = minimum_new_size;
        new_handle = GlobalReAlloc( (HGLOBAL) mhUserMemoryHandle, trial_size, GMEM_MOVEABLE );
    }
    if ( new_handle == 0 ) {
        mpcUserBuffer = (char AL_HUGE *) GlobalLock( (HGLOBAL) mhUserMemoryHandle );
        return mStatus.SetError( AL_CANT_ALLOCATE_MEMORY,
                                   "Allocation failure when attempting to "
                                   "allocate a buffer "
                                   "of %ld bytes for ALMemory "
                                   "%s",
                                   minimum_new_size,
                                   mName.GetSafeName() );
    }
    mpcUserBuffer = (char AL_HUGE *) GlobalLock( new_handle );
    mhUserMemoryHandle = new_handle;
    muUserBufferSize = trial_size;
    return AL_SUCCESS;
}
#else // #ifdef AL_WINDOWS_MEMORY

int AL_PROTO ALMemory::GrowUserBuffer( long minimum_new_size )
{
    if ( mStatus < AL_SUCCESS )
        return mStatus;
    if ( mfUserOwnsBuffer )
        return mStatus.SetError( AL_CANT_ALLOCATE_MEMORY,
                                   "Attempt to write past the end of a "
                                   "user owned buffer for ALMemory "
                                   "%s",
                                   mName.GetSafeName() );
    if ( minimum_new_size >= 65535L )
        return mStatus.SetError( AL_CANT_ALLOCATE_MEMORY,
                                   "Attempt to allocate a huge buffer "
                                   "of %ld bytes for ALMemory "
                                   "%s",
                                   minimum_new_size,
                                   mName.GetSafeName() );
    long trial_size = muUserBufferSize + 16384;
    if ( trial_size >= 65000U )
        trial_size = 65000U;
    if ( trial_size >= minimum_new_size ) {
        char *new_buf = (char *) realloc( mpcUserBuffer, (size_t) trial_size );
        if ( new_buf ) {
            mpcUserBuffer = new_buf;
            muUserBufferSize = (size_t) trial_size;
            return AL_SUCCESS;
        }
    }
    char *new_buf = (char *) realloc( mpcUserBuffer, (size_t) minimum_new_size );
    if ( new_buf ) {
        mpcUserBuffer = new_buf;
        muUserBufferSize = (size_t) trial_size;
        return AL_SUCCESS;
    }
    return mStatus.SetError( AL_CANT_ALLOCATE_MEMORY,
                               "Allocation failure when attempting to "
                               "allocate a buffer "
                               "of %ld bytes for ALMemory "
                               "%s",
                               minimum_new_size,
                               mName.GetSafeName() );
}
#endif

//
// int ALMemory::FlushBuffer()
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  AL_CANT_ALLOCATE_MEMORY, if we run out.  Otherwise, AL_SUCCESS.
//
// DESCRIPTION
//
//  This routine is called when the I/O buffer is filled up. It means
//  you have filled up the cache with what is usually 4K bytes of data.
//  This routine is also called if you have hot data in the I/O buffer
//  and you decide to do a seek(), or a read().
//  
//  All we have to do here is take the hot data in the I/O buffer and
//  write it out to our massive memory object.  The big complication is
//  that sometimes the memory object isn't big enough, so while we are
//  all busy trying to do this, we have to ask for more data at the
//  same time.
//
// REVISION HISTORY
//
//   May 22, 1994  1.0A  : First release
//

int AL_PROTO ALMemory::FlushBuffer()
{
    if ( mStatus < 0 )
        return mStatus;
//
// If the write index is 0, we can skip all this stuff, because there
// is nothing in the buffer to flush out.
//
    if ( muWriteIndex != 0 ) {
        if ( miUpdateCrcFlag )
            UpdateCrc( muWriteIndex );
        if ( ( muWriteIndex + mlFilePointer ) > (long)muUserBufferSize )
            if ( GrowUserBuffer( muWriteIndex + mlFilePointer ) < 0 )
                return mStatus;
#if defined( AL_WINDOWS_MEMORY )
//
// Can't use memcpy with huge pointers, at least not with the optimized
// versions.
//
        char AL_HUGE *temp = mpcUserBuffer + mlFilePointer;
        for ( unsigned int i = 0 ; i < muWriteIndex ; i++ )
            *temp++ = mpcBuffer[ i ];
//        memcpy( mpcUserBuffer +          mlFilePointer, mpcBuffer, muWriteIndex );
#else
        memcpy( mpcUserBuffer + (size_t) mlFilePointer, mpcBuffer, muWriteIndex );
#endif
        mlFilePointer += muWriteIndex;
        muWriteIndex = 0;
        if ( mlSize < mlFilePointer )
            mlSize = mlFilePointer;
    }
    muReadIndex = 0;
    muBufferValidData = 0;
    YieldTime();
    return AL_SUCCESS;
}

//
// int ALMemory::Close()
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  AL_SUCCESS, or various error codes that filter on down from other
//  routines.
//
// DESCRIPTION
//
//  Close() is supposed to do the same thing as fclose() in the run
//  time library.  The most important thing we are concerned about is
//  that the I/O buffer gets freed up by the base class, so this suddenly
//  might not be a giant heavyweight object any more.
//
//  After freeing things up in the base class, we check to see if 
//  we have allocated more space than we really need.  If so, we do
//  a realloc() of some sort to give space back to the O/S.
//  
// REVISION HISTORY
//
//   May 22, 1994  1.0A  : First release
//

int AL_PROTO ALMemory::Close()
{
    if ( mpcBuffer == 0 )
        return mStatus;
    FlushBuffer();
    ALStorage::Close();
//
// If we aren't using all our space, give back the extra.
//
    if ( mlSize < (long) muUserBufferSize ) {
#if defined( AL_WINDOWS_MEMORY )
        GlobalUnlock( (HGLOBAL) mhUserMemoryHandle );
        HGLOBAL new_handle = GlobalReAlloc( (HGLOBAL) mhUserMemoryHandle, mlSize, GMEM_MOVEABLE );
        if ( new_handle != 0 )
            mhUserMemoryHandle = new_handle;
        mpcUserBuffer = (char AL_HUGE *) GlobalLock( (HGLOBAL) mhUserMemoryHandle );
        muUserBufferSize = mlSize;
#else
        char *new_buf = (char *) realloc( mpcUserBuffer, (size_t) mlSize );
        if ( new_buf )
            mpcUserBuffer = new_buf;
        muUserBufferSize = (size_t) mlSize;
#endif
    }
    return mStatus;
}

//
// int ALMemory::Create()
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  AL_SUCCESS, AL_CANT_ALLOCATE_MEMORY, or various error codes that 
//  filter on down from other routines.
//
// DESCRIPTION
//
//  This is like creating a new file.  If there isn't a memory buffer
//  already assigned to this object, we create one, with an initial
//  allocation of 16Kbytes.
//  
// REVISION HISTORY
//
//   May 22, 1994  1.0A  : First release
//

int AL_PROTO ALMemory::Create()
{
    ALStorage::Create();
    if ( mStatus < AL_SUCCESS )
        return mStatus;
    if ( (char *) mName == 0 || strlen( mName ) == 0 )
        mName = "AL.TMP";
    if ( mfUserOwnsBuffer )
        return AL_SUCCESS;  //If the user supplied the buffer, we take what's available
#if defined( AL_WINDOWS_MEMORY )
    mhUserMemoryHandle = GlobalAlloc( GMEM_MOVEABLE, 16384 );
    if ( mhUserMemoryHandle ) {
        mpcUserBuffer = (char AL_HUGE *) GlobalLock( (HGLOBAL) mhUserMemoryHandle );
        muUserBufferSize = 16384;
    } else {
        mpcUserBuffer = 0;
        return mStatus.SetError( AL_CANT_ALLOCATE_MEMORY,
                                 "Allocation failure when attempting to "
                                 "create a buffer "
                                 "of %ld bytes for ALMemory "
                                 "%s in Create()",
                                 16384,
                                 mName.GetSafeName() );
    }
#else
    mpcUserBuffer = (char *) malloc( 16384 );
    muUserBufferSize = 16384;
    if ( mpcUserBuffer == 0 )
        return mStatus.SetError( AL_CANT_ALLOCATE_MEMORY,
                                 "Allocation failure when attempting to "
                                 "create a buffer "
                                 "of %ld bytes for ALMemory "
                                 "%s in Create()",
                                 16384,
                                 mName.GetSafeName() );
#endif
    return AL_SUCCESS;
}

//
// int ALMemory::Open()
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  AL_SUCCESS, AL_CANT_OPEN_FILE, or various error codes that 
//  filter on down from other routines.
//
// DESCRIPTION
//
//  This is like opening an existing file.  Since there is supposed to be
//  an existing memory buffer already, we gripe if we can't find one.
//  
// REVISION HISTORY
//
//   May 22, 1994  1.0A  : First release
//

int AL_PROTO ALMemory::Open()
{
    ALStorage::Open();
    if ( mStatus < AL_SUCCESS )
        return mStatus;
    if ( mpcUserBuffer == 0 )
        return mStatus.SetError( AL_CANT_OPEN_FILE,
                                   "Attempt to open ALMemory %s "
                                   "with no buffer allocated",
                                   mName.GetSafeName() );
    else
        mlSize = (long) muUserBufferSize;
    return AL_SUCCESS;
}