//===========================================================================// // File: thermalsight.cpp // // Project: BattleTech // // Contents: ThermalSight (thermal / infra-red vision mode) subsystem // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved // // PROPRIETARY AND CONFIDENTIAL // //===========================================================================// // // RECONSTRUCTED from the shipped binary (Ghidra pseudo-C, recovered shard // part_013.c). Cluster @004b8718..@004b881c. Each method cites its @ADDR. // // Helper-function name mapping: // FUN_004b18a4 PowerWatcher base constructor (powersub.cpp) // FUN_004b198c PowerWatcher::CreateStreamedSubsystem (powersub.cpp) // FUN_004b181c PowerWatcher base per-frame update (powersub.cpp) // FUN_004b179c PowerWatcher::HandleMessage (slot 9) (powersub.cpp) // FUN_004b1804 PowerWatcher::ResetToInitialState (slot10) (powersub.cpp) // FUN_004b1930 ~PowerWatcher (powersub.cpp) // FUN_0041b9ec AlarmIndicator(levels) // FUN_0041baa4 ~AlarmIndicator // FUN_0041bbd8 AlarmIndicator::SetLevel(n) // FUN_0041a1a4 IsDerivedFrom(classDerivations) // FUN_0045fe44 ToggleGlobalThermalVision (flips _DAT_004f1ab8 "pvision", // prints "pvision ON/OFF", re-applies the view palette) // FUN_004022d0 operator delete / global free // PTR_DAT_004fd4b8 the active L4 application/viewport (its +0x48 flag is // non-zero only when THIS mech is the locally-viewed mech) // #include #pragma hdrstop #if !defined(THERMALSIGHT_HPP) # include #endif #if !defined(APP_HPP) # include #endif #if !defined(TESTBT_HPP) # include #endif //########################################################################### // Shared Data Support // Derivation ThermalSight::ClassDerivations( // @00511238 PowerWatcher::GetClassDerivations(), "ThermalSight" ); Receiver::MessageHandlerSet ThermalSight::MessageHandlers; // task #16: publish the binary attr table @00511270 -- one entry, // {LightState (id 4) -> thermalActive @0x1D8} (offset encoded 0x1D9 = 0x1D8|1). // Function-local statics per the static-init-order rule. ThermalSight::AttributeIndexSet& ThermalSight::GetAttributeIndex() { static const ThermalSight::IndexEntry entries[]= { ATTRIBUTE_ENTRY(ThermalSight, LightState, thermalActive) // id 4 @0x1D8 }; static ThermalSight::AttributeIndexSet attributeIndex( ELEMENTS(entries), entries, PowerWatcher::GetAttributeIndex() ); return attributeIndex; } ThermalSight::SharedData ThermalSight::DefaultData( // @00511228 &ThermalSight::ClassDerivations, ThermalSight::MessageHandlers, ThermalSight::GetAttributeIndex(), ThermalSight::StateCount ); //########################################################################### // Construction -- @004b8718 // // Chains to the PowerWatcher ctor (FUN_004b18a4) with &DAT_00511228, installs // the ThermalSight vtable (PTR_FUN_00511498), builds the 2-level state // AlarmIndicator (@0x1E0). When this is a live (non-copy) segment with the // has-performance flag set it registers ThermalSightSimulation // (this[7..9] <- PTR_FUN_00511290). // ThermalSight::ThermalSight( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data ): PowerWatcher(owner, subsystem_ID, subsystem_resource, shared_data) { Check(owner); Check_Pointer(subsystem_resource); // (WAVE 4 de-shim) the 4 cross-family shim fields are gone; the accessors // read the inherited base state directly. Only the own fields init here. thermalActive = 0; // @0x1D8 requestedOn = 0; // @0x1DC stateAlarm.Initialize(2); // FUN_0041b9ec(this+0x1E0, 2) // GATE FIX (the functional bug the de-shim unblocks): the binary reads OWNER // simulationFlags (param_2+0x28, raw part_013.c:6140), NOT the old shadow // `segmentFlags` (seeded 0 -> gate always FALSE -> Performance never // installed). No else branch (matches the binary; unlike Searchlight/Torso). if (((owner->simulationFlags & 0xC) == 0) && ((owner->simulationFlags & 0x100) != 0)) { SetPerformance(&ThermalSight::ThermalSightSimulation); // this[7..9] <- PTR_FUN_00511290 } Check_Fpu(); } // // Destruction -- @004b8794 (vtable slot0). Reinstalls the vtable, tears down // the AlarmIndicator (@0x1E0), chains to ~PowerWatcher (FUN_004b1930), frees // the block when the deleting-dtor bit is set. // ThermalSight::~ThermalSight() { Check(this); stateAlarm.Finalize(); // FUN_0041baa4(this+0x1E0, 2) Check_Fpu(); } //########################################################################### // TestInstance -- @004b881c // Logical ThermalSight::TestInstance() const { return IsDerivedFrom(ClassDerivations); // FUN_0041a1a4(**this[3], 0x00511238) } Logical ThermalSight::TestClass(Mech &) { return True; // BEST-EFFORT (family convention) } //########################################################################### // ThermalSightSimulation -- @004b8648 (Performance) // // Same power/heat gating as the Searchlight, but the watched quantity is the // thermal-vision mode, and when the active state CHANGES on the locally-viewed // mech it toggles the global "pvision" render flag: // * base tick (FUN_004b181c). // * thermalActive follows requestedOn (@0x1DC) unless the host is shut down // (this[0x40] == 1), in which case it is forced off. // * the watched-voltage alarm level (@0x198) must read Ready(4); else blank. // * heat-state level (@0x140): <2 keeps it, ==2 blanks it. // * push on/off into the AlarmIndicator (@0x1E0). // * if the state changed AND this mech is the one being viewed // (PTR_DAT_004fd4b8 -> +0x48 != 0), call ToggleGlobalThermalVision // (FUN_0045fe44) to flip the cockpit IR view. // void ThermalSight::ThermalSightSimulation(Scalar time_slice) { Check(this); PowerWatcher::Simulation(time_slice); // FUN_004b181c (base per-frame update) int previous = thermalActive; // @0x1D8 thermalActive = (HostShutDown()) ? 0 : requestedOn; // this[0x40]==1 ? 0 : @0x1DC if (WatchedVoltageLevel() == 4) // @0x198 == Ready thermalActive = (thermalActive != 0); else thermalActive = 0; if (HeatStateLevel() < 2) // @0x140 thermalActive = (thermalActive != 0); else if (HeatStateLevel() == 2) thermalActive = 0; stateAlarm.SetLevel(thermalActive); // FUN_0041bbd8(this+0x1E0, thermalActive) if ((previous != thermalActive) && IsLocallyViewed()) // was LocalViewport()->viewingThisMech { ToggleGlobalThermalVision(); // FUN_0045fe44 -- bring-up no-op (pvision unported) } Check_Fpu(); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // CreateStreamedSubsystem -- ThermalSight @004b87d4 // // Chains to PowerWatcher::CreateStreamedSubsystem (FUN_004b198c) then stamps // classID 0x0BDE (+0x20) and model size 0xF4 (+0x24). No thermal-specific // fields are read. // int ThermalSight::CreateStreamedSubsystem( NotationFile *model_file, const char *model_name, const char *subsystem_name, SubsystemResource *subsystem_resource, NotationFile *subsystem_file, const ResourceDirectories *directories, int passes ) { if ( !PowerWatcher::CreateStreamedSubsystem( // FUN_004b198c model_file, model_name, subsystem_name, subsystem_resource, subsystem_file, directories, passes ) ) { return False; } subsystem_resource->subsystemModelSize = 0xF4; // +0x24 subsystem_resource->classID = RegisteredClass::ThermalSightClassID; // 0x0BDE, +0x20 return True; } //===========================================================================// // WAVE 4 -- compile-time layout locks. PowerWatcher is Torso-proven at 0x1D8, // so the own fields land at their EXACT binary offsets; sizeof is bounded to // the factory alloc. (BtAlarm is a 4-byte stand-in for the binary's 0x54 // GaugeAlarm -- immaterial: nothing reads ThermalSight at a raw offset past // its own fields, and the shared BT_LOCAL_ALARM_SHIM stays 4 bytes because // searchlight.hpp shares that guard.) //===========================================================================// struct ThermalSightLayoutCheck { static_assert(offsetof(ThermalSight, thermalActive) == 0x1D8, "ThermalSight thermalActive @0x1D8 (PowerWatcher ends 0x1D8)"); static_assert(offsetof(ThermalSight, requestedOn) == 0x1DC, "ThermalSight requestedOn @0x1DC"); static_assert(offsetof(ThermalSight, stateAlarm) == 0x1E0, "ThermalSight stateAlarm @0x1E0 (proves base ends 0x1D8)"); static_assert(sizeof(ThermalSight) <= 0x234, "sizeof(ThermalSight) must fit the factory Memory::Allocate(0x234)"); }; //===========================================================================// // WAVE 4 factory bridge -- ThermalSight (factory case 0xBDE, "MechDisplay" stub). // The real class at 0xBDE (ctor @004b8718) is ThermalSight; the factory built // a MechDisplay RECON_SUBSYS stub. No cache write; not a weapon (raw // part_012.c:10186-10195 stores roster-only). //===========================================================================// Subsystem *CreateThermalSightSubsystem(Mech *owner, int id, void *seg) { return (Subsystem *) new (Memory::Allocate(0x234)) ThermalSight(owner, id, (ThermalSight::SubsystemResource *)seg, ThermalSight::DefaultData); }