Created
September 3, 2022 20:41
-
-
Save HelloKitty/a9cda69f542c8d0850a249cb128d535e to your computer and use it in GitHub Desktop.
Patching Spell Data in 3.3.5 Client at Runtime
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @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]); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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