Skip to content

Instantly share code, notes, and snippets.

@HelloKitty
Created September 3, 2022 20:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save HelloKitty/a9cda69f542c8d0850a249cb128d535e to your computer and use it in GitHub Desktop.
Save HelloKitty/a9cda69f542c8d0850a249cb128d535e to your computer and use it in GitHub Desktop.
Patching Spell Data in 3.3.5 Client at Runtime
// @HelloKitty: Core stuff
//From Tomrus88 and HelloKitty's DLL
#if WIN32
__pragma(pack(push, 1))
struct SpellRec
#else
struct __attribute__((__packed__)) SpellRec
#endif
{
uint32 m_ID;
uint32 m_category;
uint32 m_dispelType;
uint32 m_mechanic;
uint32 m_attributes; //SpellAttributes
uint32 m_attributesEx; //SpellAtrributeEx1
uint32 m_attributesExB; //SpellAttributeEx2
uint32 m_attributesExC; //SpellAttributeEx3
uint32 m_attributesExD; //SpellAttributeEx4
uint32 m_attributesExE; //SpellAttributeEx5
uint32 m_attributesExF; //SpellAttributeEx6
uint32 m_attributesExG; //SpellAttributeEx7
uint64 m_shapeshiftMask;
uint64 m_shapeshiftExclude;
uint32 m_targets;
uint32 m_targetCreatureType;
uint32 m_requiresSpellFocus;
uint32 m_facingCasterFlags;
uint32 m_casterAuraState;
uint32 m_targetAuraState;
uint32 m_excludeCasterAuraState;
uint32 m_excludeTargetAuraState;
uint32 m_casterAuraSpell;
uint32 m_targetAuraSpell;
uint32 m_excludeCasterAuraSpell;
uint32 m_excludeTargetAuraSpell;
uint32 m_castingTimeIndex;
uint32 m_recoveryTime;
uint32 m_categoryRecoveryTime;
SpellInterruptFlags m_interruptFlags;
uint32 m_auraInterruptFlags;
uint32 m_channelInterruptFlags;
uint32 m_procTypeMask;
uint32 m_procChance;
uint32 m_procCharges;
uint32 m_maxLevel;
uint32 m_baseLevel;
uint32 m_spellLevel;
uint32 m_durationIndex;
uint32 m_powerType;
uint32 m_manaCost;
uint32 m_manaCostPerLevel;
uint32 m_manaPerSecond;
uint32 m_manaPerSecondPerLevel;
uint32 m_rangeIndex;
float m_speed;
uint32 m_modalNextSpell;
uint32 m_cumulativeAura;
uint32 m_totem[2];
uint32 m_reagent[8];
uint32 m_reagentCount[8];
uint32 m_equippedItemClass;
uint32 m_equippedItemSubclass;
uint32 m_equippedItemInvTypes;
SpellEffects m_effect[3];
uint32 m_effectDieSides[3];
float m_effectRealPointsPerLevel[3];
uint32 m_effectBasePoints[3];
uint32 m_effectMechanic[3];
uint32 m_implicitTargetA[3];
uint32 m_implicitTargetB[3];
uint32 m_effectRadiusIndex[3];
uint32 m_effectAura[3];
float m_effectAmplitude[3];
uint32 m_effectMultipleValue[3];
uint32 m_effectChainTargets[3];
uint32 m_effectItemType[3];
uint32 m_effectMiscValue[3];
uint32 m_effectMiscValueB[3];
uint32 m_effectTriggerSpell[3];
float m_effectPointsPerCombo[3];
uint32 m_effectSpellClassMaskA[3];
uint32 m_effectSpellClassMaskB[3];
uint32 m_effectSpellClassMaskC[3];
uint32 m_spellVisualID[2];
uint32 m_spellIconID;
uint32 m_activeIconID;
uint32 m_spellPriority;
uint32 m_name_langPtr; //136
uint32 m_nameSubtext_langPtr;
uint32 m_description_langPtr;
uint32 m_auraDescription_langPtr;
uint32 m_manaCostPct;
uint32 m_startRecoveryCategory;
uint32 m_startRecoveryTime;
uint32 m_maxTargetLevel;
SpellFamilyNames m_spellClassSet;
uint32 m_spellClassMask[3];
uint32 m_maxTargets;
uint32 m_defenseType;
uint32 m_preventionType;
uint32 m_stanceBarOrder;
float m_effectChainAmplitude[3];
uint32 m_minFactionID;
uint32 m_minReputation;
uint32 m_requiredAuraVision;
uint32 m_requiredTotemCategoryID[2];
uint32 m_requiredAreasID;
uint32 m_schoolMask;
uint32 m_runeCostID;
uint32 m_spellMissileID;
uint32 m_powerDisplayID;
float m_effectBonusCoefficient[3];
uint32 m_descriptionVariablesID;
uint32 m_difficulty;
};
#if WIN32
__pragma(pack(pop))
#endif
/// <summary>
/// SMSG_PATCH_SPELL: Server packet sent to the client to patch the client's loaded Spell DBC.
/// </summary>
class TC_GAME_API PatchSpellDbcEventPacket : public HelloKittyServerPacket
{
public:
PatchSpellDbcEventPacket(SpellInfo const* spell)
: HelloKittyServerPacket(HelloKittyOpcodes::SMSG_PATCH_SPELL),
_SpellReference(spell)
{
}
// Fields
ReadonlyProp(SpellInfo const*, SpellReference);
protected:
void Serialize() override;
};
void WorldPackets::HelloKitty::PatchSpellDbcEventPacket::Serialize()
{
SpellRec spell;
//We must translate the SpellInfo to the spell rec.
SpellInfo const& info = *GetSpellReference();
spell.m_ID = info.Id;
spell.m_category = info.CategoryEntry ? info.CategoryEntry->Id : 0;
spell.m_dispelType = info.Dispel;
spell.m_mechanic = info.Mechanic;
spell.m_attributes = info.Attributes;
spell.m_attributesEx = info.AttributesEx;
spell.m_attributesExB = info.AttributesEx2;
spell.m_attributesExC = info.AttributesEx3;
spell.m_attributesExD = info.AttributesEx4;
spell.m_attributesExE = info.AttributesEx5;
spell.m_attributesExF = info.AttributesEx6;
spell.m_attributesExG = info.AttributesEx7;
spell.m_shapeshiftMask = info.Stances;
spell.m_shapeshiftExclude = info.StancesNot;
spell.m_targets = info.Targets;
spell.m_targetCreatureType = info.TargetCreatureType;
spell.m_requiresSpellFocus = info.RequiresSpellFocus;
spell.m_facingCasterFlags = info.FacingCasterFlags;
spell.m_casterAuraState = info.CasterAuraState;
spell.m_targetAuraState = info.TargetAuraState;
spell.m_excludeCasterAuraState = info.CasterAuraStateNot;
spell.m_excludeTargetAuraState = info.TargetAuraStateNot;
spell.m_casterAuraSpell = info.CasterAuraSpell;
spell.m_targetAuraSpell = info.TargetAuraSpell;
spell.m_excludeCasterAuraSpell = info.ExcludeCasterAuraSpell;
spell.m_excludeTargetAuraSpell = info.ExcludeTargetAuraSpell;
spell.m_castingTimeIndex = info.CastTimeEntry ? info.CastTimeEntry->ID : 0;
spell.m_recoveryTime = info.RecoveryTime;
spell.m_categoryRecoveryTime = info.CategoryRecoveryTime;
spell.m_interruptFlags = (SpellInterruptFlags)info.InterruptFlags;
spell.m_auraInterruptFlags = info.AuraInterruptFlags;
spell.m_channelInterruptFlags = info.ChannelInterruptFlags;
spell.m_procTypeMask = info.ProcFlags;
spell.m_procChance = info.ProcChance;
spell.m_procCharges = info.ProcCharges;
spell.m_maxLevel = info.MaxLevel;
spell.m_baseLevel = info.BaseLevel;
spell.m_spellLevel = info.SpellLevel;
spell.m_durationIndex = info.DurationEntry ? info.DurationEntry->ID : 0;
spell.m_powerType = info.PowerType;
spell.m_manaCost = info.ManaCost;
spell.m_manaCostPerLevel = info.ManaCostPerlevel;
spell.m_manaPerSecond = info.ManaPerSecond;
spell.m_manaPerSecondPerLevel = info.ManaPerSecondPerLevel;
spell.m_rangeIndex = info.RangeEntry ? info.RangeEntry->ID : 0;
spell.m_speed = info.Speed;
//TODO: NEED MODAL NEXT SPELL!
//TODO: NEED CUMULATIVE AURA!!
spell.m_totem[0] = info.Totem[0];
spell.m_totem[1] = info.Totem[1];
for (uint32 i = 0; i < MAX_SPELL_REAGENTS; i++)
{
spell.m_reagent[i] = info.Reagent[i];
spell.m_reagentCount[i] = info.ReagentCount[i];
}
spell.m_equippedItemClass = info.EquippedItemClass;
spell.m_equippedItemSubclass = info.EquippedItemSubClassMask;
spell.m_equippedItemInvTypes = info.EquippedItemInventoryTypeMask;
for (uint32 i = 0; i < MAX_SPELL_EFFECTS; i++)
{
spell.m_effect[i] = (SpellEffects)info.Effects[i].Effect;
spell.m_effectDieSides[i] = info.Effects[i].DieSides;
spell.m_effectRealPointsPerLevel[i] = info.Effects[i].RealPointsPerLevel;
spell.m_effectBasePoints[i] = info.Effects[i].BasePoints;
spell.m_effectMechanic[i] = info.Effects[i].Mechanic;
spell.m_implicitTargetA[i] = (uint32)info.Effects[i].TargetA.GetTarget();
spell.m_implicitTargetB[i] = (uint32)info.Effects[i].TargetB.GetTarget();
spell.m_effectRadiusIndex[i] = info.Effects[i].RadiusEntry ? info.Effects[i].RadiusEntry->ID : 0;
spell.m_effectAura[i] = info.Effects[i].ApplyAuraName;
spell.m_effectAmplitude[i] = info.Effects[i].Amplitude;
//TODO: Is this ok?
spell.m_effectMultipleValue[i] = info.Effects[i].ValueMultiplier;
spell.m_effectChainTargets[i] = info.Effects[i].ChainTarget;
spell.m_effectItemType[i] = info.Effects[i].ItemType;
spell.m_effectMiscValue[i] = info.Effects[i].MiscValue;
spell.m_effectMiscValueB[i] = info.Effects[i].MiscValueB;
spell.m_effectTriggerSpell[i] = info.Effects[i].TriggerSpell;
spell.m_effectPointsPerCombo[i] = info.Effects[i].PointsPerComboPoint;
spell.m_effectSpellClassMaskA[i] = info.Effects[i].SpellClassMask.GetParts()[0];
spell.m_effectSpellClassMaskB[i] = info.Effects[i].SpellClassMask.GetParts()[1];
spell.m_effectSpellClassMaskC[i] = info.Effects[i].SpellClassMask.GetParts()[2];
//In DBC layout this seems to be correct, in SpellRec this is same position as this in DB
spell.m_effectChainAmplitude[i] = info.Effects[i].DamageMultiplier;
spell.m_effectBonusCoefficient[i] = info.Effects[i].BonusMultiplier;
}
spell.m_spellVisualID[0] = info.SpellVisual[0];
spell.m_spellVisualID[1] = info.SpellVisual[1];
spell.m_spellIconID = info.SpellIconID;
spell.m_activeIconID = info.ActiveIconID;
spell.m_spellPriority = info.Priority;
//We skip all the char pointers, they'll be random pointers but clientside we init them to the correct strings.
spell.m_manaCostPct = info.ManaCostPercentage;
spell.m_startRecoveryCategory = info.StartRecoveryCategory;
spell.m_startRecoveryTime = info.StartRecoveryTime;
spell.m_maxTargetLevel = info.MaxTargetLevel;
spell.m_spellClassSet = (SpellFamilyNames)info.SpellFamilyName;
for (uint32 i = 0; i < 3; i++)
spell.m_spellClassMask[i] = info.SpellFamilyFlags.GetParts()[i];
spell.m_maxTargets = info.MaxAffectedTargets;
spell.m_defenseType = info.DmgClass;
spell.m_preventionType = info.PreventionType;
spell.m_stanceBarOrder = info.StanceBarOrder;
spell.m_minFactionID = info.MinimumFactionId;
spell.m_minReputation = info.MinimumReputation;
spell.m_requiredAreasID = info.AreaGroupId;
spell.m_schoolMask = info.SchoolMask;
spell.m_runeCostID = info.RuneCostID;
spell.m_spellMissileID = info.SpellMissileId;
spell.m_powerDisplayID = info.PowerDisplayId;
spell.m_descriptionVariablesID = info.SpellDescriptionVariableId;
spell.m_difficulty = info.SpellDifficultyId;
spell.m_requiredAuraVision = info.RequiredAuraVision;
spell.m_requiredTotemCategoryID[0] = info.TotemCategory[0];
spell.m_requiredTotemCategoryID[1] = info.TotemCategory[1];
//Because of endianess we have issues where the data is nonsense from Linux to Windows lol
for (uint32 i = 0; i < (sizeof(SpellRec) / sizeof(uint32)); i++)
EndianConvert(*(reinterpret_cast<uint8*>(&spell) + i * sizeof(uint32)));
// Avoid copy by calling append instead of write which may copy, not 100% sure.
GetBuffer().append(reinterpret_cast<uint8*>(&spell), sizeof(SpellRec));
// New and improved version expanded upon below! Strings?? Woaaaaaah boys, why are you even reading this get back to work.
GetBuffer().writeLengthPrefixedString(info.SpellName[0]);
GetBuffer().writeLengthPrefixedString(info.Description[0]);
GetBuffer().writeLengthPrefixedString(info.Rank[0]);
GetBuffer().writeLengthPrefixedString(info.Tooltip[0]);
}
PacketHandlerFunction(SMSG_PATCH_SPELL_Handler)
{
//SMSG_PATCH_SPELL
auto rec = msg->Read<SpellRec>();
//Noooow we have something complex to deal with, to support it not crashing
//inbetween versions of the Core implementation we should check if we have any
//bytes left
if (msg->HasData())
{
/*
GetBuffer().writeLengthPrefixedString(info.SpellName[0]);
GetBuffer().writeLengthPrefixedString(info.Description[0]);
GetBuffer().writeLengthPrefixedString(info.Rank[0]);
GetBuffer().writeLengthPrefixedString(info.Tooltip[0]);
*/
// TODO: This leaks but don't care really
auto name = msg->GetString();
rec.m_name_lang = new char[name.length() + 1];
strcpy_s(rec.m_name_lang, name.length() + 1, name.c_str());
auto description = msg->GetString();
rec.m_description_lang = new char[description.length() + 1];
strcpy_s(rec.m_description_lang, description.length() + 1, description.c_str());
auto rank = msg->GetString();
rec.m_nameSubtext_lang = new char[rank.length() + 1];
strcpy_s(rec.m_nameSubtext_lang, rank.length() + 1, rank.c_str());
auto tooltip = msg->GetString();
rec.m_auraDescription_lang = new char[tooltip.length() + 1];
strcpy_s(rec.m_auraDescription_lang, tooltip.length() + 1, tooltip.c_str());
}
else
{
rec.m_name_lang = nullptr;
rec.m_description_lang = nullptr;
rec.m_nameSubtext_lang = nullptr;
rec.m_auraDescription_lang = nullptr;
}
DBClientMgr::PatchSpell(rec);
return true;
}
static void PatchSpell(SpellRec record)
{
auto originalSpell = GetSpellDBClient()->GetLocalizedRow<SpellRec>(record.m_ID);
if (originalSpell)
{
/*
char* m_name_lang; //136
char* m_nameSubtext_lang;
char* m_description_lang;
char* m_auraDescription_lang;
*/
// If we don't have any spell text we should copy the original, otherwise we had some sent.
if (!record.m_name_lang)
{
record.m_name_lang = originalSpell->m_name_lang;
record.m_nameSubtext_lang = originalSpell->m_nameSubtext_lang;
record.m_description_lang = originalSpell->m_description_lang;
record.m_auraDescription_lang = originalSpell->m_auraDescription_lang;
}
record.m_cumulativeAura = originalSpell->m_cumulativeAura;
record.m_modalNextSpell = originalSpell->m_modalNextSpell;
}
else
{
// If we don't have a name pointer we should init it to some placeholder shit
if (!record.m_name_lang)
{
record.m_name_lang = const_cast<char*>((new std::string("TODO"))->c_str());
record.m_nameSubtext_lang = const_cast<char*>((new std::string("TODO"))->c_str());
record.m_description_lang = const_cast<char*>((new std::string("TODO"))->c_str());
record.m_auraDescription_lang = const_cast<char*>((new std::string("TODO"))->c_str());
}
}
PatchedSpells[record.m_ID] = record;
}
// @HelloKitty: The offset in 3.3.5 for this is ClientDb__Unpack = 0x4cfbb0
void __cdecl ClientDb__Unpack(char* param_1, int size, char* outValue)
{
DetourMgr::DetourCallOriginal(ClientDb__Unpack)(param_1, size, outValue);
// To handle runtime cast interrupt changes we check if we're reading SpellDBC first
if (0x2a8 == size) //Hack but it indicates it's a spell DBC
{
auto rec = static_cast<SpellRec*>((void*)outValue);
//Here we check if we need to use the patched spell.
if (DBClientMgr::Instance().PatchedSpells.find(rec->m_ID) != DBClientMgr::Instance().PatchedSpells.end())
{
SpellRec& var = DBClientMgr::Instance().PatchedSpells[rec->m_ID];
//SpellRec* rec = static_cast<SpellRec*>((void*)outValue);
memcpy(outValue, reinterpret_cast<void*>(&var), size);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment