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:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user