[Retail] Is Spell_C_GetSpellCharges No Longer Relevant? (Notes included) menu

User Tag List

Results 1 to 7 of 7
  1. #1
    CodeBytes's Avatar Member
    Reputation
    14
    Join Date
    Feb 2020
    Posts
    39
    Thanks G/R
    7/7
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    [Retail] Is Spell_C_GetSpellCharges No Longer Relevant? (Notes included)

    Hello,

    I ran into a small obstacle while working with the Warrior spell "Charge" (Spell ID 100). Initially, I was reading s_spellHistory externally and running logic against the data to identify a spell's cooldown. When I ran Charge against this framework, it failed to give the correct cooldown as Charge has a recharge timer and can take multiple charges.

    To overcome this obstacle, I went internal and called Spell_C_GetSpellCooldown. As an Arms Warrior, this was sufficient for checking when Charge would be ready to cast again. When switching to Protection; however, the Charge spell changes. It's ID remains 100; however, it gains one extra charge and the recharge rate changes to 15 seconds. Using Spell_C_GetSpellCooldown is no longer sufficient as it returns a duration of 0 when the spell has 1 or more charges available. This is correct, because the spell is ready to be cast again but if you require logic to save a charge for later; then this collapses. It's possible to store casts as a member in my script object but I'd rather not keep track of information that I can simply query from the client.

    Given that, I moved my attention to Spell_C_GetSpellCharges. Unfortunately, this is no different than my first attempt--externally reading s_spellHistory. It returns a duration of 1.5 seconds and 0 charges when the first charge is used--which is not correct.

    You can see the method being called here:

    Code:
    Offsets are shifted by 10000 because IDA places information at the top of the IDB.
    
    signed __int64 __usercall sub_11DD8C0@<rax>(__int64 a1@<rcx>, __int64 a2@<xmm0>, __int128 *a3@<xmm1>, __m128i *a4@<xmm6>, __m256i *a5@<ymm1>)
    {
      __int64 v5; // rdi@1
      bool v6; // dl@3
      signed int v7; // eax@5
      __int64 v8; // rdx@5
      __int64 v9; // rdx@6
      __int64 v10; // rdx@6
      __int64 v11; // rdx@6
      __int64 v12; // rdx@6
      signed __int64 result; // rax@6
      int v14; // [sp+30h] [bp-18h]@5
      int v15; // [sp+34h] [bp-14h]@5
      int v16; // [sp+58h] [bp+10h]@1
      int v17; // [sp+60h] [bp+18h]@5
      unsigned int v18; // [sp+68h] [bp+20h]@5
    
      v16 = 0;
      v5 = a1;
      if ( (unsigned int)sub_11EFB30(a1, &v16, 0i64, 0i64, a2, a4, a5, 1)
        && ((signed int)sub_11ED320(v16, 1) >= 0 ? (v6 = 1) : (v6 = sub_14342B0(v16) != 0i64),
            v18 = xmmword_20CD27C,
            v7 = sub_B9B210((unsigned int)v16, v6, &v17, &v14, a3, a4, a5, &v15, &v18),          <<<< Here
            v17 > 0) )
      {
        sub_1E0C30(v5, v8, COERCE__INT64((double)v7));
        sub_1E0C30(v5, v9, COERCE__INT64((double)v17));
        sub_1E0C30(v5, v10, COERCE__INT64((double)v14 * 0.001));
        sub_1E0C30(v5, v11, COERCE__INT64((double)v15 * 0.001));
        sub_1E0C30(v5, v12, *(_OWORD *)&_mm_cvtps_pd((__m128)v18));
        result = 5i64;
      }
      else
      {
        result = 0i64;
      }
      return result;
    }
    It appears that the function returns the current number of charges (v7), v17 should be the max number of charges, v14 and v15 should be duration and start time (respectively), and v18 should be the mod rate.

    Here is the function's definition:

    Code:
    __int64 __usercall sub_B9B210@<rax>(__int64 a1@<rcx>, unsigned __int8 a2@<dl>, _DWORD *a3@<r8>, int *a4@<r9>, __int128 *xmm1_8_0@<xmm1>, __m128i *xmm6_8_0@<xmm6>, __m256i *a7@<ymm1>, _DWORD *a5, _DWORD *a6);
    If I use the first charge, Spell_C_GetSpellCharges returns a duration of 1500, and current charges as 0. After 1500 milliseconds, the method returns default full-charge values--even when the spell has one charge on cooldown. If I use the second charge, Spell_C_GetSpellCharges returns a duration of 15000 and current charges as 0. This information can not be used to sufficiently calculate the number of charges left when used against the Warrior spell "Charge".

    This leaves me back where I started. I'm sure many of you have already cracked this, is there anyone here who could point me in the right direction?
    Last edited by CodeBytes; 05-18-2020 at 01:20 PM.

    [Retail] Is Spell_C_GetSpellCharges No Longer Relevant? (Notes included)
  2. #2
    ejt's Avatar Contributor
    Reputation
    209
    Join Date
    Mar 2008
    Posts
    166
    Thanks G/R
    3/111
    Trade Feedback
    0 (0%)
    Mentioned
    4 Post(s)
    Tagged
    0 Thread(s)
    Code:
    uint64_t __cdecl Spell_GetSpellCharges(uint64_t spellId, int8_t a2, uint64_t* maxCharges, uint64_t* cooldownStart, uint64_t* cooldownDuration, uint64_t* chargeModRate)
    Haven't had any issues with it so far but there's the correct definition of the function.

  3. #3
    CodeBytes's Avatar Member
    Reputation
    14
    Join Date
    Feb 2020
    Posts
    39
    Thanks G/R
    7/7
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thanks for your reply ejt. It turns out I was using the function offset for GetSpellCooldown instead of GetSpellCharges, so I was getting incorrect values. Changing the offset fixed the issue for me.

    However! Your definition works, as does mine. But our parameter counts don't match.

    This is the definition I'm using:

    Code:
    int64 __cdecl Spell_GetSpellCharges(uint32 spellId, bool unk/* isPet? */, int64* maxCharges, int64* duration, int64* unk1, int64* unk2, int64* unk3, int64* startTime, int64* modRate)
    If you don't mind me asking, how did you determine your parameters? I'm seeing 9 parameters in IDA

    Code:
    ...
    // Call to the method in the Script_GetSpellCharges function
    sub_B9B210((unsigned int)v16, v6, &v17, &v14, a3, a4, a5, &v15, &v18),
    ...
    // IDA definition
    __int64 __usercall sub_B9B210@<rax>(__int64 a1@<rcx>, unsigned __int8 a2@<dl>, _DWORD *a3@<r8>, int *a4@<r9>, __int128 *xmm1_8_0@<xmm1>, __m128i *xmm6_8_0@<xmm6>, __m256i *a7@<ymm1>, _DWORD *a5, _DWORD *a6);

  4. #4
    ejt's Avatar Contributor
    Reputation
    209
    Join Date
    Mar 2008
    Posts
    166
    Thanks G/R
    3/111
    Trade Feedback
    0 (0%)
    Mentioned
    4 Post(s)
    Tagged
    0 Thread(s)
    Usually IDA can accurately guess the arguments but sometimes it gets confused, going into the functions can sometimes help IDA guess it correctly but sometimes you have to look at how the function is called.

    If you look at x86 Disassembly/Calling Conventions - Wikibooks, open books for an open world you'll see some examples of how it can look like. Its important to learn this because it will help you with finding the correct calling conventions and figuring out arguments when IDA fails you.

  5. Thanks CodeBytes (1 members gave Thanks to ejt for this useful post)
  6. #5
    CodeBytes's Avatar Member
    Reputation
    14
    Join Date
    Feb 2020
    Posts
    39
    Thanks G/R
    7/7
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thank you, I appreciate your input.

  7. #6
    counted's Avatar Contributor Authenticator enabled
    Reputation
    203
    Join Date
    Mar 2008
    Posts
    183
    Thanks G/R
    11/108
    Trade Feedback
    0 (0%)
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)
    I read the spell charge table out of process and it works fine.

    Here is my original post on how to do it.

    https://www.ownedcore.com/forums/wor...llcharges.html (GetSpellCharges)

    You should be able to reverse the current binary offsets from this info and stay out of process if you want.

  8. Thanks CodeBytes (1 members gave Thanks to counted for this useful post)
  9. #7
    CodeBytes's Avatar Member
    Reputation
    14
    Join Date
    Feb 2020
    Posts
    39
    Thanks G/R
    7/7
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I saw your post earlier while I was searching for clues. Thank you.

    My current framework amasses information internally, serializes it, and then sends it over to my external application via a named pipe. It's a lazy method, but my biggest hurdle at the moment is time.

