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>
This commit is contained in:
arcattack
2026-07-05 21:03:40 -05:00
co-authored by Claude Opus 4.8
commit 7b7d465e5e
4192 changed files with 604371 additions and 0 deletions
+637
View File
@@ -0,0 +1,637 @@
#include "munga.h"
#pragma hdrstop
#include "tool.h"
#include "fileutil.h"
#include "dropzone.h"
#include "camship.h"
#include "doorfram.h"
#include "eyecandy.h"
#include "explode.h"
#include "gaugalrm.h"
#include "cultural.h"
#include "scnrole.h"
#include "namelist.h"
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
ApplicationTool::CreateModelResource(
ModelData &model_resources,
const char *model_class,
ResourceFile *resource_file,
const char *model_name,
NotationFile *model_file,
const ResourceDirectories *directories
)
{
if (!stricmp(model_class,"CameraShipClassID"))
{
model_resources.subsystemResourceID =
CameraShip::CreateModelResource(
resource_file,
model_name,
model_file,
directories
);
}
else if (!stricmp(model_class,"DoorFrameClassID"))
{
model_resources.subsystemResourceID =
DoorFrame::CreateSubsystemStream(
resource_file,
model_name,
model_file,
directories
);
}
else if(!stricmp(model_class,"EyeCandyClassID"))
{
model_resources.subsystemResourceID =
EyeCandy::CreateModelResource(
resource_file,
model_name,
model_file,
directories
);
}
else if(!stricmp(model_class,"ExplosionClassID"))
{
model_resources.gameResourceID =
Explosion::CreateModelResource(
resource_file,
model_name,
model_file,
directories
);
}
else if (!stricmp(model_class, "CulturalIconClassID"))
{
model_resources.gameResourceID =
CulturalIcon::CreateModelResource(
resource_file,
model_name,
model_file,
directories
);
model_resources.damageZoneResourceID =
CulturalIcon::CreateDamageZoneStream(
resource_file,
model_name,
model_file,
directories
);
}
else if (!stricmp(model_class, "LandmarkClassID"))
{
model_resources.gameResourceID =
Landmark::CreateModelResource(
resource_file,
model_name,
model_file,
directories
);
model_resources.damageZoneResourceID =
Landmark::CreateDamageZoneStream(
resource_file,
model_name,
model_file,
directories
);
}
else if(!stricmp(model_class,"ScenarioRoleClassID"))
{
model_resources.gameResourceID =
ScenarioRole::CreateModelResource(
resource_file,
model_name,
model_file,
directories
);
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
ApplicationTool::WriteModels(
NotationFile *notation_file,
NotationFile *ops_file,
StreamableResourceFile *file,
const char *model_name,
const ResourceDirectories *resource_directories,
int resource_id
)
{
//
//------------------------------------------------
// If model is already in resource then we're done
//------------------------------------------------
//
ResourceDescription *res_description = file->FindResourceDescription(
model_name,
ResourceDescription::ModelListResourceType
);
if (res_description)
{
return True;
}
//
//----------------------------------------
// Initialize model data and ops file info
//----------------------------------------
//
ModelData model;
model.videoResourceID = -1;
model.audioResourceID = -1;
model.collisionID = -1;
model.gameResourceID = -1;
model.subsystemResourceID = -1;
model.controlsMappingsResourceID = -1;
model.skeletonResourceID = -1;
model.damageZoneResourceID = -1;
model.gaugeImageResourceID =-1;
model.gaugeAlarmResourceID =-1;
model.gaugeMissionReviewResourceID =-1;
model.explosionTableResourceID = -1;
const char *ops_name;
ops_file->GetEntry("resource", "res name", &ops_name);
//
//------------------------------------------------
// Find the geometry file and make an entry for it
//------------------------------------------------
//
model.videoResourceID =
platformTool->CreateModelVideoStreamResource(
file,
model_name,
notation_file,
resource_directories
);
//
//--------------------------------------------------
// Find the audio file and make and entry for it
//--------------------------------------------------
//
model.audioResourceID =
platformTool->CreateModelAudioStreamResource(
file,
model_name,
notation_file,
resource_directories
);
const char *model_class;
if (notation_file->GetEntry("gamedata","class",&model_class))
{
CreateModelResource(
model,
model_class,
file,
model_name,
notation_file,
resource_directories
);
}
//
//----------------------------------------------
// Find the animations and make entries for them
//----------------------------------------------
//
char animation_name[200];
if(notation_file->PageExists("animations"))
{
NameList *animationnames =
notation_file->MakeEntryList("animations", "animation");
Register_Object(animationnames);
NameList::Entry *animationentry = animationnames->GetFirstEntry();
while (animationentry)
{
strcpy(animation_name, animationentry->GetChar());
char *filename2 =
MakePathedFilename(
resource_directories->animationDirectory,
animation_name
);
Register_Pointer(filename2);
if(verboseMode)
{
Tell("Opening Animation : " << filename2 << std::endl);
}
NotationFile *new_notation_file = new NotationFile(filename2);
Register_Object(new_notation_file);
labOnly |= new_notation_file->IsMarkedLabOnly();
const char *anim_p = StripExtension(StripDirectory(animation_name));
const char *temp_pointer = animationentry->GetName();
if (new_notation_file->PageCount())
{
if(strstr(temp_pointer, "transition"))
{
if (verboseMode)
{
std::cout << "Type Transition" << std::endl;
}
if (!WriteAnimationTransition(new_notation_file, file, anim_p))
{
std::cout << "Error - Animation did not work : " << filename2 << std::endl;
}
}
else
{
if(verboseMode)
{
std::cout << "Type Cycle" << std::endl;
}
if (!WriteAnimationCycle(new_notation_file, file, anim_p))
{
std::cout << "Error - Animation did not work : " << filename2 << std::endl;
}
}
}
else
{
std::cout << filename2 << " does not exist " << std::endl;
}
Unregister_Pointer(filename2);
delete filename2;
Unregister_Object(new_notation_file);
delete new_notation_file;
animationentry = animationentry->GetNextEntry();
}
Unregister_Object(animationnames);
delete animationnames;
}
//
//--------------------------------------------------
// Find the collision file and make and entry for it
//--------------------------------------------------
//
const char *entry_data;
if (notation_file->GetEntry("collision","name",&entry_data))
{
Check_Pointer(entry_data);
Logical convert_boxes = False;
const char *dummy;
if (notation_file->GetEntry("collision","convertboxes",&dummy))
{
convert_boxes = True;
}
else if (!stricmp(model_class, "LandmarkClassID"))
{
convert_boxes = True;
}
else if(!stricmp(model_class,"CulturalIconClassID"))
{
convert_boxes = True;
}
model.collisionID =
BoxedSolidResource::CreateBoxedSolidStream(
entry_data,
file,
resource_directories,
convert_boxes
);
}
//
//-------------------------------------------------------------------
// Find the controls mapping page and build a list of all the control
// mappings there
//-------------------------------------------------------------------
//
if (notation_file->PageExists("ControlMappings"))
{
NameList *mapping_list = notation_file->MakeEntryList("ControlMappings");
Register_Object(mapping_list);
NameList::Entry *entry = mapping_list->GetFirstEntry();
//
//-----------------------------------------------------------------------
// Count the number of mapping files, and build a list big enough to hold
// all of the mappings
//-----------------------------------------------------------------------
//
int count=0;
while (entry)
{
++count;
entry = entry->GetNextEntry();
}
if (!count)
{
Unregister_Object(mapping_list);
delete mapping_list;
goto Make_Directory;
}
ResourceDescription::ResourceID *mappings =
new ResourceDescription::ResourceID[count];
Register_Pointer(mappings);
//
//-------------------------------------------------------------------
// Step through the list, reading each of the specified control files
//-------------------------------------------------------------------
//
entry = mapping_list->GetFirstEntry();
count = 0;
while (entry)
{
mappings[count] = -1;
char *ctl_filename =
MakePathedFilename(
resource_directories->modelDirectory,
entry->GetChar()
);
Register_Pointer(ctl_filename);
NotationFile *ctl_file = new NotationFile(ctl_filename);
Register_Object(ctl_file);
if (!ctl_file->PageCount())
{
std::cout << model_name << " unable to open " << ctl_filename << "!\n";
Unwind_1:
Unregister_Object(ctl_file);
delete ctl_file;
Unregister_Pointer(ctl_filename);
delete[] ctl_filename;
Unregister_Pointer(mappings);
delete[] mappings;
Unregister_Object(mapping_list);
delete mapping_list;
goto Make_Directory;
}
//
//-----------------------------
// Find out which model to call
//-----------------------------
//
mappings[count] =
CreateControlMappingStream(
model_class,
entry->GetName(),
ctl_file,
file,
model_name,
notation_file,
resource_directories,
platformTool
);
if (mappings[count] == -1)
{
std::cout << model_name << " unable to process " << entry->GetName()
<< " control mapping!\n";
goto Unwind_1;
}
Unregister_Object(ctl_file);
delete ctl_file;
Unregister_Pointer(ctl_filename);
delete[] ctl_filename;
++count;
entry = entry->GetNextEntry();
}
res_description = file->AddResourceList(
model_name,
ResourceDescription::ControlMappingsListResourceType,
1,
ResourceDescription::LoadOnDemandFlag,
mappings,
count
);
Verify(res_description);
model.controlsMappingsResourceID = res_description->resourceID;
Unregister_Object(mapping_list);
Unregister_Pointer(mappings);
delete mapping_list;
delete[] mappings;
}
//
//-------------------------------------------------------------------
// Find the gaugeMissionReview page and save the pertinent data
//-------------------------------------------------------------------
//
model.gaugeMissionReviewResourceID =
platformTool->CreateModelGaugeMissionReviewStreamResource(
file,
model_name,
notation_file,
resource_directories
);
//
//-----------------------------------------------
// Find GaugeImage File and make an entry for it
//-----------------------------------------------
//
model.gaugeImageResourceID =
platformTool->CreateModelGaugeImageStreamResource(
file,
model_name,
notation_file,
resource_directories
);
//
//------------------------------------------------
// Find Gauge alarm file and make an entry for it
//------------------------------------------------
//
{
GaugeAlarmManager
*gaugeAlarmManager = platformTool->gaugeAlarmManager;
if (gaugeAlarmManager == NULL)
{
model.gaugeAlarmResourceID = -1;
}
else
{
Check(gaugeAlarmManager);
model.gaugeAlarmResourceID =
gaugeAlarmManager->CreateModelGaugeAlarmStreamResource(
file,
model_name,
notation_file,
resource_directories
);
}
}
//
//-------------------------------------------
// Make a directory for the model information
//-------------------------------------------
//
Make_Directory:
int *model_data = new int[20];
Register_Pointer(model_data);
int i = 0;
if (model.videoResourceID >= 0)
{
model_data[i++] = model.videoResourceID;
}
if (model.audioResourceID >= 0)
{
model_data[i++] = model.audioResourceID;
}
if (model.collisionID >= 0)
{
model_data[i++] = model.collisionID;
}
if (model.gameResourceID >= 0)
{
model_data[i++] = model.gameResourceID;
}
if (model.subsystemResourceID >= 0)
{
model_data[i++] = model.subsystemResourceID;
}
if (model.skeletonResourceID >= 0)
{
model_data[i++] = model.skeletonResourceID;
}
if (model.controlsMappingsResourceID >= 0)
{
model_data[i++] = model.controlsMappingsResourceID;
}
if (model.damageZoneResourceID >= 0)
{
model_data[i++] = model.damageZoneResourceID;
}
if (model.gaugeImageResourceID >= 0)
{
model_data[i++] = model.gaugeImageResourceID;
}
if (model.gaugeAlarmResourceID >= 0)
{
model_data[i++] = model.gaugeAlarmResourceID;
}
if (model.gaugeMissionReviewResourceID >= 0)
{
model_data[i++] = model.gaugeMissionReviewResourceID;
}
if (model.explosionTableResourceID >= 0)
{
model_data[i++] = model.explosionTableResourceID;
}
res_description =
file->FindResourceDescription(
model_name,
ResourceDescription::ModelListResourceType
);
if (!res_description)
{
if (i > 0)
{
if (resource_id == -1)
{
file->AddResourceList(
model_name,
ResourceDescription::ModelListResourceType,
1,
ResourceDescription::Preload,
model_data,
i
);
}
else
{
ResourceDescription
*res_des = file->FindResourceDescription(resource_id);
if (!res_des)
{
file->AddResourceList(
model_name,
ResourceDescription::ModelListResourceType,
1,
ResourceDescription::Preload,
model_data,
i,
resource_id
);
}
else
{
DEBUG_STREAM << model_name << " : " << resource_id << std::endl << std::flush;
Fail("Duplicate Resource Number");
}
}
}
else
{
if (resource_id == -1)
{
file->AddResource(
model_name,
ResourceDescription::ModelListResourceType,
1,
ResourceDescription::Preload,
&i,
sizeof(i)
);
}
else
{
ResourceDescription
*res_des = file->FindResourceDescription(resource_id);
if (!res_des)
{
file->AddResource(
model_name,
ResourceDescription::ModelListResourceType,
1,
ResourceDescription::Preload,
&i,
sizeof(i),
resource_id
);
}
else
{
DEBUG_STREAM << model_name << " : " << resource_id << std::endl << std::flush;
Fail("Duplicate Resource Number");
}
}
}
}
Unregister_Pointer(model_data);
delete model_data;
return True;
}