#include "munga.h"
#include "windows.h"
#pragma hdrstop

#include "resource.h"
#include "resver.h"

ResourceDescription::ResourceDescription(
	ResourceFile *file,
	const char* name,
	ResourceDescription::ResourceType type,
	int priority,
	LWord flags,
	const void* address,
	size_t size,
	ResourceDescription::ResourceID index
)
{
	Check(this);
	Check_Pointer(name);

	resourceFile = file;
	resourceID = index;
	resourceType = type;
	Str_Copy(resourceName, name, sizeof(resourceName));
	for (int i = strlen(resourceName) + 1; i<sizeof(resourceName); ++i)
	{
		resourceName[i] = '\0';
	}

	downloadPriority = priority;
	resourceFlags = flags;
	offsetInFile = 0;
	resourceSize = size;
	resourceAddress = NULL;
	lockCount = 0;

	if (address)
	{
		Check_Pointer(address);
		resourceAddress = new char[resourceSize];
		Register_Pointer(resourceAddress);
		Mem_Copy(resourceAddress, address, resourceSize, resourceSize);
		SetLoaded();
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ResourceDescription::~ResourceDescription()
{
	Check(this);
//	Verify(!IsLocked());
	if (resourceAddress)
	{
		Unregister_Pointer(resourceAddress);
		delete[] resourceAddress;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ResourceDescription::ReleaseUnlockedResource()
{
	Check(this);
	if (!IsLocked())
	{
		if (IsLoaded())
		{
			SetNotLoaded();
			Verify(resourceAddress);
			Unregister_Pointer(resourceAddress);
			delete[] resourceAddress;
			resourceAddress = NULL;
		}
		Verify(!resourceAddress);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ResourceDescription::LoadResource()
{
	Check(this);
	if (!IsLoaded())
	{
		Verify(!resourceAddress);
		Check(resourceFile);
		resourceFile->LoadResource(this);
		Verify(resourceAddress);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	ResourceDescription::TestInstance() const
{
	return True;
}

//#############################################################################
//##########################    ResourceFile    ###############################
//#############################################################################

//#############################################################################
//      ResourceFile    CONSTRUCTORS & DESTRUCTORS
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ResourceFile::ResourceFile():
	labOnly(0),
	resourceArray(NULL),
	resourceArraySize(0),
	maxResourceID(ResourceDescription::NullResourceID),
	lastPreallocatedResourceID(ResourceDescription::NullResourceID)
{
	versionArray[0] = RESOURCE_FORMAT_VERSION;
	versionArray[1] = 0;
	versionArray[2] = 0;
	versionArray[3] = 0;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ResourceFile::~ResourceFile()
{
	if (resourceArray)
	{
		for (int i=maxResourceID; i>=0; i--)
		{
			if (resourceArray[i])
			{
				Unregister_Object(resourceArray[i]);
				delete resourceArray[i];
			}
		}
		Unregister_Pointer(resourceArray);
		delete[] resourceArray;
	}

	versionArray[0] = RESOURCE_FORMAT_VERSION;
	versionArray[1] = 0;
	versionArray[2] = 0;
	versionArray[3] = 0;
	labOnly = 0;
	resourceArraySize = 0;
	maxResourceID = ResourceDescription::NullResourceID;
	resourceArray = NULL;
}

//##############################################################################
//      ResourceFile    METHODS
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ResourceDescription*
	ResourceFile::AddResource(
		const char* name,
		ResourceDescription::ResourceType type,
		int priority,
		LWord load_flag,
		const void* address,
		size_t size,
		ResourceDescription::ResourceID index
	)
{
	Check(this);
	Check_Pointer(name);

	int i;
	//
	//-------------------------------------------------------------------------
	// If we asked for the next available ID number, find out what it should be
	//-------------------------------------------------------------------------
	//
	if (index == ResourceDescription::NullResourceID)
	{
		index = lastPreallocatedResourceID + 1;
		if (resourceArray)
		{
			Check_Pointer(resourceArray);
			for (; index<=resourceArraySize; ++index)
			{
				if (!resourceArray[index])
				{
					break;
				}
			}
		}
	}

	//
	//-------------------------------------------------------------
	// Check to see if the resource index table needs to be created
	//-------------------------------------------------------------
	//
	if (!resourceArray)
	{
		resourceArraySize = index + IndexBlockSize;
		resourceArray = new ResourceDescription* [resourceArraySize];
		Register_Pointer(resourceArray);
		for (i=0; i<resourceArraySize; ++i)
		{
			resourceArray[i] = NULL;
		}
	}

	//
	//-----------------------------------------------------------
	// Otherwise, check to see if we have to grow the index table
	//-----------------------------------------------------------
	//
	if (index >= resourceArraySize)
	{
		//
		//----------------------------------------
		// Allocate a new table for the main index
		//----------------------------------------
		//
		ResourceDescription **new_index =
			new ResourceDescription* [index + IndexBlockSize];
		Register_Pointer(new_index);

		//
		//------------------------------------
		// Copy the old table into the new one
		//------------------------------------
		//
		Check_Pointer(resourceArray);
		for (i=0; i<resourceArraySize; ++i)
		{
			new_index[i] = resourceArray[i];
		}

		//
		//---------------------
		// Delete the old table
		//---------------------
		//
		Unregister_Pointer(resourceArray);
		delete[] resourceArray;
		resourceArray = new_index;
		resourceArraySize = index + IndexBlockSize;

		//
		//----------------------------------
		// NULL the new entries in the table
		//----------------------------------
		//
		while (i<resourceArraySize)
		{
			resourceArray[i++] = NULL;
		}
	}

	//
	//-----------------------------------------------
	// Add the new resource in at the specified index
	//-----------------------------------------------
	//

	Check_Pointer(resourceArray);
	Verify(!resourceArray[index]);
	if (index > maxResourceID)
	{
		maxResourceID = index;
	}
	resourceArray[index] =
		new ResourceDescription(
			this,
			name,
			type,
			priority,
			load_flag,
			address,
			size,
			index
		);

	//
	//----------------------------------------------------------------------
	// The caller of this function is responsible for registering the memory
	//----------------------------------------------------------------------
	//
	Register_Object(resourceArray[index]);
	return resourceArray[index];
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ResourceDescription*
	ResourceFile::AddResourceList(
		const char* name,
		ResourceDescription::ResourceType type,
		int priority,
		LWord load_flag,
		const ResourceDescription::ResourceID* address,
		int count,
		ResourceDescription::ResourceID index
	)
{
	Check(this);
	Check_Pointer(name);
	Check_Pointer(address);

	//
	//--------------------------------------------------------
	// prefix the directory table with the count of references
	//--------------------------------------------------------
	//
	ResourceDescription::ResourceID *table =
		new ResourceDescription::ResourceID[count+1];
	Register_Pointer(table);
	for (int i=0; i<count; ++i)
	{
		table[i+1] = address[i];
	}
	*table = count;

	//
	//-------------------------------------------------
	// Go ahead and add the directory to the main index
	//-------------------------------------------------
	//
	ResourceDescription *directory =
		AddResource(
			name,
			type,
			priority,
			load_flag,
			table,
			(count+1) * sizeof(ResourceDescription::ResourceID),
			index
		);
	Unregister_Pointer(table);
	delete[] table;

	return directory;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ResourceDescription*
	ResourceFile::AddResourceMemoryStream(
		const char* name,
		ResourceDescription::ResourceType type,
		int priority,
      LWord load_flag,
      MemoryStream* memory_stream,
		ResourceDescription::ResourceID index
   )
{
	Check(this);
   Check(memory_stream);

   size_t size = memory_stream->GetBytesUsed();
   memory_stream->Rewind();

	return
		AddResource(
			name,
			type,
			priority,
			load_flag,
			memory_stream->GetPointer(),
			size,
			index
		);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ResourceFile::DeleteResource(ResourceDescription *resource)
{
	Check(this);
	Check(resource);
	Check_Pointer(resourceArray);
	Verify(!resource->IsLocked());

	//
	//------------------------------------------------------------------------
	// Delete pointer from resoureArray if it was there
	//------------------------------------------------------------------------
	//
	if (resource->resourceID != ResourceDescription::NullResourceID )
	{
		resourceArray[resource->resourceID] = NULL;

		//
		//------------------------------------------------------------------------
		// Find new maxResourceID
		//------------------------------------------------------------------------
		//
		if (resource->resourceID == maxResourceID )
		{
			ResourceDescription::ResourceID i;
			for (i = maxResourceID; i>=0; i--)
			{
				if(resourceArray[i] != NULL)
				{
					Check(resourceArray[i]);
					break;
				}
			}
			maxResourceID = i;
		}
	}
	//
	//------------------------------------------------------------------------
	// The caller of this function is responsible for unregistering the memory
	//------------------------------------------------------------------------
	//

	delete resource;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ResourceDescription*
	ResourceFile::FindResourceDescription(ResourceDescription::ResourceID index)
{
	Check(this);

	if (index<0 || index>maxResourceID || !resourceArray)
	{
		return NULL;
	}

	Check_Pointer(resourceArray);
	ResourceDescription *res = resourceArray[index];
	return res;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ResourceDescription*
	ResourceFile::FindResourceDescription(
		const char* name,
		ResourceDescription::ResourceType type,
		ResourceDescription::ResourceID after
	)
{
	Check(this);

	ResourceDescription *desc;
	ResourceDescription::ResourceID i;
	for (i=after+1; i<=maxResourceID; ++i)
	{
		desc = FindResourceDescription(i);
		if (desc)
		{
			Check(desc);
			if (
				type == desc->resourceType &&
				 (!name || !strcmp(name, desc->resourceName))
			)
			{
				break;
			}
		}
	}
	if (i > maxResourceID)
	{
		desc = NULL;
	}
	return desc;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ResourceDescription*
	ResourceFile::SearchList(
		ResourceDescription::ResourceID list_id,
		ResourceDescription::ResourceType type
	)
{
	ResourceDescription *list_res = FindResourceDescription(list_id);
	if (list_res)
	{
		Check(list_res);
		list_res->Lock();
		Verify(
			list_res->resourceType <= ResourceDescription::MaxListResourceType
		);
		ResourceDescription::ResourceID *list =
			(ResourceDescription::ResourceID*)list_res->resourceAddress;
		Check_Pointer(list);
		for (int i=1; i<=*list; ++i)
		{
			ResourceDescription *res = FindResourceDescription(list[i]);
			Check(res);
			if (res->resourceType == type)
			{
				list_res->Unlock();
				return res;
			}
		}
		list_res->Unlock();
	}
	return NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ResourceDescription*
	ResourceFile::SearchList(
		ResourceDescription::ResourceID list_id,
		const char* name
	)
{
	Check_Pointer(name);
	ResourceDescription *list_res = FindResourceDescription(list_id);
	if (list_res)
	{
		Check(list_res);
		list_res->Lock();
		Verify(
			list_res->resourceType <= ResourceDescription::MaxListResourceType
		);
		ResourceDescription::ResourceID *list =
			(ResourceDescription::ResourceID*)list_res->resourceAddress;
		Check_Pointer(list);
		for (int i=1; i<=*list; ++i)
		{
			ResourceDescription *res = FindResourceDescription(list[i]);
			Check(res);
			if (!strcmp(name, res->resourceName))
			{
				list_res->Unlock();
				return res;
			}
		}
		list_res->Unlock();
	}
	return NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ResourceFile::ReleaseUnlockedResources()
{
	for (ResourceDescription::ResourceID i = maxResourceID; i>=0; i--)
	{
		if(resourceArray[i] != NULL)
		{
			ResourceDescription *res = resourceArray[i];
			Check(res);
			res->ReleaseUnlockedResource();
		}
	}
}

//#############################################################################
// ResourceFile Test Support
//

Logical
	ResourceFile::TestInstance() const
{
	return True;
}

//##############################################################################
//##########################    StreamableResourceFile    ######################
//##############################################################################
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
StreamableResourceFile::StreamableResourceFile()
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
StreamableResourceFile::StreamableResourceFile(const char* file_name)
{
	Open(file_name);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
StreamableResourceFile::StreamableResourceFile(const char* file_name, Byte version_array[3], int match_level)
{
	Open(file_name);
	int i;
	for (i=0; i<match_level; ++i)
		if (version_array[i] != versionArray[i+1])
			break;

	if (i < match_level)
	{
		DEBUG_STREAM << "\n\nError - Resource file " << file_name << " v" << (int)versionArray[0] << '.' << (int)versionArray[1] << '.' << (int)versionArray[2] << '.' << (int)versionArray[3] << " is obsolete!\n\n" << std::flush;
		PostQuitMessage(AbortExitCodeID);
	}

#if 0
	if (release_only && labOnly)
	{
		DEBUG_STREAM << "\n\n\nERROR!\n\n"
			"This resource file was not properly released!\n\n";
		PostQuitMessage(AbortExitCodeID);
	}
#endif
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
StreamableResourceFile::~StreamableResourceFile()
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

int
	StreamableResourceFile::Open(const char *file_name)
{
	Check(this);
	Str_Copy(resourceFilename, file_name, sizeof(resourceFilename));

	diskFile.Open(resourceFilename);
	if (!diskFile.IsFileOpened())
	{
		return False;
	}

	//
	//-----------------------
	// Read in resource count
	//-----------------------
	//
	maxResourceID = 0;

	diskFile.ReadBytes(&versionArray, sizeof(versionArray));

	if (versionArray[0] != RESOURCE_FORMAT_VERSION)
	{
		DEBUG_STREAM << "\n\nError - Resource file " << file_name << " v"
		 << (int)versionArray[0] << '.' << (int)versionArray[1] << '.'
		 << (int)versionArray[2] << '.' << (int)versionArray[3]
		<< " is obsolete!\n\n" << std::flush;
		PostQuitMessage(AbortExitCodeID);
	}

	diskFile >> labOnly >> maxResourceID;

	//
	//------------------------------------------------------------------
	// Read in main index table first, and make room for expansion later
	//------------------------------------------------------------------
	//
	size_t *offset_table = new size_t[maxResourceID];
	Register_Pointer(offset_table);

	diskFile.ReadBytes(offset_table, maxResourceID*sizeof(size_t));
	resourceArraySize = maxResourceID + IndexBlockSize;

	resourceArray = new ResourceDescription*[resourceArraySize];
	Register_Pointer(resourceArray);
	int i;
	for (i=0; i<resourceArraySize; ++i)
	{
		resourceArray[i] = NULL;
	}
	--maxResourceID;

	//
	//---------------------------------------------------------------------
	// Step through the index table, reading each description as we find it
	//---------------------------------------------------------------------
	//
	for (i=0; i<=maxResourceID; ++i)
	{
		//
		//--------------------------------------------
		// Make sure we skip empty resources correctly
		//--------------------------------------------
		//
		if (!offset_table[i])
		{
			resourceArray[i] = NULL;
			continue;
		}

		//
		//------------------------
		// Read in the description
		//------------------------
		//
		ResourcePhysicalFormat buffer;
		diskFile.SetPointer(offset_table[i]);
		diskFile >> buffer;

		//
		//-------------------------------------------------
		// If the preload flag is set, read in the resource
		//-------------------------------------------------
		//

		char *data = NULL;
		if (buffer.resourceFlags == ResourceDescription::Preload && buffer.resourceLength > 0)
		{
			data = new char[buffer.resourceLength];
			Register_Pointer(data);
			diskFile.SetPointer(buffer.resourceOffset);
			diskFile.ReadBytes(data, buffer.resourceLength);
		}
		if (buffer.resourceType > ResourceDescription::MaxListResourceType || !data)
		{
			resourceArray[i] = AddResource(
				buffer.resourceName,
				buffer.resourceType,
				buffer.downloadPriority,
				buffer.resourceFlags,
				data,
				buffer.resourceLength,
				buffer.resourceID
			);
		}
		else
		{
			resourceArray[i] = AddResourceList(
				buffer.resourceName,
				buffer.resourceType,
				buffer.downloadPriority,
				buffer.resourceFlags,
				(int*)data + 1,
				*(int*)data,
				buffer.resourceID
			);
		}
		resourceArray[i]->offsetInFile = buffer.resourceOffset;
		if (data)
		{
			resourceArray[i]->Lock();
			Unregister_Pointer(data);
			delete[] data;
		}
	}

	Unregister_Pointer(offset_table);
	delete[] offset_table;
	return True;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	StreamableResourceFile::Save()
{
	Check(this);
	if (!resourceArray)
	{
		return False;
	}

	//
	//-----------------------------------------------------------------------
	// Make sure to load any unloaded resources before closing the input file
	//-----------------------------------------------------------------------
	//
	ResourceDescription *desc;
	ResourceDescription::ResourceID i;
	for (i=0; i<=maxResourceID; ++i)
	{
		desc = resourceArray[i];
		if (desc)
		{
			Check(desc);
			desc->LoadResource();
		}
	}

	//
	//-------------------------------------------
	// Close the current file, and open a new one
	//-------------------------------------------
	//
	if (diskFile.IsFileOpened())
	{
		diskFile.Close();
	}
	diskFile.Open(resourceFilename, True);
	Verify(diskFile.IsFileOpened());

	//
	//-------------------------------------
	// Write out resource count and version
	//-------------------------------------
	//
	++maxResourceID;

	diskFile.WriteBytes(&versionArray, sizeof(versionArray));
	diskFile << labOnly << maxResourceID;

	//
	//--------------------------------------------------------------------------
	// Write out main index table first.  This is just filling up space, and the
	// offsets will have to be written back into this section later
	//--------------------------------------------------------------------------
	//
	size_t *offset_table = new size_t[maxResourceID];
	Register_Pointer(offset_table);
	diskFile.WriteBytes(offset_table, maxResourceID*sizeof(size_t));

	//
	//-------------------------------------------------------------------------
	// Step through the index table, writing out each description as we find it
	//-------------------------------------------------------------------------
	//
	for (i=0; i<maxResourceID; ++i)
	{
		//
		//--------------------------------------------
		// Make sure we skip empty resources correctly
		//--------------------------------------------
		//
		desc = resourceArray[i];
		if (!desc)
		{
			offset_table[i] = 0;
			continue;
		}

		Check(desc);

		//
		//-------------------
		// Write out the data
		//-------------------
		//
		ResourcePhysicalFormat buffer;
		buffer.resourceOffset = diskFile.GetIndex();
		diskFile.WriteBytes(desc->resourceAddress, desc->resourceSize);

		//
		//--------------------------
		// Write out the description
		//--------------------------
		//
		offset_table[i] = diskFile.GetIndex();
		buffer.resourceID = desc->resourceID;
		buffer.resourceType = desc->resourceType;

		//#if sizeof(buffer.resourceName) < sizeof(desc->resourceName)
		//#       error ResourceDescription::resourceName is too big!
		//#endif

		memcpy(
			buffer.resourceName,
			desc->resourceName,
			sizeof(desc->resourceName)
		);

		buffer.resourceLength   = desc->resourceSize;
		buffer.downloadPriority = desc->downloadPriority;
		buffer.resourceFlags =
			desc->resourceFlags & ~ResourceDescription::LoadedFlag;
		diskFile << buffer;
	}
	diskFile.SetPointer(
		sizeof(maxResourceID) + sizeof(versionArray) + sizeof(labOnly)
	);
	diskFile.WriteBytes(offset_table, maxResourceID*sizeof(long));
	Unregister_Pointer(offset_table);
	delete(offset_table);
	--maxResourceID;
	diskFile.Close();
	return True;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	StreamableResourceFile::SaveAs(const char* file_name)
{
	Str_Copy(resourceFilename, file_name, sizeof(resourceFilename));
	return Save();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	StreamableResourceFile::LoadResource(ResourceDescription *res)
{
	ResourceFile::LoadResource(res);
	Verify(!res->resourceAddress);
	res->resourceAddress = new char[res->resourceSize];
	Register_Pointer(res->resourceAddress);
	diskFile.SetPointer(res->offsetInFile);
	diskFile.ReadBytes(res->resourceAddress, res->resourceSize);
}

//#############################################################################
// StreamableResourceFile Test Support
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	StreamableResourceFile::TestInstance() const
{
	return True;
}

#if defined(TEST_CLASS)
	#include "resource.tcp"
#endif
