Files
BT411/engine/MUNGA/RESOURCE.cpp
T
arcattackandClaude Opus 4.8 7b7d465e5e Initial commit: bt411 -- standalone Windows BattleTech (Tesla 4.10 port)
Clean, self-contained extraction of the BattleTech-specific work from the
reverse-engineering workspace -- engine + game + content + build, with nothing
from Red Planet or the raw archive dumps. Builds green (Win32) and runs the
single-player drive->animate->target->fire->damage->destroy loop out of the box.

Layout:
  engine/   MUNGA + MUNGA_L4 shared 2007 engine, carrying our BT render/loader
            work (bgfload/L4D3D/L4VIDEO: BSL bit-slice decode, LOD/ground/shadow
            models) + image codec; the minimal rp/ headers the audio HAL needs
  game/     reconstructed BT logic + surviving-original BT source + fwd shims
            + WinMain launcher
  content/  full runtime tree (BTL4.RES, VIDEO/, GAUGE/, AUDIO/, eggs, BTDPL.INI)
  docs/     format specs + reconstruction ledgers
  reference/ raw Ghidra pseudocode (recon source-of-truth) + decomp exporter
  tools/    MP console emulator + map/resource scanners

One top-level CMake builds munga_engine lib + bt410_l4 game lib + btl4.exe.
All paths relativized (186 fwd shims + ~437 CMake abs paths -> repo-relative);
DXSDK is the one external, overridable via -DDXSDK. Verified: builds to a
byte-identical 2.27MB exe and runs combat (TARGET DESTROYED, 0 crashes) against
the bundled content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 21:03:40 -05:00

912 lines
22 KiB
C++

#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