Core/Spells: Fully prevent infinite proc loops and add logging to detect most spells that could possibly trigger this behavior at startup

Port From (https://github.com/TrinityCore/TrinityCore/commit/29eac37a16df2ec14cba89f4d6e28f54ca1a4e25)
This commit is contained in:
hondacrx
2023-07-01 09:24:55 -04:00
parent 04eaeb0fe8
commit 445733704e
4 changed files with 41 additions and 1 deletions
+2 -1
View File
@@ -77,7 +77,8 @@ namespace Game.Entities
MultiMap<uint, uint>[] m_spellImmune = new MultiMap<uint, uint>[(int)SpellImmunity.Max];
SpellAuraInterruptFlags m_interruptMask;
SpellAuraInterruptFlags2 m_interruptMask2;
protected int m_procDeep;
int m_procDeep; // tracked for proc system correctness (what spells should proc what)
int m_procChainLength; // tracked to protect against infinite proc loops (hard limit, will disallow procs even if they should happen)
SpellHistory _spellHistory;
//Auras
+16
View File
@@ -1480,6 +1480,13 @@ namespace Game.Entities
public static void ProcSkillsAndAuras(Unit actor, Unit actionTarget, ProcFlagsInit typeMaskActor, ProcFlagsInit typeMaskActionTarget, ProcFlagsSpellType spellTypeMask, ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, Spell spell, DamageInfo damageInfo, HealInfo healInfo)
{
int ProcChainHardLimit = 10;
if (spell != null && spell.GetProcChainLength() >= ProcChainHardLimit)
{
Log.outError(LogFilter.Spells, $"Unit::ProcSkillsAndAuras: Possible infinite proc loop detected, current triggering spell {spell.GetDebugInfo()}");
return;
}
WeaponAttackType attType = damageInfo != null ? damageInfo.GetAttackType() : WeaponAttackType.BaseAttack;
if (typeMaskActor && actor != null)
actor.ProcSkillsAndReactives(false, actionTarget, typeMaskActor, hitMask, attType);
@@ -1625,6 +1632,11 @@ namespace Game.Entities
{
Spell triggeringSpell = eventInfo.GetProcSpell();
bool disableProcs = triggeringSpell && triggeringSpell.IsProcDisabled();
int oldProcChainLength = m_procChainLength;
m_procChainLength = Math.Max(m_procChainLength + 1, triggeringSpell != null ? triggeringSpell.GetProcChainLength() : 0);
if (disableProcs)
SetCantProc(true);
@@ -1638,6 +1650,8 @@ namespace Game.Entities
if (disableProcs)
SetCantProc(false);
m_procChainLength = oldProcChainLength;
}
void SetCantProc(bool apply)
@@ -3877,6 +3891,8 @@ namespace Game.Entities
public bool CanProc() { return m_procDeep == 0; }
public int GetProcChainLength() { return m_procChainLength; }
public void _ApplyAuraEffect(Aura aura, uint effIndex)
{
Cypher.Assert(aura != null);
+4
View File
@@ -84,6 +84,7 @@ namespace Game.Spells
m_castId = ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, m_caster.GetMapId(), m_spellInfo.Id, m_caster.GetMap().GenerateLowGuid(HighGuid.Cast));
m_originalCastId = originalCastId;
m_SpellVisual.SpellXSpellVisualID = caster.GetCastSpellXSpellVisualId(m_spellInfo);
m_procChainLength = caster.IsUnit() ? caster.ToUnit().GetProcChainLength() : 0;
//Auto Shot & Shoot (wand)
m_autoRepeat = m_spellInfo.IsAutoRepeatRangedSpell();
@@ -7991,6 +7992,8 @@ namespace Game.Spells
public bool IsProcDisabled() { return _triggeredCastFlags.HasAnyFlag(TriggerCastFlags.DisallowProcEvents); }
public bool IsChannelActive() { return m_caster.IsUnit() && m_caster.ToUnit().GetChannelSpellId() != 0; }
public int GetProcChainLength() { return m_procChainLength; }
public bool IsDeletable()
{
return !m_referencedFromCurrentSpell && !m_executedCurrently;
@@ -8161,6 +8164,7 @@ namespace Game.Spells
// we can't store original aura link to prevent access to deleted auras
// and in same time need aura data and after aura deleting.
public SpellInfo m_triggeredByAuraSpell;
int m_procChainLength;
#endregion
}
+19
View File
@@ -1518,6 +1518,7 @@ namespace Game.Entities
procEntry.SpellPhaseMask = ProcFlagsSpellPhase.Hit;
procEntry.HitMask = ProcFlagsHit.None; // uses default proc @see SpellMgr::CanSpellTriggerProcOnEvent
bool triggersSpell = false;
foreach (var spellEffectInfo in spellInfo.GetEffects())
{
if (!spellEffectInfo.IsAura())
@@ -1543,6 +1544,10 @@ namespace Game.Entities
if (spellEffectInfo.CalcValue() <= -100)
procEntry.HitMask = ProcFlagsHit.Miss;
break;
case AuraType.ProcTriggerSpell:
case AuraType.ProcTriggerSpellWithValue:
triggersSpell = spellEffectInfo.TriggerSpell != 0;
break;
default:
continue;
}
@@ -1561,6 +1566,20 @@ namespace Game.Entities
procEntry.Cooldown = spellInfo.ProcCooldown;
procEntry.Charges = spellInfo.ProcCharges;
if (spellInfo.HasAttribute(SpellAttr3.CanProcFromProcs) && !procEntry.SpellFamilyMask
&& procEntry.Chance >= 100
&& spellInfo.ProcBasePPM <= 0.0f
&& procEntry.Cooldown <= 0
&& procEntry.Charges <= 0
&& procEntry.ProcFlags.HasFlag(ProcFlags.DealMeleeAbility | ProcFlags.DealRangedAttack | ProcFlags.DealRangedAbility | ProcFlags.DealHelpfulAbility
| ProcFlags.DealHarmfulAbility | ProcFlags.DealHelpfulSpell | ProcFlags.DealHarmfulSpell | ProcFlags.DealHarmfulPeriodic | ProcFlags.DealHelpfulPeriodic)
&& triggersSpell)
{
Log.outError(LogFilter.Sql, $"Spell Id {spellInfo.Id} has SPELL_ATTR3_CAN_PROC_FROM_PROCS attribute and no restriction on what spells can cause it to proc and no cooldown. " +
"This spell can cause infinite proc loops. Proc data for this spell was not generated, data in `spell_proc` table is required for it to function!");
continue;
}
mSpellProcMap[(spellInfo.Id, spellInfo.Difficulty)] = procEntry;
++count;
}