Similar Threads

  1. [Selling] Selling WoW account, with 6 Gladiator Mounts, that is no longer obtainable + Title.
    By AndrewDenmark1992 in forum WoW-EU Account Buy Sell Trade
    Replies: 4
    Last Post: 07-05-2016, 12:05 AM
  2. GameGuard is no longer running.
    By Parog in forum Blade and Soul Exploits|Hacks
    Replies: 50
    Last Post: 03-04-2016, 11:56 AM
  3. Sarkoth is no longer a good idea (Banned on US)
    By f0xm4n in forum Diablo 3 General
    Replies: 7
    Last Post: 07-21-2012, 12:23 AM
  4. API CastSpellByName is NO LONGER PROTECTED!
    By Iaccidentallytwink in forum World of Warcraft Exploits
    Replies: 6
    Last Post: 09-08-2011, 01:37 PM
  5. Flying Piggy is no longer Mod?
    By Nimaasuss in forum Community Chat
    Replies: 32
    Last Post: 06-28-2007, 07:55 AM
All times are GMT -5. The time now is 07:01 AM. Powered by vBulletin® Version 4.2.3
Copyright © 2024 vBulletin Solutions, Inc. All rights reserved. User Alert System provided by Advanced User Tagging (Pro) - vBulletin Mods & Addons Copyright © 2024 DragonByte Technologies Ltd.
Digital Point modules: Sphinx-based search