//===========================================================================// // File: searchlight.cpp // // Project: BattleTech // // Contents: Searchlight (illumination) 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 @004b84dc..@004b8648. 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_0041bd34 Subsystem::ReadUpdate (base, slot 6) // FUN_0041c500 Subsystem::WriteUpdate (base, slot 7) // FUN_004022d0 operator delete / global free // #include #pragma hdrstop #if !defined(SEARCHLIGHT_HPP) # include #endif #if !defined(APP_HPP) # include #endif #if !defined(TESTBT_HPP) # include #endif //########################################################################### // Shared Data Support // Derivation Searchlight::ClassDerivations( // @005111a8 PowerWatcher::GetClassDerivations(), "Searchlight" ); Receiver::MessageHandlerSet Searchlight::MessageHandlers; // includes { "ToggleLamp", &Searchlight::ToggleLamp } @00511210 // gauge data-binding wave: publish "LightOn" -> lightState@0x1D8 (the button-5 // searchlight lamp; the empty default-constructed index resolved NOTHING). // Chained to PowerWatcher's dense index. const Searchlight::IndexEntry Searchlight::AttributePointers[]= // @005111e0 { ATTRIBUTE_ENTRY(Searchlight, LightOn, lightState) // @0x1D8 (reported on/off) }; Searchlight::AttributeIndexSet Searchlight::AttributeIndex( ELEMENTS(Searchlight::AttributePointers), Searchlight::AttributePointers, PowerWatcher::GetAttributeIndex() ); Searchlight::SharedData Searchlight::DefaultData( // @00511198 &Searchlight::ClassDerivations, Searchlight::MessageHandlers, Searchlight::AttributeIndex, Searchlight::StateCount ); //########################################################################### // Construction -- @004b84dc // // Chains to the PowerWatcher ctor (FUN_004b18a4) with &DAT_00511198, installs // the Searchlight vtable (PTR_FUN_005114d4), builds the 2-level state // AlarmIndicator (@0x1E4) and seeds commandedOn from the segment field // (+0x28). When this is a live (non-copy) segment with the has-performance // flag set it registers SearchlightSimulation (this[7..9] <- PTR_FUN_00511200); // otherwise it marks the instance "no per-frame performance" (flag bit 2). // Searchlight::Searchlight( 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 7 cross-family shim fields are gone; the accessors // read the inherited base state directly. Only the own fields init here. lightState = 0; // @0x1D8 requestedOn = 0; // @0x1E0 stateAlarm.Initialize(2); // FUN_0041b9ec(this+0x1E4, 2) commandedOn = subsystem_resource->segmentIndex; // @0x1DC <- inherited resource +0x28 // GATE FIX (the functional bug the de-shim unblocks): the binary reads OWNER // simulationFlags (param_2+0x28, raw part_013.c:6012), NOT the old shadow // `segmentFlags` -- which was seeded 0, so the gate was ALWAYS FALSE and // SearchlightSimulation NEVER installed. A live master segment arms it; a // copy/replicant gets DontExecute (flag 2). Mirrors torso.cpp:181-182. if (((owner->simulationFlags & 0xC) == 0) && ((owner->simulationFlags & 0x100) != 0)) { SetPerformance(&Searchlight::SearchlightSimulation); // this[7..9] <- PTR_FUN_00511200 } else { SetInstanceFlag(2); // this[10] |= 2 (no active performance) } Check_Fpu(); } // // Destruction -- @004b8568 (vtable slot0). Reinstalls the vtable, tears down // the AlarmIndicator (@0x1E4), chains to ~PowerWatcher (FUN_004b1930), frees // the block when the deleting-dtor bit is set. // Searchlight::~Searchlight() { Check(this); stateAlarm.Finalize(); // FUN_0041baa4(this+0x1E4, 2) Check_Fpu(); } //########################################################################### // TestInstance -- @004b85f0 // Logical Searchlight::TestInstance() const { return IsDerivedFrom(ClassDerivations); // FUN_0041a1a4(**this[3], 0x005111a8) } Logical Searchlight::TestClass(Mech &) { return True; // BEST-EFFORT (family convention) } //########################################################################### // ToggleLamp -- @004b860c ("ToggleLamp" message handler) // // Bound by the shared-data message table @00511210. On a positive command // (message arg @0xC > 0) and when the host mech's controls allow it // (mech +0xD0 -> +0x190 -> +0x25C != 0), flips commandedOn (@0x1DC). // Logical Searchlight::ToggleLamp(Message &message) { if ((0 < MessageArg(message)) && // *(message + 0xC) ControlsAllowLights()) // *(*(owner +0xD0)+0x190)+0x25C { commandedOn = (commandedOn == 0); // toggle @0x1DC } return True; } //########################################################################### // SearchlightSimulation -- @004b841c (Performance) // // Gates the reported light state by power and heat: // * base tick (FUN_004b181c). // * if the host is shut down (this[0x40] == 1) the light is forced off, // else it follows requestedOn (@0x1E0). // * the watched-voltage alarm level (@0x198) must read Ready(4) for the // light to stay lit; otherwise it blanks. // * the heat-state level (@0x140): <2 keeps the light, ==2 blanks it. // * pushes the resulting on/off into the state AlarmIndicator (@0x1E4) and, // if it changed, raises the subsystem "graphics dirty" bit (this[0x18] |= 1). // void Searchlight::SearchlightSimulation(Scalar time_slice) { Check(this); PowerWatcher::Simulation(time_slice); // FUN_004b181c (base per-frame update) int previous = lightState; // @0x1D8 lightState = (HostShutDown()) ? 0 : requestedOn; // this[0x40]==1 ? 0 : @0x1E0 if (WatchedVoltageLevel() == 4) // @0x198 == Ready lightState = (lightState != 0); else lightState = 0; if (HeatStateLevel() < 2) // @0x140 lightState = (lightState != 0); else if (HeatStateLevel() == 2) lightState = 0; stateAlarm.SetLevel(lightState); // FUN_0041bbd8(this+0x1E4, lightState) if (previous != lightState) SetGraphicsDirty(); // this[0x18] |= 1 Check_Fpu(); } //########################################################################### // ReadUpdate / WriteUpdate -- network state replication // // slot 6 @004b83b8: chain base ReadUpdate (FUN_0041bd34), then take the // replicated on/off (packet +0x10) as lightState (@0x1D8) and drive the // alarm (@0x1E4). // slot 7 @004b83f0: chain base WriteUpdate (FUN_0041c500), then stamp the // record kind 0x14 and write lightState into record field [4]. // // The decomp's NetworkPacket.value / NetworkRecord{kind,value} do not exist on // the engine UpdateRecord (SIMULATE.h). The replicated on/off maps to // UpdateRecord::simulationState and the 0x14 record tag to ::recordID. // void Searchlight::ReadUpdateRecord(UpdateRecord *message) { PowerWatcher::ReadUpdateRecord(message); // inherited Simulation::ReadUpdateRecord lightState = (int)message->simulationState; // @0x1D8 <- replicated on/off stateAlarm.SetLevel(lightState); // FUN_0041bbd8(this+0x1E4, lightState) } void Searchlight::WriteUpdateRecord(UpdateRecord *message, int update_model) { PowerWatcher::WriteUpdateRecord(message, update_model); // Subsystem::WriteUpdateRecord message->recordID = 0x14; // record kind tag message->simulationState = lightState; // replicated on/off <- @0x1D8 } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // CreateStreamedSubsystem -- Searchlight @004b85a8 // // Chains to PowerWatcher::CreateStreamedSubsystem (FUN_004b198c) then stamps // classID 0x0BD8 (+0x20) and model size 0xF4 (+0x24). No searchlight-specific // fields are read (empty resource extension). // int Searchlight::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::SearchlightClassID; // 0x0BD8, +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 Searchlight at a raw offset past its // own fields, and the shared BT_LOCAL_ALARM_SHIM stays 4 bytes because // thermalsight.hpp shares that guard.) //===========================================================================// struct SearchlightLayoutCheck { static_assert(offsetof(Searchlight, lightState) == 0x1D8, "Searchlight lightState @0x1D8 (PowerWatcher ends 0x1D8)"); static_assert(offsetof(Searchlight, commandedOn) == 0x1DC, "Searchlight commandedOn @0x1DC (res+0x28)"); static_assert(offsetof(Searchlight, requestedOn) == 0x1E0, "Searchlight requestedOn @0x1E0"); static_assert(offsetof(Searchlight, stateAlarm) == 0x1E4, "Searchlight stateAlarm @0x1E4 (proves base ends 0x1D8)"); static_assert(sizeof(Searchlight) <= 0x238, "sizeof(Searchlight) must fit the factory Memory::Allocate(0x238)"); }; //===========================================================================// // WAVE 4 factory bridge -- Searchlight (factory case 0xBD8, "LegSubsystem" label). // The real class at 0xBD8 (ctor @004b84dc) is Searchlight; the factory built a // LegSubsystem RECON_SUBSYS stub. No cache write; not a weapon (raw // part_012.c:10166-10175 stores roster-only). //===========================================================================// Subsystem *CreateSearchlightSubsystem(Mech *owner, int id, void *seg) { return (Subsystem *) new (Memory::Allocate(0x238)) Searchlight(owner, id, (Searchlight::SubsystemResource *)seg, Searchlight::DefaultData); }