From 209d509a6ac66c7d49810e9d7772dd3efe42e137 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Sat, 1 Aug 2026 23:25:34 +0800 Subject: [PATCH 1/6] Decouple SW.ShowCameo / SW.ManualFire from SW.AutoFire --- CREDITS.md | 1 + docs/Fixed-or-Improved-Logics.md | 1 + docs/Whats-New.md | 1 + src/Ext/Sidebar/SWSidebar/SWButtonClass.cpp | 2 +- src/Ext/Sidebar/SWSidebar/SWSidebarClass.cpp | 2 +- src/Misc/Hooks.Ares.cpp | 26 +++++++++++++++++++- 6 files changed, 30 insertions(+), 3 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index 5d9ea011ce..4baf9d7c21 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -738,6 +738,7 @@ This page lists all the individual contributions to the project by their author. - Remove the restriction that prohibits InfantryTypes from using the InitialPayload logic - Add `ammo`, `health`, `mission`, `landtype` and `sequence` conditions to `DiscardOn` - Disable AlphaImage during Buildup + - Allow `SW.ShowCameo` and `SW.ManualFire` to work independently of `SW.AutoFire` - **Ollerus**: - Build limit group enhancement - Customizable rocker amplitude diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index 2f3ca0ff90..51d7b72041 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -381,6 +381,7 @@ This page describes all ingame logics that are fixed or improved in Phobos witho - Fixed the issue of Ares' EMP not suspending the production of AI factories. - Removed the restriction that prohibits InfantryTypes from using the InitialPayload logic. - `ProjectileRange` now has weapon range modifiers applied to it if greater than 0 and unless `ProjectileRange.ApplyModifiers` is set to false on the WeaponType. +- Allowed `SW.ShowCameo` and `SW.ManualFire` to work independently of `SW.AutoFire`. ## Newly added global settings diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 3f918553c5..5f8743d889 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -826,6 +826,7 @@ HideShakeEffects=false ; boolean - Fixed the issue of Ares' EMP not suspending the production of AI factories (by CrimRecya) - Removed the restriction that prohibits InfantryTypes from using the InitialPayload logic (by Noble_Fish) - `ProjectileRange` now has weapon range modifiers applied to it if greater than 0 and unless `ProjectileRange.ApplyModifiers` is set to false on the WeaponType (by Starkku) +- Allowed `SW.ShowCameo` and `SW.ManualFire` to work independently of `SW.AutoFire` (by Noble_Fish) ``` ### 0.4.0.3 diff --git a/src/Ext/Sidebar/SWSidebar/SWButtonClass.cpp b/src/Ext/Sidebar/SWSidebar/SWButtonClass.cpp index 31b4e2f023..afb8cf31f9 100644 --- a/src/Ext/Sidebar/SWSidebar/SWButtonClass.cpp +++ b/src/Ext/Sidebar/SWSidebar/SWButtonClass.cpp @@ -182,7 +182,7 @@ bool SWButtonClass::LaunchSuper() const const auto pSuper = pCurrent->Supers[this->SuperIndex]; const auto pType = pSuper->Type; const auto pSWExt = SWTypeExt::Fetch(pType); - const bool manual = !pSWExt->SW_ManualFire && pSWExt->SW_AutoFire; + const bool manual = !pSWExt->SW_ManualFire; const bool unstoppable = pType->UseChargeDrain && pSuper->ChargeDrainState == ChargeDrainState::Draining && pSWExt->SW_Unstoppable; if (!pSuper->CanFire() && !manual) diff --git a/src/Ext/Sidebar/SWSidebar/SWSidebarClass.cpp b/src/Ext/Sidebar/SWSidebar/SWSidebarClass.cpp index 72fc4943b9..0245cec68d 100644 --- a/src/Ext/Sidebar/SWSidebar/SWSidebarClass.cpp +++ b/src/Ext/Sidebar/SWSidebar/SWSidebarClass.cpp @@ -129,7 +129,7 @@ bool SWSidebarClass::AddButton(int superIdx) const auto pSWExt = SWTypeExt::Fetch(pSWType); - if (!pSWExt->SW_ShowCameo && pSWExt->SW_AutoFire) + if (!pSWExt->SW_ShowCameo) return false; if (!pSWExt->SuperWeaponSidebar_Allow.Get(RulesExt::Global()->SuperWeaponSidebar_AllowByDefault)) diff --git a/src/Misc/Hooks.Ares.cpp b/src/Misc/Hooks.Ares.cpp index 1e9d201176..63389fe7c8 100644 --- a/src/Misc/Hooks.Ares.cpp +++ b/src/Misc/Hooks.Ares.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -292,6 +292,18 @@ void Apply_Ares3_0_Patches() Patch::Apply_CALL(AresHelper::AresBaseAddress + 0x13FA7, &BuildingExt::UpdateFactoryQueues); Patch::Apply_CALL(AresHelper::AresBaseAddress + 0x4CD9E, &BuildingExt::UpdateFactoryQueues); Patch::Apply_CALL(AresHelper::AresBaseAddress + 0x4CE84, &BuildingExt::UpdateFactoryQueues); + + // Decouple SW.ShowCameo from SW.AutoFire - Ares' HouseClass_UpdateSuperWeaponsUnavailable + Patch::Apply_RAW(AresHelper::AresBaseAddress + 0x3962C, { + 0x80, 0xB8, 0xE5, 0x01, 0x00, 0x00, 0x00, // cmp byte ptr [eax+0x1E5], 0 (SW_ShowCameo) + 0x74, 0x2F, // jz -> skip AddCameo (hide) + 0xEB, 0x07, // jmp -> AddCameo (show) + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 + }); + // Decouple SW.ManualFire from SW.AutoFire - Ares' SidebarClass_ProcessCameoClick_SuperWeapons + Patch::Apply_RAW(AresHelper::AresBaseAddress + 0x34DD0, { + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 // nop cmp [esi+0x1E1],0; jz (AutoFire check) + }); } void Apply_Ares3_0p1_Patches() @@ -403,4 +415,16 @@ void Apply_Ares3_0p1_Patches() Patch::Apply_CALL(AresHelper::AresBaseAddress + 0x14537, &BuildingExt::UpdateFactoryQueues); Patch::Apply_CALL(AresHelper::AresBaseAddress + 0x4DA0E, &BuildingExt::UpdateFactoryQueues); Patch::Apply_CALL(AresHelper::AresBaseAddress + 0x4DAF4, &BuildingExt::UpdateFactoryQueues); + + // Decouple SW.ShowCameo from SW.AutoFire - Ares' HouseClass_UpdateSuperWeaponsUnavailable + Patch::Apply_RAW(AresHelper::AresBaseAddress + 0x3A0AC, { + 0x80, 0xB8, 0xE5, 0x01, 0x00, 0x00, 0x00, // cmp byte ptr [eax+0x1E5], 0 (SW_ShowCameo) + 0x74, 0x2F, // jz -> skip AddCameo (hide) + 0xEB, 0x07, // jmp -> AddCameo (show) + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 + }); + // Decouple SW.ManualFire from SW.AutoFire - Ares' SidebarClass_ProcessCameoClick_SuperWeapons + Patch::Apply_RAW(AresHelper::AresBaseAddress + 0x35810, { + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 // nop cmp [esi+0x1E1],0; jz (AutoFire check) + }); } From 134e3cc7809212a7723f74e7b8b61d9954e55f9b Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Mon, 10 Aug 2026 15:33:55 +0800 Subject: [PATCH 2/6] Refactor SW.AutoFire decoupling to redirect Ares hook bodies --- src/Ext/SWType/Body.cpp | 2 + src/Ext/SWType/Body.h | 2 + src/Misc/Hooks.Ares.cpp | 206 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 191 insertions(+), 19 deletions(-) diff --git a/src/Ext/SWType/Body.cpp b/src/Ext/SWType/Body.cpp index 531a66fb0d..480aa70ffc 100644 --- a/src/Ext/SWType/Body.cpp +++ b/src/Ext/SWType/Body.cpp @@ -31,6 +31,7 @@ void SWTypeExt::Serialize(T& Stm) .Process(this->SW_Unstoppable) .Process(this->SW_AllowPlayer) .Process(this->SW_AllowAI) + .Process(this->SW_AlwaysGranted) .Process(this->SW_Inhibitors) .Process(this->SW_AnyInhibitor) .Process(this->SW_Designators) @@ -129,6 +130,7 @@ void SWTypeExt::LoadFromINIFile(CCINIClass* const pINI) this->SW_Unstoppable.Read(exINI, pSection, "SW.Unstoppable"); this->SW_AllowPlayer.Read(exINI, pSection, "SW.AllowPlayer"); this->SW_AllowAI.Read(exINI, pSection, "SW.AllowAI"); + this->SW_AlwaysGranted.Read(exINI, pSection, "SW.AlwaysGranted"); this->SW_Inhibitors.Read(exINI, pSection, "SW.Inhibitors"); this->SW_AnyInhibitor.Read(exINI, pSection, "SW.AnyInhibitor"); this->SW_Designators.Read(exINI, pSection, "SW.Designators"); diff --git a/src/Ext/SWType/Body.h b/src/Ext/SWType/Body.h index e064cdcfdb..3836e4684c 100644 --- a/src/Ext/SWType/Body.h +++ b/src/Ext/SWType/Body.h @@ -36,6 +36,7 @@ class SWTypeExt final : public AbstractTypeExt Valueable SW_Unstoppable; Valueable SW_AllowPlayer; Valueable SW_AllowAI; + Valueable SW_AlwaysGranted; ValueableVector SW_Inhibitors; Valueable SW_AnyInhibitor; ValueableVector SW_Designators; @@ -138,6 +139,7 @@ class SWTypeExt final : public AbstractTypeExt , SW_Unstoppable { false } , SW_AllowPlayer { true } , SW_AllowAI { true } + , SW_AlwaysGranted { false } , SW_Inhibitors {} , SW_AnyInhibitor { false } , SW_Designators { } diff --git a/src/Misc/Hooks.Ares.cpp b/src/Misc/Hooks.Ares.cpp index 63389fe7c8..78cc8d79cd 100644 --- a/src/Misc/Hooks.Ares.cpp +++ b/src/Misc/Hooks.Ares.cpp @@ -1,14 +1,16 @@ -#include +#include #include #include #include #include +#include #include #include #include #include #include +#include #include @@ -175,6 +177,186 @@ static bool __fastcall ParadropPlaneUnlimbo(AircraftClass* pThis, void* _, const #pragma endregion +#pragma region AresAutoFireDecoupling + +namespace SuperWeaponStatusTemp +{ + struct Status + { + bool Available; + bool PowerSourced; + bool Charging; + }; + + // Re-implements Ares' GetSuperWeaponStatuses() + std::vector GetStatuses(HouseClass* pHouse) + { + std::vector Statuses(pHouse->Supers.Count, { false, false, false }); + + if (!pHouse->Defeated && !pHouse->IsObserver()) + { + for (int i = 0; i < pHouse->Supers.Count; ++i) + { + const auto pData = SWTypeExt::Fetch(pHouse->Supers[i]->Type); + if (pData->SW_AlwaysGranted && pData->IsAvailable(pHouse)) + Statuses[i] = { true, true, true }; + } + + for (auto pBld : pHouse->Buildings) + { + if (pBld->IsAlive && !pBld->InLimbo) + { + auto UpdateStatus = [pBld, pHouse, &Statuses](int idxSW) + { + if (idxSW > -1 && !Statuses[idxSW].Charging) + { + const auto pData = SWTypeExt::Fetch(pHouse->Supers[idxSW]->Type); + + if (!pData->IsAvailable(pHouse)) + return; + + auto& status = Statuses[idxSW]; + status.Available = true; + + if (!status.Charging && pBld->HasPower && !pBld->IsUnderEMP()) + { + status.PowerSourced = true; + + if (!pBld->IsBeingWarpedOut() + && pBld->CurrentMission != Mission::Construction + && pBld->CurrentMission != Mission::Selling + && pBld->QueuedMission != Mission::Construction + && pBld->QueuedMission != Mission::Selling) + { + status.Charging = true; + } + } + } + }; + + for (auto pUpgrade : pBld->Upgrades) + { + if (const auto pUpgradeExt = BuildingTypeExt::TryFetch(pUpgrade)) + { + for (int i = 0; i < 2 + (int)pUpgradeExt->SuperWeapons.size(); ++i) + UpdateStatus(pUpgradeExt->GetSuperWeaponIndex(i, pHouse)); + } + } + + const auto pTypeExt = BuildingTypeExt::Fetch(pBld->Type); + for (int i = 0; i < 2 + (int)pTypeExt->SuperWeapons.size(); ++i) + UpdateStatus(pTypeExt->GetSuperWeaponIndex(i)); + } + } + + const bool hasPower = pHouse->HasFullPower(); + + for (auto pSuper : pHouse->Supers) + { + auto index = pSuper->Type->ArrayIndex; + auto& status = Statuses[index]; + + if (SessionClass::Instance.GameMode != GameMode::Campaign && !Unsorted::SWAllowed) + { + if (pSuper->Type->DisableableFromShell) + status.Available = false; + } + + if (pSuper->IsPowered()) + status.PowerSourced &= hasPower; + } + } + + return Statuses; + } +} + +// Decoupled: SW.ShowCameo now hides the cameo regardless of SW.AutoFire. +static int __cdecl Ares_HouseClass_UpdateSuperWeaponsUnavailable(REGISTERS* R) +{ + GET(HouseClass*, pThis, ECX); + + if (!pThis->Defeated) + { + auto Statuses = SuperWeaponStatusTemp::GetStatuses(pThis); + + for (auto pSuper : pThis->Supers) + { + if (!pSuper->IsPresent || pSuper->IsOneTime) + { + auto index = pSuper->Type->ArrayIndex; + auto& status = Statuses[index]; + + if (status.Available) + { + pSuper->Grant(false, pThis->IsCurrentPlayer(), !status.PowerSourced); + + if (pThis->IsCurrentPlayer()) + { + const auto pExt = SWTypeExt::Fetch(pSuper->Type); + if (pExt->SW_ShowCameo) + { + MouseClass::Instance.AddCameo(AbstractType::Special, index); + int idxTab = SidebarClass::GetObjectTabIdx(SuperClass::AbsID, index, 0); + MouseClass::Instance.RepaintSidebar(idxTab); + } + } + } + } + } + } + + return 0x50B36E; +} + +// Decoupled: SW.ManualFire now disallows manual firing regardless of SW.AutoFire. +static int __cdecl Ares_SidebarClass_ProcessCameoClick_SuperWeapons(REGISTERS* R) +{ + GET(int, idxSW, ESI); + const auto pSuper = HouseClass::CurrentPlayer->Supers[idxSW]; + const auto pExt = SWTypeExt::Fetch(pSuper->Type); + + // Decoupled: manual is decided by SW.ManualFire alone. + const bool manual = !pExt->SW_ManualFire; + const bool unstoppable = pSuper->Type->UseChargeDrain + && pSuper->ChargeDrainState == ChargeDrainState::Draining + && pExt->SW_Unstoppable; + + if (!manual && !pSuper->CanFire()) + { + VoxClass::PlayIndex(pExt->EVA_Impatient); + return 0x6AAFB1; + } + + if (!HouseClass::CurrentPlayer->CanTransactMoney(pExt->Money_Amount)) + { + VoxClass::PlayIndex(pExt->EVA_InsufficientFunds); + pExt->PrintMessage(pExt->Message_InsufficientFunds, HouseClass::CurrentPlayer); + return 0x6AAFB1; + } + + if (!pExt->SW_UseAITargeting + || (AresFunctions::IsTargetConstraintsEligible + && AresFunctions::IsTargetConstraintsEligible(AresFunctions::SWTypeExtMap_Find(pSuper->Type), HouseClass::CurrentPlayer, true))) + { + if (manual || unstoppable) + return 0x6AAFB1; + + if (pSuper->Type->Action == Action::None || pExt->SW_UseAITargeting) + { + R->EAX(HouseClass::CurrentPlayer); + return 0x6AAF10; + } + + return 0x6AAF46; + } + + pExt->PrintMessage(pExt->Message_CannotFire, HouseClass::CurrentPlayer); + return 0x6AAFB1; +} + +#pragma endregion + DEFINE_HOOK(0x440580, BuildingClass_Unlimbo_UnitDeliveryFix, 0x5) { if (UnitDeliveryTemp::Placing) @@ -294,16 +476,9 @@ void Apply_Ares3_0_Patches() Patch::Apply_CALL(AresHelper::AresBaseAddress + 0x4CE84, &BuildingExt::UpdateFactoryQueues); // Decouple SW.ShowCameo from SW.AutoFire - Ares' HouseClass_UpdateSuperWeaponsUnavailable - Patch::Apply_RAW(AresHelper::AresBaseAddress + 0x3962C, { - 0x80, 0xB8, 0xE5, 0x01, 0x00, 0x00, 0x00, // cmp byte ptr [eax+0x1E5], 0 (SW_ShowCameo) - 0x74, 0x2F, // jz -> skip AddCameo (hide) - 0xEB, 0x07, // jmp -> AddCameo (show) - 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 - }); + Patch::Apply_LJMP(AresHelper::AresBaseAddress + 0x39580, GET_OFFSET(Ares_HouseClass_UpdateSuperWeaponsUnavailable)); // Decouple SW.ManualFire from SW.AutoFire - Ares' SidebarClass_ProcessCameoClick_SuperWeapons - Patch::Apply_RAW(AresHelper::AresBaseAddress + 0x34DD0, { - 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 // nop cmp [esi+0x1E1],0; jz (AutoFire check) - }); + Patch::Apply_LJMP(AresHelper::AresBaseAddress + 0x34DA0, GET_OFFSET(Ares_SidebarClass_ProcessCameoClick_SuperWeapons)); } void Apply_Ares3_0p1_Patches() @@ -417,14 +592,7 @@ void Apply_Ares3_0p1_Patches() Patch::Apply_CALL(AresHelper::AresBaseAddress + 0x4DAF4, &BuildingExt::UpdateFactoryQueues); // Decouple SW.ShowCameo from SW.AutoFire - Ares' HouseClass_UpdateSuperWeaponsUnavailable - Patch::Apply_RAW(AresHelper::AresBaseAddress + 0x3A0AC, { - 0x80, 0xB8, 0xE5, 0x01, 0x00, 0x00, 0x00, // cmp byte ptr [eax+0x1E5], 0 (SW_ShowCameo) - 0x74, 0x2F, // jz -> skip AddCameo (hide) - 0xEB, 0x07, // jmp -> AddCameo (show) - 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 - }); + Patch::Apply_LJMP(AresHelper::AresBaseAddress + 0x3A000, GET_OFFSET(Ares_HouseClass_UpdateSuperWeaponsUnavailable)); // Decouple SW.ManualFire from SW.AutoFire - Ares' SidebarClass_ProcessCameoClick_SuperWeapons - Patch::Apply_RAW(AresHelper::AresBaseAddress + 0x35810, { - 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 // nop cmp [esi+0x1E1],0; jz (AutoFire check) - }); + Patch::Apply_LJMP(AresHelper::AresBaseAddress + 0x357E0, GET_OFFSET(Ares_SidebarClass_ProcessCameoClick_SuperWeapons)); } From d1cd087d530f7754eb2b48c54e9aa2a990d5fe47 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Mon, 10 Aug 2026 16:33:54 +0800 Subject: [PATCH 3/6] clean format --- src/Misc/Hooks.Ares.cpp | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/Misc/Hooks.Ares.cpp b/src/Misc/Hooks.Ares.cpp index 78cc8d79cd..73f1ddb10c 100644 --- a/src/Misc/Hooks.Ares.cpp +++ b/src/Misc/Hooks.Ares.cpp @@ -191,7 +191,7 @@ namespace SuperWeaponStatusTemp // Re-implements Ares' GetSuperWeaponStatuses() std::vector GetStatuses(HouseClass* pHouse) { - std::vector Statuses(pHouse->Supers.Count, { false, false, false }); + std::vector statuses(pHouse->Supers.Count, { false, false, false }); if (!pHouse->Defeated && !pHouse->IsObserver()) { @@ -199,23 +199,23 @@ namespace SuperWeaponStatusTemp { const auto pData = SWTypeExt::Fetch(pHouse->Supers[i]->Type); if (pData->SW_AlwaysGranted && pData->IsAvailable(pHouse)) - Statuses[i] = { true, true, true }; + statuses[i] = { true, true, true }; } - for (auto pBld : pHouse->Buildings) + for (const auto pBld : pHouse->Buildings) { if (pBld->IsAlive && !pBld->InLimbo) { - auto UpdateStatus = [pBld, pHouse, &Statuses](int idxSW) + auto UpdateStatus = [pBld, pHouse, &statuses](int idxSW) { - if (idxSW > -1 && !Statuses[idxSW].Charging) + if (idxSW > -1 && !statuses[idxSW].Charging) { const auto pData = SWTypeExt::Fetch(pHouse->Supers[idxSW]->Type); if (!pData->IsAvailable(pHouse)) return; - auto& status = Statuses[idxSW]; + auto& status = statuses[idxSW]; status.Available = true; if (!status.Charging && pBld->HasPower && !pBld->IsUnderEMP()) @@ -234,7 +234,7 @@ namespace SuperWeaponStatusTemp } }; - for (auto pUpgrade : pBld->Upgrades) + for (const auto pUpgrade : pBld->Upgrades) { if (const auto pUpgradeExt = BuildingTypeExt::TryFetch(pUpgrade)) { @@ -251,10 +251,10 @@ namespace SuperWeaponStatusTemp const bool hasPower = pHouse->HasFullPower(); - for (auto pSuper : pHouse->Supers) + for (const auto pSuper : pHouse->Supers) { - auto index = pSuper->Type->ArrayIndex; - auto& status = Statuses[index]; + const auto index = pSuper->Type->ArrayIndex; + auto& status = statuses[index]; if (SessionClass::Instance.GameMode != GameMode::Campaign && !Unsorted::SWAllowed) { @@ -267,7 +267,7 @@ namespace SuperWeaponStatusTemp } } - return Statuses; + return statuses; } } @@ -278,14 +278,14 @@ static int __cdecl Ares_HouseClass_UpdateSuperWeaponsUnavailable(REGISTERS* R) if (!pThis->Defeated) { - auto Statuses = SuperWeaponStatusTemp::GetStatuses(pThis); + auto statuses = SuperWeaponStatusTemp::GetStatuses(pThis); - for (auto pSuper : pThis->Supers) + for (const auto pSuper : pThis->Supers) { if (!pSuper->IsPresent || pSuper->IsOneTime) { - auto index = pSuper->Type->ArrayIndex; - auto& status = Statuses[index]; + const auto index = pSuper->Type->ArrayIndex; + auto& status = statuses[index]; if (status.Available) { @@ -297,7 +297,7 @@ static int __cdecl Ares_HouseClass_UpdateSuperWeaponsUnavailable(REGISTERS* R) if (pExt->SW_ShowCameo) { MouseClass::Instance.AddCameo(AbstractType::Special, index); - int idxTab = SidebarClass::GetObjectTabIdx(SuperClass::AbsID, index, 0); + const int idxTab = SidebarClass::GetObjectTabIdx(SuperClass::AbsID, index, 0); MouseClass::Instance.RepaintSidebar(idxTab); } } @@ -316,7 +316,6 @@ static int __cdecl Ares_SidebarClass_ProcessCameoClick_SuperWeapons(REGISTERS* R const auto pSuper = HouseClass::CurrentPlayer->Supers[idxSW]; const auto pExt = SWTypeExt::Fetch(pSuper->Type); - // Decoupled: manual is decided by SW.ManualFire alone. const bool manual = !pExt->SW_ManualFire; const bool unstoppable = pSuper->Type->UseChargeDrain && pSuper->ChargeDrainState == ChargeDrainState::Draining From a3a621c9725fc3e780199e0af0179b0568769f8a Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Mon, 10 Aug 2026 23:22:39 +0800 Subject: [PATCH 4/6] Revert "clean format" This reverts commit d1cd087d530f7754eb2b48c54e9aa2a990d5fe47. --- src/Misc/Hooks.Ares.cpp | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/Misc/Hooks.Ares.cpp b/src/Misc/Hooks.Ares.cpp index 73f1ddb10c..78cc8d79cd 100644 --- a/src/Misc/Hooks.Ares.cpp +++ b/src/Misc/Hooks.Ares.cpp @@ -191,7 +191,7 @@ namespace SuperWeaponStatusTemp // Re-implements Ares' GetSuperWeaponStatuses() std::vector GetStatuses(HouseClass* pHouse) { - std::vector statuses(pHouse->Supers.Count, { false, false, false }); + std::vector Statuses(pHouse->Supers.Count, { false, false, false }); if (!pHouse->Defeated && !pHouse->IsObserver()) { @@ -199,23 +199,23 @@ namespace SuperWeaponStatusTemp { const auto pData = SWTypeExt::Fetch(pHouse->Supers[i]->Type); if (pData->SW_AlwaysGranted && pData->IsAvailable(pHouse)) - statuses[i] = { true, true, true }; + Statuses[i] = { true, true, true }; } - for (const auto pBld : pHouse->Buildings) + for (auto pBld : pHouse->Buildings) { if (pBld->IsAlive && !pBld->InLimbo) { - auto UpdateStatus = [pBld, pHouse, &statuses](int idxSW) + auto UpdateStatus = [pBld, pHouse, &Statuses](int idxSW) { - if (idxSW > -1 && !statuses[idxSW].Charging) + if (idxSW > -1 && !Statuses[idxSW].Charging) { const auto pData = SWTypeExt::Fetch(pHouse->Supers[idxSW]->Type); if (!pData->IsAvailable(pHouse)) return; - auto& status = statuses[idxSW]; + auto& status = Statuses[idxSW]; status.Available = true; if (!status.Charging && pBld->HasPower && !pBld->IsUnderEMP()) @@ -234,7 +234,7 @@ namespace SuperWeaponStatusTemp } }; - for (const auto pUpgrade : pBld->Upgrades) + for (auto pUpgrade : pBld->Upgrades) { if (const auto pUpgradeExt = BuildingTypeExt::TryFetch(pUpgrade)) { @@ -251,10 +251,10 @@ namespace SuperWeaponStatusTemp const bool hasPower = pHouse->HasFullPower(); - for (const auto pSuper : pHouse->Supers) + for (auto pSuper : pHouse->Supers) { - const auto index = pSuper->Type->ArrayIndex; - auto& status = statuses[index]; + auto index = pSuper->Type->ArrayIndex; + auto& status = Statuses[index]; if (SessionClass::Instance.GameMode != GameMode::Campaign && !Unsorted::SWAllowed) { @@ -267,7 +267,7 @@ namespace SuperWeaponStatusTemp } } - return statuses; + return Statuses; } } @@ -278,14 +278,14 @@ static int __cdecl Ares_HouseClass_UpdateSuperWeaponsUnavailable(REGISTERS* R) if (!pThis->Defeated) { - auto statuses = SuperWeaponStatusTemp::GetStatuses(pThis); + auto Statuses = SuperWeaponStatusTemp::GetStatuses(pThis); - for (const auto pSuper : pThis->Supers) + for (auto pSuper : pThis->Supers) { if (!pSuper->IsPresent || pSuper->IsOneTime) { - const auto index = pSuper->Type->ArrayIndex; - auto& status = statuses[index]; + auto index = pSuper->Type->ArrayIndex; + auto& status = Statuses[index]; if (status.Available) { @@ -297,7 +297,7 @@ static int __cdecl Ares_HouseClass_UpdateSuperWeaponsUnavailable(REGISTERS* R) if (pExt->SW_ShowCameo) { MouseClass::Instance.AddCameo(AbstractType::Special, index); - const int idxTab = SidebarClass::GetObjectTabIdx(SuperClass::AbsID, index, 0); + int idxTab = SidebarClass::GetObjectTabIdx(SuperClass::AbsID, index, 0); MouseClass::Instance.RepaintSidebar(idxTab); } } @@ -316,6 +316,7 @@ static int __cdecl Ares_SidebarClass_ProcessCameoClick_SuperWeapons(REGISTERS* R const auto pSuper = HouseClass::CurrentPlayer->Supers[idxSW]; const auto pExt = SWTypeExt::Fetch(pSuper->Type); + // Decoupled: manual is decided by SW.ManualFire alone. const bool manual = !pExt->SW_ManualFire; const bool unstoppable = pSuper->Type->UseChargeDrain && pSuper->ChargeDrainState == ChargeDrainState::Draining From 41c6fde70f84fbb32316cd84d64e0dfb96a7efef Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Mon, 10 Aug 2026 23:22:45 +0800 Subject: [PATCH 5/6] Revert "Refactor SW.AutoFire decoupling to redirect Ares hook bodies" This reverts commit 134e3cc7809212a7723f74e7b8b61d9954e55f9b. --- src/Ext/SWType/Body.cpp | 2 - src/Ext/SWType/Body.h | 2 - src/Misc/Hooks.Ares.cpp | 206 ++++------------------------------------ 3 files changed, 19 insertions(+), 191 deletions(-) diff --git a/src/Ext/SWType/Body.cpp b/src/Ext/SWType/Body.cpp index 480aa70ffc..531a66fb0d 100644 --- a/src/Ext/SWType/Body.cpp +++ b/src/Ext/SWType/Body.cpp @@ -31,7 +31,6 @@ void SWTypeExt::Serialize(T& Stm) .Process(this->SW_Unstoppable) .Process(this->SW_AllowPlayer) .Process(this->SW_AllowAI) - .Process(this->SW_AlwaysGranted) .Process(this->SW_Inhibitors) .Process(this->SW_AnyInhibitor) .Process(this->SW_Designators) @@ -130,7 +129,6 @@ void SWTypeExt::LoadFromINIFile(CCINIClass* const pINI) this->SW_Unstoppable.Read(exINI, pSection, "SW.Unstoppable"); this->SW_AllowPlayer.Read(exINI, pSection, "SW.AllowPlayer"); this->SW_AllowAI.Read(exINI, pSection, "SW.AllowAI"); - this->SW_AlwaysGranted.Read(exINI, pSection, "SW.AlwaysGranted"); this->SW_Inhibitors.Read(exINI, pSection, "SW.Inhibitors"); this->SW_AnyInhibitor.Read(exINI, pSection, "SW.AnyInhibitor"); this->SW_Designators.Read(exINI, pSection, "SW.Designators"); diff --git a/src/Ext/SWType/Body.h b/src/Ext/SWType/Body.h index 3836e4684c..e064cdcfdb 100644 --- a/src/Ext/SWType/Body.h +++ b/src/Ext/SWType/Body.h @@ -36,7 +36,6 @@ class SWTypeExt final : public AbstractTypeExt Valueable SW_Unstoppable; Valueable SW_AllowPlayer; Valueable SW_AllowAI; - Valueable SW_AlwaysGranted; ValueableVector SW_Inhibitors; Valueable SW_AnyInhibitor; ValueableVector SW_Designators; @@ -139,7 +138,6 @@ class SWTypeExt final : public AbstractTypeExt , SW_Unstoppable { false } , SW_AllowPlayer { true } , SW_AllowAI { true } - , SW_AlwaysGranted { false } , SW_Inhibitors {} , SW_AnyInhibitor { false } , SW_Designators { } diff --git a/src/Misc/Hooks.Ares.cpp b/src/Misc/Hooks.Ares.cpp index 78cc8d79cd..63389fe7c8 100644 --- a/src/Misc/Hooks.Ares.cpp +++ b/src/Misc/Hooks.Ares.cpp @@ -1,16 +1,14 @@ -#include +#include #include #include #include #include -#include #include #include #include #include #include -#include #include @@ -177,186 +175,6 @@ static bool __fastcall ParadropPlaneUnlimbo(AircraftClass* pThis, void* _, const #pragma endregion -#pragma region AresAutoFireDecoupling - -namespace SuperWeaponStatusTemp -{ - struct Status - { - bool Available; - bool PowerSourced; - bool Charging; - }; - - // Re-implements Ares' GetSuperWeaponStatuses() - std::vector GetStatuses(HouseClass* pHouse) - { - std::vector Statuses(pHouse->Supers.Count, { false, false, false }); - - if (!pHouse->Defeated && !pHouse->IsObserver()) - { - for (int i = 0; i < pHouse->Supers.Count; ++i) - { - const auto pData = SWTypeExt::Fetch(pHouse->Supers[i]->Type); - if (pData->SW_AlwaysGranted && pData->IsAvailable(pHouse)) - Statuses[i] = { true, true, true }; - } - - for (auto pBld : pHouse->Buildings) - { - if (pBld->IsAlive && !pBld->InLimbo) - { - auto UpdateStatus = [pBld, pHouse, &Statuses](int idxSW) - { - if (idxSW > -1 && !Statuses[idxSW].Charging) - { - const auto pData = SWTypeExt::Fetch(pHouse->Supers[idxSW]->Type); - - if (!pData->IsAvailable(pHouse)) - return; - - auto& status = Statuses[idxSW]; - status.Available = true; - - if (!status.Charging && pBld->HasPower && !pBld->IsUnderEMP()) - { - status.PowerSourced = true; - - if (!pBld->IsBeingWarpedOut() - && pBld->CurrentMission != Mission::Construction - && pBld->CurrentMission != Mission::Selling - && pBld->QueuedMission != Mission::Construction - && pBld->QueuedMission != Mission::Selling) - { - status.Charging = true; - } - } - } - }; - - for (auto pUpgrade : pBld->Upgrades) - { - if (const auto pUpgradeExt = BuildingTypeExt::TryFetch(pUpgrade)) - { - for (int i = 0; i < 2 + (int)pUpgradeExt->SuperWeapons.size(); ++i) - UpdateStatus(pUpgradeExt->GetSuperWeaponIndex(i, pHouse)); - } - } - - const auto pTypeExt = BuildingTypeExt::Fetch(pBld->Type); - for (int i = 0; i < 2 + (int)pTypeExt->SuperWeapons.size(); ++i) - UpdateStatus(pTypeExt->GetSuperWeaponIndex(i)); - } - } - - const bool hasPower = pHouse->HasFullPower(); - - for (auto pSuper : pHouse->Supers) - { - auto index = pSuper->Type->ArrayIndex; - auto& status = Statuses[index]; - - if (SessionClass::Instance.GameMode != GameMode::Campaign && !Unsorted::SWAllowed) - { - if (pSuper->Type->DisableableFromShell) - status.Available = false; - } - - if (pSuper->IsPowered()) - status.PowerSourced &= hasPower; - } - } - - return Statuses; - } -} - -// Decoupled: SW.ShowCameo now hides the cameo regardless of SW.AutoFire. -static int __cdecl Ares_HouseClass_UpdateSuperWeaponsUnavailable(REGISTERS* R) -{ - GET(HouseClass*, pThis, ECX); - - if (!pThis->Defeated) - { - auto Statuses = SuperWeaponStatusTemp::GetStatuses(pThis); - - for (auto pSuper : pThis->Supers) - { - if (!pSuper->IsPresent || pSuper->IsOneTime) - { - auto index = pSuper->Type->ArrayIndex; - auto& status = Statuses[index]; - - if (status.Available) - { - pSuper->Grant(false, pThis->IsCurrentPlayer(), !status.PowerSourced); - - if (pThis->IsCurrentPlayer()) - { - const auto pExt = SWTypeExt::Fetch(pSuper->Type); - if (pExt->SW_ShowCameo) - { - MouseClass::Instance.AddCameo(AbstractType::Special, index); - int idxTab = SidebarClass::GetObjectTabIdx(SuperClass::AbsID, index, 0); - MouseClass::Instance.RepaintSidebar(idxTab); - } - } - } - } - } - } - - return 0x50B36E; -} - -// Decoupled: SW.ManualFire now disallows manual firing regardless of SW.AutoFire. -static int __cdecl Ares_SidebarClass_ProcessCameoClick_SuperWeapons(REGISTERS* R) -{ - GET(int, idxSW, ESI); - const auto pSuper = HouseClass::CurrentPlayer->Supers[idxSW]; - const auto pExt = SWTypeExt::Fetch(pSuper->Type); - - // Decoupled: manual is decided by SW.ManualFire alone. - const bool manual = !pExt->SW_ManualFire; - const bool unstoppable = pSuper->Type->UseChargeDrain - && pSuper->ChargeDrainState == ChargeDrainState::Draining - && pExt->SW_Unstoppable; - - if (!manual && !pSuper->CanFire()) - { - VoxClass::PlayIndex(pExt->EVA_Impatient); - return 0x6AAFB1; - } - - if (!HouseClass::CurrentPlayer->CanTransactMoney(pExt->Money_Amount)) - { - VoxClass::PlayIndex(pExt->EVA_InsufficientFunds); - pExt->PrintMessage(pExt->Message_InsufficientFunds, HouseClass::CurrentPlayer); - return 0x6AAFB1; - } - - if (!pExt->SW_UseAITargeting - || (AresFunctions::IsTargetConstraintsEligible - && AresFunctions::IsTargetConstraintsEligible(AresFunctions::SWTypeExtMap_Find(pSuper->Type), HouseClass::CurrentPlayer, true))) - { - if (manual || unstoppable) - return 0x6AAFB1; - - if (pSuper->Type->Action == Action::None || pExt->SW_UseAITargeting) - { - R->EAX(HouseClass::CurrentPlayer); - return 0x6AAF10; - } - - return 0x6AAF46; - } - - pExt->PrintMessage(pExt->Message_CannotFire, HouseClass::CurrentPlayer); - return 0x6AAFB1; -} - -#pragma endregion - DEFINE_HOOK(0x440580, BuildingClass_Unlimbo_UnitDeliveryFix, 0x5) { if (UnitDeliveryTemp::Placing) @@ -476,9 +294,16 @@ void Apply_Ares3_0_Patches() Patch::Apply_CALL(AresHelper::AresBaseAddress + 0x4CE84, &BuildingExt::UpdateFactoryQueues); // Decouple SW.ShowCameo from SW.AutoFire - Ares' HouseClass_UpdateSuperWeaponsUnavailable - Patch::Apply_LJMP(AresHelper::AresBaseAddress + 0x39580, GET_OFFSET(Ares_HouseClass_UpdateSuperWeaponsUnavailable)); + Patch::Apply_RAW(AresHelper::AresBaseAddress + 0x3962C, { + 0x80, 0xB8, 0xE5, 0x01, 0x00, 0x00, 0x00, // cmp byte ptr [eax+0x1E5], 0 (SW_ShowCameo) + 0x74, 0x2F, // jz -> skip AddCameo (hide) + 0xEB, 0x07, // jmp -> AddCameo (show) + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 + }); // Decouple SW.ManualFire from SW.AutoFire - Ares' SidebarClass_ProcessCameoClick_SuperWeapons - Patch::Apply_LJMP(AresHelper::AresBaseAddress + 0x34DA0, GET_OFFSET(Ares_SidebarClass_ProcessCameoClick_SuperWeapons)); + Patch::Apply_RAW(AresHelper::AresBaseAddress + 0x34DD0, { + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 // nop cmp [esi+0x1E1],0; jz (AutoFire check) + }); } void Apply_Ares3_0p1_Patches() @@ -592,7 +417,14 @@ void Apply_Ares3_0p1_Patches() Patch::Apply_CALL(AresHelper::AresBaseAddress + 0x4DAF4, &BuildingExt::UpdateFactoryQueues); // Decouple SW.ShowCameo from SW.AutoFire - Ares' HouseClass_UpdateSuperWeaponsUnavailable - Patch::Apply_LJMP(AresHelper::AresBaseAddress + 0x3A000, GET_OFFSET(Ares_HouseClass_UpdateSuperWeaponsUnavailable)); + Patch::Apply_RAW(AresHelper::AresBaseAddress + 0x3A0AC, { + 0x80, 0xB8, 0xE5, 0x01, 0x00, 0x00, 0x00, // cmp byte ptr [eax+0x1E5], 0 (SW_ShowCameo) + 0x74, 0x2F, // jz -> skip AddCameo (hide) + 0xEB, 0x07, // jmp -> AddCameo (show) + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 + }); // Decouple SW.ManualFire from SW.AutoFire - Ares' SidebarClass_ProcessCameoClick_SuperWeapons - Patch::Apply_LJMP(AresHelper::AresBaseAddress + 0x357E0, GET_OFFSET(Ares_SidebarClass_ProcessCameoClick_SuperWeapons)); + Patch::Apply_RAW(AresHelper::AresBaseAddress + 0x35810, { + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 // nop cmp [esi+0x1E1],0; jz (AutoFire check) + }); } From c8deacacbef12120bcacc66cccab855f0cf31f47 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Mon, 10 Aug 2026 23:24:00 +0800 Subject: [PATCH 6/6] Convert to LJMP syntax --- src/Misc/Hooks.Ares.cpp | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/Misc/Hooks.Ares.cpp b/src/Misc/Hooks.Ares.cpp index 63389fe7c8..ace8948e13 100644 --- a/src/Misc/Hooks.Ares.cpp +++ b/src/Misc/Hooks.Ares.cpp @@ -294,16 +294,9 @@ void Apply_Ares3_0_Patches() Patch::Apply_CALL(AresHelper::AresBaseAddress + 0x4CE84, &BuildingExt::UpdateFactoryQueues); // Decouple SW.ShowCameo from SW.AutoFire - Ares' HouseClass_UpdateSuperWeaponsUnavailable - Patch::Apply_RAW(AresHelper::AresBaseAddress + 0x3962C, { - 0x80, 0xB8, 0xE5, 0x01, 0x00, 0x00, 0x00, // cmp byte ptr [eax+0x1E5], 0 (SW_ShowCameo) - 0x74, 0x2F, // jz -> skip AddCameo (hide) - 0xEB, 0x07, // jmp -> AddCameo (show) - 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 - }); + Patch::Apply_LJMP(AresHelper::AresBaseAddress + 0x39635, AresHelper::AresBaseAddress + 0x39664); // Decouple SW.ManualFire from SW.AutoFire - Ares' SidebarClass_ProcessCameoClick_SuperWeapons - Patch::Apply_RAW(AresHelper::AresBaseAddress + 0x34DD0, { - 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 // nop cmp [esi+0x1E1],0; jz (AutoFire check) - }); + Patch::Apply_LJMP(AresHelper::AresBaseAddress + 0x34DD0, AresHelper::AresBaseAddress + 0x34DD9); } void Apply_Ares3_0p1_Patches() @@ -417,14 +410,7 @@ void Apply_Ares3_0p1_Patches() Patch::Apply_CALL(AresHelper::AresBaseAddress + 0x4DAF4, &BuildingExt::UpdateFactoryQueues); // Decouple SW.ShowCameo from SW.AutoFire - Ares' HouseClass_UpdateSuperWeaponsUnavailable - Patch::Apply_RAW(AresHelper::AresBaseAddress + 0x3A0AC, { - 0x80, 0xB8, 0xE5, 0x01, 0x00, 0x00, 0x00, // cmp byte ptr [eax+0x1E5], 0 (SW_ShowCameo) - 0x74, 0x2F, // jz -> skip AddCameo (hide) - 0xEB, 0x07, // jmp -> AddCameo (show) - 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 - }); + Patch::Apply_LJMP(AresHelper::AresBaseAddress + 0x3A0B5, AresHelper::AresBaseAddress + 0x3A0E4); // Decouple SW.ManualFire from SW.AutoFire - Ares' SidebarClass_ProcessCameoClick_SuperWeapons - Patch::Apply_RAW(AresHelper::AresBaseAddress + 0x35810, { - 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 // nop cmp [esi+0x1E1],0; jz (AutoFire check) - }); + Patch::Apply_LJMP(AresHelper::AresBaseAddress + 0x35810, AresHelper::AresBaseAddress + 0x35819); }