//===========================================================================// // File: thermalsight.hpp // // 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). NO THERMALSIGHT.HPP survived; the class body is inferred from // usage and the embedded string pool. Each method cites its @ADDR. // // IDENTIFICATION: // * mech3.cpp factory map: ThermalSight::CreateStreamedSubsystem @004b87d4, // DefaultData &DAT_00511228. // * vtable @00511498 (slot0 dtor @004b8794), ctor @004b8718. // * CreateStreamedSubsystem @004b87d4 stamps classID 0x0BDE and streamed // model size 0xF4; it parses NO thermal-specific model fields. // * String pool @00511474 ("ThermalSight", "LightOn", "LightState"). // ClassDerivations name "ThermalSight" @00511238. // * Its per-frame update toggles the GLOBAL "pvision" (pixel/thermal vision) // render flag via FUN_0045fe44 (which prints "pvision ON"/"pvision OFF", // strings @004f2f9d/@004f2fa8) -- confirming this is the cockpit IR/thermal // view-mode subsystem rather than a world light. // // BASE CLASS: // ThermalSight IS-A PowerWatcher (powersub.hpp): ctor @004b8718 chains to the // PowerWatcher ctor FUN_004b18a4 with &DAT_00511228, and // CreateStreamedSubsystem chains to PowerWatcher::CreateStreamedSubsystem // FUN_004b198c. Shared base 0..0x1D8; own members begin at 0x1D8. Exposes // one queryable attribute "LightState". Unlike Searchlight it does NOT // override the network-replication slots (6/7) -- it inherits the base. // #if !defined(THERMALSIGHT_HPP) # define THERMALSIGHT_HPP #if !defined(POWERSUB_HPP) #include "powersub.hpp" #endif //########################################################################## //##################### Local multi-level alarm shim ################# //########################################################################## // // CROSS-FAMILY: heat.hpp does `typedef GaugeAlarm AlarmIndicator;` but the // engine GaugeAlarm (GAUGALRM.h) has a PROTECTED ctor/dtor and no // Initialize/Finalize/SetLevel; the mech.hpp/mechrecon.hpp alias resolves // AlarmIndicator to ReconAlarm, which also lacks Initialize/Finalize. Until // the heat family provides a real multi-level AlarmIndicator, this module owns // a tiny faithful stand-in for ITS OWN alarm member (it does NOT redefine the // AlarmIndicator typedef). // #if !defined(BT_LOCAL_ALARM_SHIM) # define BT_LOCAL_ALARM_SHIM struct BtAlarm { int level; BtAlarm() : level(0) {} void Initialize(int /*levels*/) { level = 0; } // FUN_0041b9ec void Finalize() {} // FUN_0041baa4 void SetLevel(int n) { level = n; } // FUN_0041bbd8 int GetLevel() const { return level; } }; #endif //########################################################################## //#################### ThermalSight::SubsystemResource ############### //########################################################################## // // Empty extension of the PowerWatcher resource (CreateStreamedSubsystem // @004b87d4 reads no thermal-specific fields). // struct ThermalSight__SubsystemResource: public PowerWatcher::SubsystemResource { }; //########################################################################## //######################### ThermalSight ############################# //########################################################################## class ThermalSight: public PowerWatcher { friend struct ThermalSightLayoutCheck; // compile-time offset locks (thermalsight.cpp) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Shared Data Support // // DefaultData @00511228, ClassDerivations @00511238 ("ThermalSight"). // public: static Derivation ClassDerivations; // @00511238 static SharedData DefaultData; // @00511228 static Receiver::MessageHandlerSet MessageHandlers; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Attribute Support // // AttributePointers IndexEntry table @00511270: // id 4 "LightState" -> @0x1D8 (offset encoded 0x1D9 == 0x1D8|1) // public: enum { LightStateAttributeID = PowerWatcher::NextAttributeID, // 4 NextAttributeID }; private: static const IndexEntry AttributePointers[]; // @00511270 protected: static AttributeIndexSet& GetAttributeIndex(); // @00511270 table (task #16) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Local data. // protected: int thermalActive; // @0x1D8 reported IR mode on/off (gated by power + heat); "LightState" int requestedOn; // @0x1DC init 0; on/off input consumed by ThermalSightSimulation BtAlarm stateAlarm; // @0x1E0 2-level indicator (size 0x54) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // BT segment / base-state compatibility shims (CROSS-FAMILY). // // Logically belong on the shared bases (Subsystem::GetSegmentFlags, // PowerWatcher::HostShutDown / WatchedVoltageLevel, HeatSink::HeatStateLevel) // plus the cockpit view-mode hooks (the locally-viewed-mech viewport and the // global "pvision" thermal toggle, FUN_0045fe44). Backed locally so this // module compiles; see report. // public: // De-shimmed (WAVE 4 un-stub): the 5 CROSS-FAMILY backing fields // (segmentFlags/hostShutDown/watchedVoltageLevel/heatStateLevel/ // localViewport) + struct BtViewport are DELETED. They duplicated // engine-base state AND -- via the old shadow `segmentFlags` (seeded 0) -- // made the ctor gate ((0&0xC)==0 && (0&0x100)!=0) FALSE, so // ThermalSightSimulation was NEVER installed. Accessors now read the REAL // inherited base members (mirrors torso.hpp / searchlight.hpp): // HostShutDown <- MechSubsystem::simulationState == Destroyed (this+0x40) // WatchedVoltageLevel <- PowerWatcher::watchdogAlarm level (this+0x198) // HeatStateLevel <- HeatWatcher::heatAlarm level (this+0x140) // The cockpit IR view-mode RENDER hooks have no ported target and stay // clearly-marked no-ops for bring-up (thermalActive/stateAlarm are still // computed faithfully): // IsLocallyViewed() -- was localViewport->viewingThisMech (L4 viewport // global PTR_DAT_004fd4b8+0x48); no ported accessor. // ToggleGlobalThermalVision() -- was FUN_0045fe44 (global "pvision" // palette flip); the D3D9 renderer has no pvision. Logical HostShutDown() const { return simulationState == 1; /* Destroyed */ } int WatchedVoltageLevel() const { return watchdogAlarm.GetLevel(); } // @0x198 int HeatStateLevel() const { return heatAlarm.GetLevel(); } // @0x140 Logical IsLocallyViewed() const { return False; } // bring-up no-op (see above) void ToggleGlobalThermalVision() {} // bring-up no-op (pvision unported) // ControlsAllowLights() [T1]: the ToggleLamp @004b860c gate is the owning // BTPlayer's +0x25c "sim live" EXPERIENCE flag (mech+0xD0 -> +0x190 // playerLink -> player+0x25C), 0 only for NOVICE. Routed through the // complete-type bridge in btplayer.cpp (databinding rule: never raw-read // player offsets). Mirrors searchlight.hpp. Logical ControlsAllowLights() const { extern int BTPlayerExperienceSimLive(void *owner_mech); // btplayer.cpp (player+0x25c) return BTPlayerExperienceSimLive(owner) ? True : False; } // The "ToggleLamp" command arg is carried at message+0xC (decomp) -- that // is ReceiverDataMessageOf::dataContents, the first member added to // Receiver::Message. Read the NAMED member (databinding rule); // thermalsight.cpp static_asserts the 0xC equivalence. static int MessageArg(Message &m) { return Cast_Object(const ReceiverDataMessageOf*, &m)->dataContents; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Message handling -- table @0051120c // // Gitea #61 (input-path audit systemic cause #1). `MessageHandlers` above is // DEFAULT-CONSTRUCTED -- entryCount 0, no parent chain -- so Find() returned // NullHandler for every id and Receiver::Receive dropped every message the // thermal sight was sent, in silence. // // The binary's table @0051120c holds exactly ONE entry -- // { 3, "ToggleLamp" @0051146a, @004b860c }. That handler was previously // transcribed into searchlight.cpp by mistake: @004b860c toggles +0x1DC, which // is THIS class's requestedOn (the field its own Performance @004b8648 reads) // and is a resource-seeded non-boolean in Searchlight. The un-pooled // "ToggleLamp" string @0051146a sits immediately before "ThermalSight" // @00511475, which is what pins the entry to this TU. // [T1: reference/decomp/section_dump.txt lines 69670-69711] // // Signature note: Receiver::Handler is void(const Message*) while the decomp's // shape is Logical(Message&), so the table binds a typed forwarder, not a cast. // Parent link: PowerWatcher declares no set of its own, so // PowerWatcher::GetMessageHandlers() name-resolves through HeatWatcher and // MechSubsystem to Receiver's empty ROOT set -- HeatSink's id 3 (ToggleCooling) // is NOT on this chain, so id 3 is unambiguous here. // public: enum { ToggleLampMessageID = 3 }; // table @0051120c, fn @004b860c static Receiver::MessageHandlerSet& GetMessageHandlers(); Logical ToggleLamp(Message &message); // @004b860c void ToggleLampMessageHandler(ReceiverDataMessageOf *message); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Simulation Support // public: typedef void (ThermalSight::*Performance)(Scalar time_slice); void SetPerformance(Performance performance) { Check(this); activePerformance = (Simulation::Performance)performance; } void ThermalSightSimulation(Scalar time_slice); // @004b8648 (Performance, PTR @00511290) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Test Class Support // public: static Logical TestClass(Mech&); Logical TestInstance() const; // @004b881c //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Construction and Destruction // // vtable @00511498 differs from the PowerWatcher base only in slot0 (dtor) // and the inherited PowerWatcher slots 9/10 (@004b179c / @004b1804); no // further virtual overrides. // public: typedef ThermalSight__SubsystemResource SubsystemResource; ThermalSight( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data = DefaultData ); // @004b8718 ~ThermalSight(); // @004b8794 static int CreateStreamedSubsystem( // @004b87d4 NotationFile *model_file, const char *model_name, const char *subsystem_name, SubsystemResource *subsystem_resource, NotationFile *subsystem_file, const ResourceDirectories *directories, int passes = 1 ); }; #endif