//===========================================================================// // File: btl4app.cpp // // Project: BattleTech Brick: BattleTech LBE Application // // Contents: BTL4Application -- the top-level LBE-mode application object. // // Builds the registry, renderers, mode/controls managers, the // // mission, and the player's viewpoint entity; drives the run / // // stop mission state. // //---------------------------------------------------------------------------// // Date Who Modification // // -------- --- ---------------------------------------------------------- // // // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved // // PROPRIETARY AND CONFIDENTIAL // //===========================================================================// // // RECONSTRUCTED from the shipped binary (BTL4OPT.EXE). The class declaration // is taken VERBATIM from the surviving header BTL4APP.HPP; the method bodies // are recovered from the Ghidra pseudo-C for the 0x4d34xx-0x4d3axx cluster // (see all/part_014.c) and cross-checked against Red Planet's direct analog // RP_L4\RPL4APP.cpp and the MUNGA base MUNGA\APP.cpp. // // The 12 functions of this object file map 1:1 onto the 12 methods declared // in BTL4APP.HPP, in source (compiler-emission) order: // // @004d34c4 BTL4Application::BTL4Application (constructor) // @004d350c BTL4Application::~BTL4Application (scalar-deleting dtor) // @004d3538 BTL4Application::MakeRegistry // @004d3564 BTL4Application::MakeVideoRenderer // @004d35b0 BTL4Application::MakeAudioRenderer // @004d3614 BTL4Application::MakeModeManager // @004d3644 BTL4Application::MakeControlsManager // @004d368c BTL4Application::MakeGaugeRenderer // @004d36c4 BTL4Application::MakeMission // @004d36f4 BTL4Application::MakeViewpointEntity (anchor; 822 bytes) // @004d3a2c BTL4Application::RunMissionMessageHandler // @004d3a94 BTL4Application::StopMissionMessageHandler // // Helper / engine-internal name mapping used below (FUN_ -> intent): // FUN_0047b410 L4Application::L4Application (base ctor) // FUN_0047b624 L4Application::~L4Application (base dtor) // FUN_0047b788 L4Application::RunMissionMessageHandler (base) // FUN_0047b864 L4Application::StopMissionMessageHandler (base) // FUN_00402298 operator new / Mem_Alloc(size) // FUN_004022d0 operator delete / Mem_Free(p) // FUN_004c1548 BTRegistry::BTRegistry (0x10 bytes) // FUN_004ceecc BTL4VideoRenderer::BTL4VideoRenderer (0x3e0 bytes) // FUN_004d2b6c BTL4AudioRenderer::BTL4AudioRenderer (0x1b0 bytes) // FUN_0044c2d0 ModeManager init helper (BTL4ModeManager, 0xc bytes) // FUN_00475c18 L4ControlsManager::L4ControlsManager (0xd10 bytes) // FUN_004cbea0 BTL4GaugeRenderer::BTL4GaugeRenderer (0x1c53c bytes) // FUN_004d2be0 BTL4Mission::BTL4Mission (0xfc bytes) // FUN_004dee74 getenv() // FUN_0044e19c GetSampleRate() (reads division-params float @+0x10) // FUN_0042d16c CameraShip::Make // FUN_004a2d48 Mech::Make // FUN_00476eac ControlsManager::SetJoystickDeadBand // FUN_00476eec ControlsManager::SetThrottleDeadBand // FUN_00476f10 ControlsManager::SetPedalsDeadBand // FUN_0047ac5c CameraThrustmasterMapper::CameraThrustmasterMapper // FUN_0047ad3c CameraRIOMapper::CameraRIOMapper // FUN_0047aa78 CameraL4Mapper::CameraL4Mapper // FUN_0042c630 CameraShip::SetMappingSubsystem // FUN_004d21d0 MechThrustmasterMapper::MechThrustmasterMapper // FUN_004d266c MechRIOMapper::MechRIOMapper // FUN_00402460 (controls-mapper resource ctor / Str_Copy "ControlsMapper") // FUN_0049fe40 Mech::SetMappingSubsystem // FUN_00407064 ResourceFile::SearchList(id, type) // FUN_004070dc ResourceFile::SearchList(id, name) // FUN_00406cd0 ResourceDescription::Lock // FUN_0047703c ControlsManager::CreateStreamedMappings // FUN_0046ff64 GaugeRenderer::ConfigureForModel(name, entity) // FUN_004dbb24 DEBUG_STREAM << (msg) FUN_004d9c38 << flush // (the two together == the Tell()/Msg() macro) // FUN_0040385c Fail(msg, file, line) // DAT_004efc94 the global application pointer // DAT_00524e20 the debug message stream // // L4Application base member offsets observed in the decomp: // this+0x3c controls manager (GetControlsManager) // this+0x4c gauge renderer (GetGaugeRenderer) // this+0x50 mode manager (GetModeManager) // this+0x68 resource file (GetResourceFile) // this+0xd0 division-params present flag (cf. RP "divisionParameters") // controlsManager+0xc98 primaryControlType (2==ThrustMaster, 4==RIO) // modeManager+0x4 current mode mask modeManager+0x8 saved mode mask // #include #pragma hdrstop #if !defined(BTL4APP_HPP) # include #endif #if !defined(BTL4VID_HPP) # include #endif #if !defined(BTL4ARND_HPP) # include #endif #if !defined(BTL4GRND_HPP) # include #endif #if !defined(BTL4MSSN_HPP) # include #endif #if !defined(BTL4MODE_HPP) # include #endif #if !defined(BTL4MPPR_HPP) # include #endif #if !defined(BTREG_HPP) # include #endif #if !defined(L4CTRL_HPP) # include // LBE4ControlsManager #endif #if !defined(CAMSHIP_HPP) # include // CameraShip #endif #if !defined(CAMMPPR_HPP) # include // CameraControlsMapper #endif #if !defined(L4MPPR_HPP) # include // Camera{Thrustmaster,RIO,L4}Mapper #endif // // Audio sound-card environment variables (both must be present for the // audio renderer to be built). Observed at @004d35b0 as the live form of // the front/rear-card check that RPL4Application left commented out. // #define AWE_FRONT_ENV_VAR "AWE_FRONT" // s_AWE_FRONT_0051ec1e #define AWE_REAR_ENV_VAR "AWE_REAR" // s_AWE_REAR_0051ec28 //############################################################################# // Message Support // const Receiver::HandlerEntry BTL4Application::MessageHandlerEntries[]= { MESSAGE_ENTRY(BTL4Application, RunMission), MESSAGE_ENTRY(BTL4Application, StopMission) }; BTL4Application::MessageHandlerSet BTL4Application::MessageHandlers( ELEMENTS(BTL4Application::MessageHandlerEntries), BTL4Application::MessageHandlerEntries, L4Application::GetMessageHandlers() // WinTesla: GetMessageHandlers() replaces the static MessageHandlers ); //############################################################################# // Shared Data Support // Derivation BTL4Application::ClassDerivations( L4Application::GetClassDerivations(), // WinTesla: GetClassDerivations() (returns Derivation*) "BTL4Application" ); BTL4Application::SharedData BTL4Application::DefaultData( &BTL4Application::ClassDerivations, // SharedData(Derivation*, MessageHandlerSet&) BTL4Application::MessageHandlers ); // //############################################################################# // BTL4Application @004d34c4 //############################################################################# // BTL4Application::BTL4Application( HINSTANCE hInstance, HWND hWnd, ResourceFile *resource_file, ClassID class_ID, SharedData &shared_data ): // // Base call FUN_0047b410(this, resource_file, 1, class_ID, shared_data). // The literal "1" is the L4 game identifier (BTL4) -- the analog of // RPL4Application passing the RPL4 enum to L4Application. The WinTesla // L4Application ctor additionally takes (hInstance, hWnd) up front // (cf. RPL4Application::RPL4Application). // L4Application(hInstance, hWnd, resource_file, BTL4, class_ID, shared_data) { // vtable installed by the compiler == &PTR_FUN_0051ed9c Tell("BTL4Application::BTL4Application\n"); // FUN_004dbb24/004d9c38 } // //############################################################################# // ~BTL4Application @004d350c //############################################################################# // BTL4Application::~BTL4Application() { // FUN_004d350c is the compiler-generated scalar-deleting destructor; // the body is empty -- it merely reinstalls the vtable and chains to // ~L4Application (FUN_0047b624). } // //############################################################################# // MakeRegistry @004d3538 //############################################################################# // Registry* BTL4Application::MakeRegistry() { Check(this); return new BTRegistry(GetResourceFile()); // FUN_00402298(0x10) -> FUN_004c1548 } // //############################################################################# // MakeVideoRenderer @004d3564 //############################################################################# // VideoRenderer* BTL4Application::MakeVideoRenderer() { // // Only build the renderer when the division parameters are present // (this+0xd0 != 0), exactly as RPL4Application gates on // "divisionParameters". // // The DOS-era "division parameters present" flag (this+0xd0) is the // WinTesla public char* L4Application::divisionParameters. The // reconstructed BTL4VideoRenderer ctor (btl4vid.hpp) takes the standard // Renderer calibration tuple (rate, complexity, priority) followed by // the interest type/depth; it derives the window + screen geometry from // the L4Application statics internally. // if (divisionParameters) { return new BTL4VideoRenderer( DefaultRendererRate, // calibration rate MaxRendererComplexity, // calibration complexity DefaultRendererPriority, // calibration priority VisualInterestType, // DAT_004e8238 DefaultInterestDepth // DAT_004e823c ); } return NULL; } // //############################################################################# // MakeAudioRenderer @004d35b0 //############################################################################# // AudioRenderer* BTL4Application::MakeAudioRenderer() { // // Both AWE sound-card environment variables must be present. This is // the live form of the front/rear-card test RPL4Application left // commented out (blaster1/blaster2). // char *front_card = getenv(AWE_FRONT_ENV_VAR); // FUN_004dee74 char *rear_card = getenv(AWE_REAR_ENV_VAR); if (front_card == NULL || rear_card == NULL) { return NULL; } // // FUN_0044e19c read the sample rate as a float out of the division // parameter block (@+0x10). In WinTesla that block is the public // L4Application::divisionParameters char*; fall back to 1000.0f (the // RP default) when it is absent. Second arg is the mission-review // flag (the 1995 binary hard-coded it off; DAT_004fd550 == 0). // RendererRate sample_rate = divisionParameters ? (RendererRate)*reinterpret_cast(divisionParameters + 0x10) : (RendererRate)1000.0f; return new BTL4AudioRenderer( sample_rate, False ); } // //############################################################################# // MakeModeManager @004d3614 //############################################################################# // ModeManager* BTL4Application::MakeModeManager() { // FUN_00402298(0xc) -> FUN_0044c2d0(p, 0x50421) BTL4ModeManager *manager = new BTL4ModeManager(BTL4ModeManager::ModeInitial); Check(manager); return manager; } // //############################################################################# // MakeControlsManager @004d3644 //############################################################################# // ControlsManager* BTL4Application::MakeControlsManager() { // FUN_00402298(0xd10) -> FUN_00475c18(p) // WinTesla renamed the DOS L4ControlsManager to LBE4ControlsManager. LBE4ControlsManager *manager = new LBE4ControlsManager; Check(manager); // // Register this application as the controls owner / key-command // handler. The DOS decomp showed a virtual call // (manager+0x68)->vtbl[+0x14](-1, this, 7, this) -- "mode mask = all, // receiver = this, message = KeyCommand (7), handler = this". In // WinTesla that is the keyboard-group Add() the base // L4Application::MakeControlsManager performs. // manager->keyboardGroup[LBE4ControlsManager::KeyboardPC].Add( ModeManager::ModeAlwaysActive, this, KeyCommandMessageID, this ); return manager; } // //############################################################################# // MakeGaugeRenderer @004d368c //############################################################################# // GaugeRenderer* BTL4Application::MakeGaugeRenderer(int *secondaryIndex, int *aux1Index, int *aux2Index) { // // Only build the gauge renderer when the L4GAUGE environment variable // is set -- matches RPL4Application's getenv("L4GAUGE") gate. // // SIGNATURE NOTE: the shipped BT override (@004d368c) took no arguments, // but the 2007 WinTesla engine WIDENED the base virtual to // Application::MakeGaugeRenderer(int* secondaryIndex, int* aux1, int* aux2) // (the secondary/aux surface indices for windowed mode) and calls THAT // 3-arg virtual from Application::Initialize (APP.cpp:382). A no-arg // method here only HIDES the virtual -- it never overrides it -- so the // engine's L4Application::MakeGaugeRenderer ran instead and built a BASE // L4GaugeRenderer (which never parses gauge\l4gauge.cfg -> empty symbol // table -> "undefined label 'bhk1Init'" -> no ports). Matching the 3-arg // signature makes this the real override. BT is fullscreen with no // secondary/aux gauge indices, so the args are ignored (the // BTL4GaugeRenderer ctor hardcodes windowed=false / NULL indices). // (void)secondaryIndex; (void)aux1Index; (void)aux2Index; if (getenv("L4GAUGE") == NULL) // FUN_004dee74 { return NULL; } return new BTL4GaugeRenderer; // FUN_00402298(0x1c53c) -> FUN_004cbea0 } // //############################################################################# // MakeMission @004d36c4 //############################################################################# // Mission* BTL4Application::MakeMission( NotationFile *notation_file, ResourceFile *resources ) { // FUN_00402298(0xfc) -> FUN_004d2be0(p, notation_file, resources) return new BTL4Mission(notation_file, resources); } // //############################################################################# // MakeViewpointEntity @004d36f4 //############################################################################# // // Anchor function (822 bytes). Creates the viewpoint entity for the local // player and wires up its controls mapper + gauges. Structurally identical // to RPL4Application::MakeViewpointEntity, except the second case creates a // Mech (class 0xbb9) instead of a VTV. // Entity* BTL4Application::MakeViewpointEntity(Entity::MakeMessage *message) { Check(this); // WinTesla: GetControlsManager() yields the LBE4ControlsManager // (the renamed DOS L4ControlsManager); the deadband / primary-control // accessors live on it, not on the ControlsManager base. LBE4ControlsManager *controls = (LBE4ControlsManager*)GetControlsManager(); // *(this+0x3c) Entity *viewing_entity = NULL; char *primary_mapping_name = NULL; switch (message->classToCreate) // *(message+0x1c) { case CameraShipClassID: // 0x45 //=============================================================== // Create Camera ship //=============================================================== { CameraShip *viewing_camera = CameraShip::Make((CameraShip::MakeMessage*)message); Check(viewing_camera); viewing_entity = viewing_camera; //------------------------------------------------------------ // Create the controls-mapping subsystem resource //------------------------------------------------------------ CameraControlsMapper::SubsystemResource control_subsystem_resource; Str_Copy( control_subsystem_resource.subsystemName, "ControlsMapper", sizeof(control_subsystem_resource.subsystemName) ); control_subsystem_resource.classID = TrivialSubsystemClassID; // 0xf control_subsystem_resource.subsystemModelSize = // 0x30 sizeof(control_subsystem_resource); CameraControlsMapper *camera_mapper; switch (controls->primaryControlType) // *(controls+0xc98) { case LBE4ControlsManager::PrimaryThrustMaster: // 2 camera_mapper = new CameraThrustmasterMapper( // FUN_0047ac5c viewing_camera, CameraShip::ControlsMapperSubsystem, &control_subsystem_resource ); primary_mapping_name = "Thrustmaster"; break; case LBE4ControlsManager::PrimaryRIO: // 4 camera_mapper = new CameraRIOMapper( // FUN_0047ad3c viewing_camera, CameraShip::ControlsMapperSubsystem, &control_subsystem_resource ); primary_mapping_name = "L4"; break; default: camera_mapper = new CameraL4Mapper( // FUN_0047aa78 viewing_camera, CameraShip::ControlsMapperSubsystem, &control_subsystem_resource ); // primary mapping name is NULL break; } //------------------------------------------------------------ // Attach the mapping subsystem to the entity //------------------------------------------------------------ viewing_camera->SetMappingSubsystem(camera_mapper); // FUN_0042c630 } break; case MechClassID: // 0xbb9 //=============================================================== // Create Mech (the BattleTech analog of RP's VTV case) //=============================================================== { Mech *viewing_mech = Mech::Make((Mech::MakeMessage*)message); // FUN_004a2d48 Check(viewing_mech); viewing_entity = viewing_mech; //------------------------------------------------------------ // Control deadbanding //------------------------------------------------------------ controls->SetJoystickDeadBand(0.05f); // FUN_00476eac controls->SetThrottleDeadBand(0.05f); // FUN_00476eec controls->SetPedalsDeadBand(0.05f); // FUN_00476f10 MechControlsMapper *mech_mapper; switch (controls->primaryControlType) // *(controls+0xc98) { case LBE4ControlsManager::PrimaryThrustMaster: // 2 { // // The DOS binary heap-allocated the subsystem resource // and ref-counted it (FUN_00402460 + puVar2[3]++). The // WinTesla Subsystem__SubsystemResource is a plain struct // that the Subsystem ctor copies out of, so we build it // on the stack and name it via Str_Copy -- the exact // idiom RP uses for its VTV/Camera mapper resources. // MechControlsMapper::SubsystemResource control_subsystem_resource; Str_Copy( control_subsystem_resource.subsystemName, "ControlsMapper", sizeof(control_subsystem_resource.subsystemName) ); control_subsystem_resource.classID = TrivialSubsystemClassID; control_subsystem_resource.subsystemModelSize = sizeof(control_subsystem_resource); mech_mapper = new MechThrustmasterMapper( // FUN_004d21d0 viewing_mech, 0, &control_subsystem_resource, (ClassID)MechControlsMapperClassID, // 0x7dc MechThrustmasterMapper::DefaultData // &DAT_0051dc68 ); } primary_mapping_name = "Thrustmaster"; break; case LBE4ControlsManager::PrimaryRIO: // 4 { MechControlsMapper::SubsystemResource control_subsystem_resource; Str_Copy( control_subsystem_resource.subsystemName, "ControlsMapper", sizeof(control_subsystem_resource.subsystemName) ); control_subsystem_resource.classID = TrivialSubsystemClassID; control_subsystem_resource.subsystemModelSize = sizeof(control_subsystem_resource); mech_mapper = new MechRIOMapper( // FUN_004d266c viewing_mech, 0, &control_subsystem_resource, (ClassID)MechControlsMapperClassID, // 0x7dc MechRIOMapper::DefaultData // &DAT_0051dcf0 ); } primary_mapping_name = "L4"; break; default: // // TODO(bring-up): the shipped pod binary Fail()s here when no // RIO/Thrustmaster is detected (primaryControlType==None on a // dev box with no joystick). To let the spawn pipeline finish // and the mech reach the render/frame loop, fall back to the // RIO mapper (the pod's real L4 device). Keyboard/gamepad // remap is a separate pod-bring-up task (CLAUDE.md Phase 8). // DEBUG_STREAM << "*****Mech has no controls mapping -- bring-up RIO fallback*****"; { MechControlsMapper::SubsystemResource control_subsystem_resource; Str_Copy( control_subsystem_resource.subsystemName, "ControlsMapper", sizeof(control_subsystem_resource.subsystemName) ); control_subsystem_resource.classID = TrivialSubsystemClassID; control_subsystem_resource.subsystemModelSize = sizeof(control_subsystem_resource); mech_mapper = new MechRIOMapper( viewing_mech, 0, &control_subsystem_resource, (ClassID)MechControlsMapperClassID, MechRIOMapper::DefaultData ); } primary_mapping_name = "L4"; break; } //------------------------------------------------------------ // Attach the mapping subsystem and enable collision //------------------------------------------------------------ viewing_mech->SetMappingSubsystem(mech_mapper); // FUN_0049fe40 viewing_mech->StartCollisionAssistant(); // vtbl[+0x38] } break; } //------------------------------------------------------------------- // Load the control-mapping resource stream for the entity //------------------------------------------------------------------- if (primary_mapping_name != NULL) { ResourceDescription *resource_description = application->GetResourceFile()->SearchList( // *(DAT_004efc94+0x68) viewing_entity->GetResourceID(), // piVar5[0x6f] ResourceDescription::ControlMappingsListResourceType // 6 ); if (resource_description != NULL) { resource_description = GetResourceFile()->SearchList( // *(this+0x68) resource_description->resourceID, primary_mapping_name ); if (resource_description != NULL) { resource_description->Lock(); // FUN_00406cd0 DEBUG_STREAM << "[ctrlmap] installing '" << primary_mapping_name << "' table: " << *(int *)resource_description->resourceAddress << " mappings on entity " << (void *)viewing_entity << std::endl; controls->CreateStreamedMappings( // FUN_0047703c viewing_entity, (int*)resource_description->resourceAddress ); resource_description->Unlock(); } else { DEBUG_STREAM << "[ctrlmap] NO '" << primary_mapping_name << "' sub-table for this mech" << std::endl; } } } //------------------------------------------------------------------- // Configure the gauges for this entity //------------------------------------------------------------------- BTL4GaugeRenderer *gauge_renderer = (BTL4GaugeRenderer*)GetGaugeRenderer(); // *(this+0x4c) if (getenv("BT_VSS_LOG")) DEBUG_STREAM << "[vss] MakeViewpointEntity: gaugeRenderer=" << (int)(gauge_renderer != NULL) << " -> ConfigureForModel(Init)\n" << std::flush; if (gauge_renderer != NULL) { gauge_renderer->ConfigureForModel("Init", viewing_entity); // FUN_0046ff64 } return viewing_entity; } // //############################################################################# // RunMissionMessageHandler @004d3a2c //############################################################################# // void BTL4Application::RunMissionMessageHandler(RunMissionMessage *message) { Tell("BTL4Application::RunMissionMessageHandler\n"); // FUN_004dbb24/004d9c38 // // Turn on the plasma score display: save the current mode mask and // set the ModePlasmaDisplay bit (1<<22 == 0x400000) on the mode // manager (this+0x50). // BTL4ModeManager *mode_manager = (BTL4ModeManager*)GetModeManager(); // *(this+0x50) // WinTesla ModeManager exposes the mask through AddModeMask(), which // stashes the old mask into previousModeMask (== the DOS "savedMode" // = "currentMode" step) and ORs in the new bits (== "currentMode |="). mode_manager->AddModeMask(BTL4ModeManager::ModePlasmaDisplay); // |= 0x400000 Tell("Turning Plasma Score Display On\n"); L4Application::RunMissionMessageHandler(message); // FUN_0047b788 } // //############################################################################# // StopMissionMessageHandler @004d3a94 //############################################################################# // void BTL4Application::StopMissionMessageHandler(StopMissionMessage *message) { // // Turn off the plasma score display: save the current mode mask and // clear the ModePlasmaDisplay bit (~0x400000 == 0xffbfffff). // BTL4ModeManager *mode_manager = (BTL4ModeManager*)GetModeManager(); // *(this+0x50) // RemoveModeMask() = stash old mask into previousModeMask (the DOS // "savedMode = currentMode") then clear the bits ("currentMode &= ~"). mode_manager->RemoveModeMask(BTL4ModeManager::ModePlasmaDisplay); // &= 0xffbfffff L4Application::StopMissionMessageHandler(message); // FUN_0047b864 Tell("Turning Plasma Score Display Off\n"); }