Files
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

1204 lines
29 KiB
C++

#include "munga.h"
#pragma hdrstop
#include "objstrm.h"
#include "fileutil.h"
#include "resource.h"
#include "cstr.h"
#include "namelist.h"
#include "notation.h"
//#############################################################################
//########################## ObjectStream ###############################
//#############################################################################
ObjectID ObjectStream::lastObjectID = NullObjectID;
//
//#############################################################################
// ObjectStream
//#############################################################################
//
ObjectStream::ObjectStream()
{
resourceFile = NULL;
macroSocket = NULL;
}
//
//#############################################################################
// ObjectStream
//#############################################################################
//
ObjectStream::ObjectStream(
void *stream_start,
size_t stream_size
):
DynamicMemoryStream(stream_start, stream_size)
{
resourceFile = NULL;
macroSocket = NULL;
}
//
//#############################################################################
// ObjectStream
//#############################################################################
//
ObjectStream::ObjectStream(ResourceDescription *resource_description):
DynamicMemoryStream(
resource_description->resourceAddress,
resource_description->resourceSize
)
{
resourceFile = NULL;
macroSocket = NULL;
}
//
//#############################################################################
// ~ObjectStream
//#############################################################################
//
ObjectStream::~ObjectStream()
{
Verify(resourceFile == NULL);
Verify(macroSocket == NULL);
}
//
//#############################################################################
// TestInstance
//#############################################################################
//
Logical
ObjectStream::TestInstance() const
{
DynamicMemoryStream::TestInstance();
if (resourceFile != NULL)
{
Check(resourceFile);
}
if (macroSocket != NULL)
{
Check(macroSocket);
}
return True;
}
//
//#############################################################################
// MakeUniqueObjectID
//#############################################################################
//
ObjectID
ObjectStream::MakeUniqueObjectID()
{
lastObjectID++;
Verify(lastObjectID < ULONG_MAX);
return lastObjectID;
}
//
//#############################################################################
// CreateObjects
//#############################################################################
//
void
ObjectStream::CreateObjects()
{
Check(this);
//
//--------------------------------------------------------------------------
// While the stream is not empty, read objects...
//--------------------------------------------------------------------------
//
while (GetBytesRemaining() > 0L)
{
//
//-----------------------------------------------------------------------
// Read the class ID and object ID
//-----------------------------------------------------------------------
//
Enumeration class_ID;
ObjectID object_ID;
MemoryStream_Read(this, &class_ID);
MemoryStream_Read(this, &object_ID);
RewindPointer(sizeof(object_ID));
RewindPointer(sizeof(class_ID));
Verify(class_ID != RegisteredClass::NullClassID);
Verify(object_ID != NullObjectID);
//
//-----------------------------------------------------------------------
// Read the object
//-----------------------------------------------------------------------
//
RegisteredClass *object = MakeObjectImplementation(class_ID);
Register_Object(object);
//
//-----------------------------------------------------------------------
// Perform post creation processing
//-----------------------------------------------------------------------
//
CreatedObjectImplementation(object, object_ID);
}
}
//
//#############################################################################
// MakeObject
//#############################################################################
//
RegisteredClass*
ObjectStream::MakeObjectImplementation(Enumeration class_ID)
{
Check(this);
Verify(class_ID != RegisteredClass::NullClassID);
//
//-----------------------------------------------------------------------
// HACK - Constructors should be referenced through registration, for
// now just use a switch statement
//-----------------------------------------------------------------------
//
RegisteredClass *object = NULL;
switch (class_ID)
{
default:
Dump(class_ID);
Fail("ObjectStream::MakeObject - Unknown class ID");
break;
}
return object;
}
//
//#############################################################################
// CreatedObject
//#############################################################################
//
void
ObjectStream::CreatedObjectImplementation(
RegisteredClass*,
ObjectID
)
{
Check(this);
//
// Default behavior is to do nothing
//
}
//
//#############################################################################
// BuildFromNotationFile
//#############################################################################
//
void
ObjectStream::BuildFromNotationFile(
NotationFile *notation_file,
ResourceFile *resource_file
)
{
Check(this);
Check(notation_file);
Check(resource_file);
//
// Remember resource file
//
Verify(resourceFile == NULL);
resourceFile = resource_file;
//
// Make the macro socket
//
macroSocket = new MacroSocket(NULL, True);
Register_Object(macroSocket);
//
// Parse...
//
ParseNotationFile(notation_file);
//
// Delete macros
//
{
MacroIterator iterator(macroSocket);
iterator.DeletePlugs();
}
//
// Clean up
//
Unregister_Object(macroSocket);
delete macroSocket;
macroSocket = NULL;
resourceFile = NULL;
}
//
//#############################################################################
// ParseNotationFile
//#############################################################################
//
void
ObjectStream::ParseNotationFile(NotationFile *notation_file)
{
Check(this);
Check(notation_file);
Verify(DoesNotationFileExist(notation_file));
//
//--------------------------------------------------------------------------
// Parse pages
//--------------------------------------------------------------------------
//
NameList *page_name_list;
NameList::Entry *page_entry;
page_name_list = notation_file->MakePageList();
Register_Object(page_name_list);
page_entry = page_name_list->GetFirstEntry();
while (page_entry != NULL)
{
Check(page_entry);
//
//-----------------------------------------------------------------------
// Read the page name
//-----------------------------------------------------------------------
//
CString inculde_name_string("include");
CString macro_name_string("macro");
CString page_name_string;
Check_Pointer(page_entry->GetName());
page_name_string = page_entry->GetName();
//
//-----------------------------------------------------------------------
// Skip "no-ops"
//-----------------------------------------------------------------------
//
if (page_name_string.operator [](0) == '!')
{
page_entry = page_entry->GetNextEntry();
continue;
}
//
//-----------------------------------------------------------------------
// Make the entry list
//-----------------------------------------------------------------------
//
NameList *entry_list =
notation_file->MakeEntryList(
(NotationFile::NotePage*)page_entry->GetData()
);
Register_Object(entry_list);
//
//-----------------------------------------------------------------------
// Read "included" files
//-----------------------------------------------------------------------
//
if (page_name_string == inculde_name_string)
{
ReadIncludedFiles(entry_list);
}
//
//-----------------------------------------------------------------------
// Read "macros"
//-----------------------------------------------------------------------
//
else if (page_name_string == macro_name_string)
{
ReadMacros(entry_list);
}
//
//-----------------------------------------------------------------------
// Read the class ID and object name
//-----------------------------------------------------------------------
//
else
{
Tell(" Class " << page_name_string << "\n");
//
// Get class ID and object ID
//
Enumeration class_ID;
ObjectID object_ID;
class_ID = RegisteredClass::GetClassID(page_name_string);
if (class_ID == RegisteredClass::NullClassID)
{
Dump(page_name_string);
Dump(class_ID);
}
Verify(class_ID != RegisteredClass::NullClassID);
object_ID = MakeUniqueObjectID();
Verify(object_ID != NullObjectID);
//
// Get object name
//
const char *object_name;
CString object_name_string;
PerformMacroReplacement(entry_list);
if (
(object_name = (const char *)entry_list->FindData("name")) != NULL
)
{
object_name_string = object_name;
}
Check(&object_name_string);
//
//--------------------------------------------------------------------
// Build the object stream from the page
//--------------------------------------------------------------------
//
BuildFromPageImplementation(entry_list, class_ID, object_ID);
CleanupMacroReplacement(entry_list);
//
//--------------------------------------------------------------------
// Perform post build processing
//--------------------------------------------------------------------
//
BuiltFromPageImplementation(class_ID, object_ID, object_name_string);
}
Unregister_Object(entry_list);
delete entry_list;
page_entry = page_entry->GetNextEntry();
}
Unregister_Object(page_name_list);
delete page_name_list;
}
//
//#############################################################################
// ReadIncludedFiles
//#############################################################################
//
void
ObjectStream::ReadIncludedFiles(NameList *entry_list)
{
Check(this);
NameList::Entry *entry;
Check(entry_list);
entry = entry_list->GetFirstEntry();
while (entry != NULL)
{
Check(entry);
if (entry->IsName("file"))
{
Check_Pointer(entry->GetChar());
CString included_file_name_string;
included_file_name_string = entry->GetChar();
Tell(" File " << included_file_name_string << "\n");
NotationFile included_notation_file(included_file_name_string);
Check(&included_notation_file);
ParseNotationFile(&included_notation_file);
}
entry = entry->GetNextEntry();
}
}
//
//#############################################################################
// ReadMacros
//#############################################################################
//
void
ObjectStream::ReadMacros(NameList *entry_list)
{
Check(this);
NameList::Entry *entry;
Check(entry_list);
entry = entry_list->GetFirstEntry();
while (entry != NULL)
{
Check(entry);
if (entry->GetData() != NULL)
{
Check_Pointer(entry->GetName());
Check_Pointer(entry->GetChar());
CString macro_name;
MacroValue *macro_value;
MacroValue *current_macro_value;
macro_name = entry->GetName();
macro_value = new MacroValue(entry->GetChar());
Register_Object(macro_value);
Check(macroSocket);
if ((current_macro_value = macroSocket->Find(macro_name)) != NULL)
{
Unregister_Object(current_macro_value);
delete current_macro_value;
}
macroSocket->AddValue(macro_value, macro_name);
}
entry = entry->GetNextEntry();
}
}
//
//#############################################################################
// PerformMacroReplacement
//#############################################################################
//
void
ObjectStream::PerformMacroReplacement(NameList *entry_list)
{
Check(this);
NameList::Entry *entry;
Check(entry_list);
entry = entry_list->GetFirstEntry();
while (entry != NULL)
{
Check(entry);
if (entry->GetData() != NULL)
{
CString value_string(entry->GetChar());
//
// Create a replacement string by matching each token in the
// value against the tokens in the macro table
//
CString replacement_string;
CString token_string;
CString delimiter_string(",");
CString empty_string;
int i = 0;
Check(&value_string);
Check(&empty_string);
while ((token_string = value_string.GetNthToken(i)) != empty_string)
{
Check(&token_string);
//
// Append delimter
//
if (replacement_string != empty_string)
{
replacement_string += delimiter_string;
}
//
// Does a macro exist for this token? If so, use it, otherwise
// use the original token
//
#if 0
MacroValue *macro_value;
Check(macroSocket);
if ((macro_value = macroSocket->Find(token_string)) != NULL)
{
Check(macro_value);
Check(&replacement_string);
replacement_string += macro_value->GetItem();
}
else
{
replacement_string += token_string;
}
#else
replacement_string += DeriveMacroValue(token_string);
#endif
i++;
}
//
// Allocate memory for the string and assign it to the entry
//
size_t size;
char *pointer;
size = replacement_string.Length() + 1;
#if 0
pointer = (char*)malloc(size);
#else
pointer = new char[size];
#endif
Register_Pointer(pointer);
Str_Copy(pointer, ((const char*)replacement_string), size);
entry->dataReference = pointer;
}
entry = entry->GetNextEntry();
}
}
//
//#############################################################################
//#############################################################################
//
CString
ObjectStream::DeriveMacroValue(const CString &token_string)
{
Check(this);
Check(&token_string);
MacroValue *macro_value;
CString replacement_string = token_string;
Check(macroSocket);
while ((macro_value = macroSocket->Find(replacement_string)) != NULL)
{
Check(macro_value);
replacement_string = macro_value->GetItem();
}
return replacement_string;
}
//
//#############################################################################
// CleanupMacroReplacement
//#############################################################################
//
void
ObjectStream::CleanupMacroReplacement(NameList *entry_list)
{
Check(this);
NameList::Entry *entry;
Check(entry_list);
entry = entry_list->GetFirstEntry();
while (entry != NULL)
{
Check(entry);
if (entry->GetData() != NULL)
{
char *replacement_string = entry->GetChar();
Unregister_Pointer(replacement_string);
#if 0
free(replacement_string);
#else
delete[] replacement_string;
#endif
}
entry = entry->GetNextEntry();
}
}
//
//#############################################################################
// BuildFromPage
//#############################################################################
//
void
ObjectStream::BuildFromPageImplementation(
NameList*,
Enumeration class_ID,
ObjectID
)
{
Check(this);
Verify(class_ID != RegisteredClass::NullClassID);
//
//-----------------------------------------------------------------------
// HACK - Page interpreters should be referenced through registration,
// for now just use a switch statement
//-----------------------------------------------------------------------
//
switch (class_ID)
{
default:
Dump(class_ID);
Fail("ObjectStream::BuildFromPage - Unknown class ID");
break;
}
}
//
//#############################################################################
// BuiltFromPage
//#############################################################################
//
void
ObjectStream::BuiltFromPageImplementation(
Enumeration,
ObjectID,
const CString&
)
{
Check(this);
//
// Default behavior is to do nothing
//
}
//#############################################################################
//####################### PlugStreamManager #############################
//#############################################################################
PlugStreamManager PlugStreamManager::plugStreamManager;
#define PLUGSTREAMMANAGER_HASHSIZE (500)
//
//#############################################################################
// PlugStreamManager
//#############################################################################
//
PlugStreamManager::PlugStreamManager():
plugIndex(PLUGSTREAMMANAGER_HASHSIZE, NULL, True),
objectIDIndex(NULL, True)
{
}
//
//#############################################################################
// ~PlugStreamManager
//#############################################################################
//
PlugStreamManager::~PlugStreamManager()
{
Cleanup();
}
//
//#############################################################################
// TestInstance
//#############################################################################
//
Logical
PlugStreamManager::TestInstance() const
{
Node::TestInstance();
Check(&plugIndex);
Check(&objectIDIndex);
return True;
}
//
//#############################################################################
// Cleanup
//#############################################################################
//
void
PlugStreamManager::Cleanup()
{
Check(this);
//
// Delete static objects
//
{
TreeIteratorOf<ObjectIDPlug*, CString> iterator(&objectIDIndex);
Check(&iterator);
iterator.DeletePlugs();
}
{
HashIteratorOf<Plug*, ObjectID> iterator(&plugIndex);
Check(&iterator);
iterator.DeletePlugs();
}
}
//
//#############################################################################
// AddPlug
//#############################################################################
//
void
PlugStreamManager::AddPlug(
Plug *plug,
ObjectID object_ID
)
{
Check(plug);
Check(&plugStreamManager);
plugStreamManager.plugIndex.AddValue(plug, object_ID);
}
//
//#############################################################################
// FindPlug
//#############################################################################
//
Plug*
PlugStreamManager::FindPlug(ObjectID object_ID)
{
Check(&plugStreamManager);
return plugStreamManager.plugIndex.Find(object_ID);
}
//
//#############################################################################
// AddPlugName
//#############################################################################
//
void
PlugStreamManager::AddPlugName(
ObjectID object_ID,
const CString &object_name_string
)
{
Check(&object_name_string);
ObjectIDPlug *object_ID_plug =
new ObjectIDPlug(object_ID);
Register_Object(object_ID_plug);
Check(&plugStreamManager.objectIDIndex);
#if DEBUG_LEVEL>0
if (plugStreamManager.objectIDIndex.Find(object_name_string) != NULL)
{
Dump(object_name_string);
}
#endif
plugStreamManager.objectIDIndex.AddValue(
object_ID_plug,
object_name_string
);
}
//
//#############################################################################
// FindPlugName
//#############################################################################
//
ObjectID
PlugStreamManager::FindPlugName(const CString &object_name_string)
{
Check(&object_name_string);
Check(&plugStreamManager.objectIDIndex);
ObjectIDPlug *plug = plugStreamManager.objectIDIndex.Find(object_name_string);
if (plug != NULL)
{
Check(plug);
return plug->GetItem();
}
return NullObjectID;
}
//#############################################################################
//########################### PlugStream ################################
//#############################################################################
//
//#############################################################################
// PlugStream
//#############################################################################
//
PlugStream::PlugStream()
{
localPlugIndex = NULL;
localObjectIDIndex = NULL;
}
//
//#############################################################################
// PlugStream
//#############################################################################
//
PlugStream::PlugStream(
void *stream_start,
size_t stream_size
):
ObjectStream(stream_start, stream_size)
{
localPlugIndex = NULL;
localObjectIDIndex = NULL;
}
//
//#############################################################################
// PlugStream
//#############################################################################
//
PlugStream::PlugStream(ResourceDescription *resource_description):
ObjectStream(resource_description)
{
localPlugIndex = NULL;
localObjectIDIndex = NULL;
}
//
//#############################################################################
// ~PlugStream
//#############################################################################
//
PlugStream::~PlugStream()
{
Verify(localPlugIndex == NULL);
Verify(localObjectIDIndex == NULL);
}
//
//#############################################################################
// TestInstance
//#############################################################################
//
Logical
PlugStream::TestInstance() const
{
ObjectStream::TestInstance();
if (localPlugIndex != NULL)
{
Check(localPlugIndex);
}
if (localObjectIDIndex != NULL)
{
Check(localObjectIDIndex);
}
return True;
}
//
//#############################################################################
// CreateObjects
//#############################################################################
//
void
PlugStream::CreateObjects()
{
Check(this);
//
//--------------------------------------------------------------------------
// Keep local object address index
//--------------------------------------------------------------------------
//
LocalPlugIndex
local_index(NULL, True);
Check(&local_index);
localPlugIndex = &local_index;
//
//--------------------------------------------------------------------------
// Parse object stream
//--------------------------------------------------------------------------
//
ObjectStream::CreateObjects();
//
//--------------------------------------------------------------------------
// Forget local object address index
//--------------------------------------------------------------------------
//
localPlugIndex = NULL;
}
//
//#############################################################################
// CreatedObject
//#############################################################################
//
void
PlugStream::CreatedObjectImplementation(
RegisteredClass *object,
ObjectID object_ID
)
{
Check(this);
AddLocalPlug(Cast_Object(Plug*, object), object_ID);
}
//
//#############################################################################
// AddLocalPlugAddress
//#############################################################################
//
void
PlugStream::AddLocalPlug(
Plug *plug,
ObjectID object_ID
)
{
Check(this);
Check(localPlugIndex);
localPlugIndex->AddValue(plug, object_ID);
}
//
//#############################################################################
// AddGlobalPlugAddress
//#############################################################################
//
void
PlugStream::AddGlobalPlug(
Plug *plug,
ObjectID object_ID
)
{
Check(this);
#if 0
Check(&plugStreamManager);
plugStreamManager.globalPlugIndex.AddValue(plug, object_ID);
#else
PlugStreamManager::AddPlug(plug, object_ID);
#endif
}
//
//#############################################################################
// FindPlug
//#############################################################################
//
Plug*
PlugStream::FindPlug(ObjectID object_ID)
{
Check(this);
//
//--------------------------------------------------------------------------
// Search the local address index
//--------------------------------------------------------------------------
//
Plug *plug;
Check(localPlugIndex);
if ((plug = localPlugIndex->Find(object_ID)) != NULL)
{
Check(plug);
return plug;
}
//
//--------------------------------------------------------------------------
// Search the global address index
//--------------------------------------------------------------------------
//
#if 0
Check(&plugStreamManager);
if ((plug = plugStreamManager.globalPlugIndex.Find(object_ID)) != NULL)
{
Check(plug);
return plug;
}
return NULL;
#else
if ((plug = PlugStreamManager::FindPlug(object_ID)) != NULL)
{
Check(plug);
return plug;
}
return NULL;
#endif
}
//
//#############################################################################
// BuildFromNotationFile
//#############################################################################
//
void
PlugStream::BuildFromNotationFile(
NotationFile *notation_file,
ResourceFile *resource_file
)
{
Check(this);
Check(notation_file);
Check(resource_file);
//
//--------------------------------------------------------------------------
// Keep local object ID index
//--------------------------------------------------------------------------
//
LocalObjectIDIndex
local_index(NULL, True);
Check(&local_index);
localObjectIDIndex = &local_index;
//
//--------------------------------------------------------------------------
// Parse notation file
//--------------------------------------------------------------------------
//
ObjectStream::BuildFromNotationFile(notation_file, resource_file);
//
//--------------------------------------------------------------------------
// Forget local object ID index
//--------------------------------------------------------------------------
//
{
VChainIteratorOf<ObjectIDPlug*, CString> iterator(&local_index);
Check(&iterator);
iterator.DeletePlugs();
}
localObjectIDIndex = NULL;
}
//
//#############################################################################
// BuiltFromPage
//#############################################################################
//
void
PlugStream::BuiltFromPageImplementation(
Enumeration,
ObjectID object_ID,
const CString &object_name_string
)
{
Check(this);
Check(&object_name_string);
AddLocalObjectID(object_ID, object_name_string);
}
//
//#############################################################################
// AddLocalObjectID
//#############################################################################
//
void
PlugStream::AddLocalObjectID(
ObjectID object_ID,
const CString &object_name_string
)
{
Check(this);
Check(&object_name_string);
ObjectIDPlug *object_ID_plug =
new ObjectIDPlug(object_ID);
Register_Object(object_ID_plug);
Check(localObjectIDIndex);
#if DEBUG_LEVEL>0
if (localObjectIDIndex->Find(object_name_string) != NULL)
{
Dump(object_name_string);
}
#endif
localObjectIDIndex->AddValue(object_ID_plug, object_name_string);
}
//
//#############################################################################
// AddGlobalObjectID
//#############################################################################
//
void
PlugStream::AddGlobalObjectID(
ObjectID object_ID,
const CString &object_name_string
)
{
Check(this);
Check(&object_name_string);
PlugStreamManager::AddPlugName(object_ID, object_name_string);
}
//
//#############################################################################
// FindObjectID
//#############################################################################
//
ObjectID
PlugStream::FindObjectID(const CString &object_name_string)
{
Check(this);
Check(&object_name_string);
//
//--------------------------------------------------------------------------
// Search the local object ID index
//--------------------------------------------------------------------------
//
ObjectIDPlug *plug;
Check(localObjectIDIndex);
if ((plug = localObjectIDIndex->Find(object_name_string)) != NULL)
{
Check(plug);
return plug->GetItem();
}
//
//--------------------------------------------------------------------------
// Search the global address index
//--------------------------------------------------------------------------
//
#if 0
Check(&plugStreamManager);
plug = plugStreamManager.globalObjectIDIndex.Find(object_name_string);
if (plug != NULL)
{
Check(plug);
return plug->GetItem();
}
return NULL;
#else
return PlugStreamManager::FindPlugName(object_name_string);
#endif
}
//
//#############################################################################
// PlugStream_FindEntryAndWriteObjectID
//#############################################################################
//
void
PlugStream_FindEntryAndWriteObjectID(
PlugStream *stream,
NameList *name_list,
const CString &entry_name
)
{
Check(stream);
Check(name_list);
Check(&entry_name);
void *entry_data;
CString object_name;
ObjectID object_ID;
entry_data = name_list->FindData(entry_name);
if (entry_data == NULL)
{
std::cout << "PlugStream_FindEntryAndWriteObjectID - entry_name == ";
std::cout << entry_name << "\n";
Fail("PlugStream_FindEntryAndWriteObjectID - entry_data == NULL");
}
Check_Pointer(entry_data);
object_name = (const char *)entry_data;
Check(stream);
object_ID = stream->FindObjectID(object_name);
if (object_ID == NullObjectID)
{
std::cout << "PlugStream_FindEntryAndWriteObjectID - object_name == ";
std::cout << object_name << "\n";
Fail("PlugStream_FindEntryAndWriteObjectID - object_ID == NullObjectID");
}
Verify(object_ID != NullObjectID);
MemoryStream_Write(stream, &object_ID);
}