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]; MultiMap<uint, uint>[] m_spellImmune = new MultiMap<uint, uint>[(int)SpellImmunity.Max];
SpellAuraInterruptFlags m_interruptMask; SpellAuraInterruptFlags m_interruptMask;
SpellAuraInterruptFlags2 m_interruptMask2; 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; SpellHistory _spellHistory;
//Auras //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) 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; WeaponAttackType attType = damageInfo != null ? damageInfo.GetAttackType() : WeaponAttackType.BaseAttack;
if (typeMaskActor && actor != null) if (typeMaskActor && actor != null)
actor.ProcSkillsAndReactives(false, actionTarget, typeMaskActor, hitMask, attType); actor.ProcSkillsAndReactives(false, actionTarget, typeMaskActor, hitMask, attType);
@@ -1625,6 +1632,11 @@ namespace Game.Entities
{ {
Spell triggeringSpell = eventInfo.GetProcSpell(); Spell triggeringSpell = eventInfo.GetProcSpell();
bool disableProcs = triggeringSpell && triggeringSpell.IsProcDisabled(); bool disableProcs = triggeringSpell && triggeringSpell.IsProcDisabled();
int oldProcChainLength = m_procChainLength;
m_procChainLength = Math.Max(m_procChainLength + 1, triggeringSpell != null ? triggeringSpell.GetProcChainLength() : 0);
if (disableProcs) if (disableProcs)
SetCantProc(true); SetCantProc(true);
@@ -1638,6 +1650,8 @@ namespace Game.Entities
if (disableProcs) if (disableProcs)
SetCantProc(false); SetCantProc(false);
m_procChainLength = oldProcChainLength;
} }
void SetCantProc(bool apply) void SetCantProc(bool apply)
@@ -3877,6 +3891,8 @@ namespace Game.Entities
public bool CanProc() { return m_procDeep == 0; } public bool CanProc() { return m_procDeep == 0; }
public int GetProcChainLength() { return m_procChainLength; }
public void _ApplyAuraEffect(Aura aura, uint effIndex) public void _ApplyAuraEffect(Aura aura, uint effIndex)
{ {
Cypher.Assert(aura != null); 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_castId = ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, m_caster.GetMapId(), m_spellInfo.Id, m_caster.GetMap().GenerateLowGuid(HighGuid.Cast));
m_originalCastId = originalCastId; m_originalCastId = originalCastId;
m_SpellVisual.SpellXSpellVisualID = caster.GetCastSpellXSpellVisualId(m_spellInfo); m_SpellVisual.SpellXSpellVisualID = caster.GetCastSpellXSpellVisualId(m_spellInfo);
m_procChainLength = caster.IsUnit() ? caster.ToUnit().GetProcChainLength() : 0;
//Auto Shot & Shoot (wand) //Auto Shot & Shoot (wand)
m_autoRepeat = m_spellInfo.IsAutoRepeatRangedSpell(); m_autoRepeat = m_spellInfo.IsAutoRepeatRangedSpell();
@@ -7991,6 +7992,8 @@ namespace Game.Spells
public bool IsProcDisabled() { return _triggeredCastFlags.HasAnyFlag(TriggerCastFlags.DisallowProcEvents); } public bool IsProcDisabled() { return _triggeredCastFlags.HasAnyFlag(TriggerCastFlags.DisallowProcEvents); }
public bool IsChannelActive() { return m_caster.IsUnit() && m_caster.ToUnit().GetChannelSpellId() != 0; } public bool IsChannelActive() { return m_caster.IsUnit() && m_caster.ToUnit().GetChannelSpellId() != 0; }
public int GetProcChainLength() { return m_procChainLength; }
public bool IsDeletable() public bool IsDeletable()
{ {
return !m_referencedFromCurrentSpell && !m_executedCurrently; 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 // we can't store original aura link to prevent access to deleted auras
// and in same time need aura data and after aura deleting. // and in same time need aura data and after aura deleting.
public SpellInfo m_triggeredByAuraSpell; public SpellInfo m_triggeredByAuraSpell;
int m_procChainLength;
#endregion #endregion
} }
+19
View File
@@ -1518,6 +1518,7 @@ namespace Game.Entities
procEntry.SpellPhaseMask = ProcFlagsSpellPhase.Hit; procEntry.SpellPhaseMask = ProcFlagsSpellPhase.Hit;
procEntry.HitMask = ProcFlagsHit.None; // uses default proc @see SpellMgr::CanSpellTriggerProcOnEvent procEntry.HitMask = ProcFlagsHit.None; // uses default proc @see SpellMgr::CanSpellTriggerProcOnEvent
bool triggersSpell = false;
foreach (var spellEffectInfo in spellInfo.GetEffects()) foreach (var spellEffectInfo in spellInfo.GetEffects())
{ {
if (!spellEffectInfo.IsAura()) if (!spellEffectInfo.IsAura())
@@ -1543,6 +1544,10 @@ namespace Game.Entities
if (spellEffectInfo.CalcValue() <= -100) if (spellEffectInfo.CalcValue() <= -100)
procEntry.HitMask = ProcFlagsHit.Miss; procEntry.HitMask = ProcFlagsHit.Miss;
break; break;
case AuraType.ProcTriggerSpell:
case AuraType.ProcTriggerSpellWithValue:
triggersSpell = spellEffectInfo.TriggerSpell != 0;
break;
default: default:
continue; continue;
} }
@@ -1561,6 +1566,20 @@ namespace Game.Entities
procEntry.Cooldown = spellInfo.ProcCooldown; procEntry.Cooldown = spellInfo.ProcCooldown;
procEntry.Charges = spellInfo.ProcCharges; 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; mSpellProcMap[(spellInfo.Id, spellInfo.Difficulty)] = procEntry;
++count; ++count;
} }