Where is item condition stored in memory?

Discuss coding questions, pull requests, and implementation details.
User avatar
numidium3rd
Posts: 187
Joined: Sun Mar 25, 2018 12:34 am
Location: United States

Re: Where is item condition stored in memory?

Post by numidium3rd »

Elenwel wrote: Mon May 11, 2020 8:30 am

Code: Select all

//a = [6 * ([[[zeroInAllMyTests * 2 + itemDataLocation] + 0028706C] + 00292454]) + charInfoLocation + 0000009D]
playerSkill = pPlayerData->skills[gMagicSchoolsToSkills[gSpellTypeToMagicSchool[pSpell->types[i].major]]].level
//b = [itemDataLocation + 000E] * [002A5E3C]
b = pSpell->duration * gSpellModifier->field_0
x = ((110 - playerSkill) * b) / 100
This is exceedingly helpful. Thank you! I'll do some experimenting with this in DFU to see if the behavior matches classic.

Elenwel
Posts: 7
Joined: Tue Jan 01, 2019 5:14 pm

Re: Where is item condition stored in memory?

Post by Elenwel »

Ok, I think I found the equip/unequip item handlers. And it seems to be bugged in Daggerfall classic. :)

Long story short, the spell cost (calculated using my previous post function) for the last "on held" effect is reduced from item's health when equipping. The cost is calculated using current player stats.

Longer version : a given item can have up to 10 enchantment, most of the time (on stroke, or when time is passing) each enchantment will reduce item's health (for example 10pt per stroke per enchantment with "cast strikes"). Except for equipping item callback (zipped code below):

Code: Select all

SC_OBJECT_DATA *__fastcall SC_inven::96CCF_EquipMagicItem(SC_OBJECT *pItemObj, char inventorySlot)
{
  unsigned __int16 effectType; // ax
  signed int cost; // eax
  signed int effectIdx; // [esp+Ch] [ebp-24h]
  SC_OBJECT_DATA *pItem; // [esp+14h] [ebp-1Ch]
  SC_OBJECT *pNewSpellObj; // [esp+18h] [ebp-18h]
  ELE_SpellsSTDEntry *pLastHeldSpellEffect; // [esp+1Ch] [ebp-14h]

  effectIdx = 0;
  pItem = &pItemObj.u;
  pLastHeldSpellEffect = 0;
  while ( effectIdx < 10 )
  {
    if ( pItem->type2_item.magics[effectIdx].effectType == -1 )
      break;
      
    effectType = pItem->type2_item.magics[effectIdx].effectType;
    if ( effectType == CAST_HELD )
   {
        for (int i = 0;  gFile_Spells_std[i].SpellID != pItem->type2_item.magics[effectIdx].spellID; ++i )
          ;
        pNewSpellObj = SC_Object::InsertNewObject(gObjRoot, 0, sizeof(ELE_SpellsSTDEntry));
        pNewSpellObj->h.type = SC_OBJECT_TYPE_SPELL;
        pNewSpellObj->h.field_15 = 3;
        SC_jmem::memcpy(&pNewSpellObj->u, &gFile_Spells_std[i], sizeof(ELE_SpellsSTDEntry), "inven.c" , 2092, 4);
        pLastHeldSpellEffect = &pNewSpellObj->u;
        pNewSpellObj->u.type9_spell.Icon = inventorySlot - 56;
        for (int j = 0; j < 3; ++j )
        {
          if ( pLastHeldSpellEffect->types[j].major != 255 )
          {
            pLastHeldSpellEffect->duration[j].base = -1;
            pLastHeldSpellEffect->duration[j].x = 0;
            pLastHeldSpellEffect->duration[j].div = 0;
          }
        }
        ELE_SPELL::ApplyEffect_qqq(pNewSpellObj, &pNewSpellObj->u);
      }
    }
    //...
    effectIdx++;
  }
  if ( !effectIdx )
    return 0;
    
  if ( !pLastHeldSpellEffect )
    return effectIdx;
    
  cost = SC_spells::_3A0C0_GetCost_qqq(pLastHeldSpellEffect, gPlayerData);
  return SC_OBJECT_TYPE2::ReduceEnchantment(pItemObj, cost);
}
This code is iterating over the 10 possible effects, and is applying every "on held" effect . But cost is calculated using pLastHeldSpellEffect which is overwritten for each effects... So, for an item with multiple "on held" effect, only last one is used for cost calculation. I don't know if this is the intended behaviour, it may be more coherent to reduce item health by each effect cost.

Interkarma: it should be noted that this function use current player stats for spell cost, item maker uses a dummy player with every skills at 50 for calculating cost (*10).

Once more, it's my current understanding of Daggerfall, it should be validated by in-game experimenting ;)

User avatar
numidium3rd
Posts: 187
Joined: Sun Mar 25, 2018 12:34 am
Location: United States

Re: Where is item condition stored in memory?

Post by numidium3rd »

More progress. We were on the right track saying that b = pSpell->duration * gSpellModifier->field_0 but we also need to add something to that to make it the actual b variable for my original formula.

To Add:
[ebp-001C] = [002A5E40] * ([spellData + 20] + [spellData + 21]) / 2 + (([spellData + 22] + [spellData + 23]) / 2) * ([002A5E42] / [spellData + 24])

For the Amulet of The Orc Lord this ends up being 6E. We then need to add this to EAX which is set to the value below:
ax = [spellData + 000E] * [002A5E3C] = F9
F9 + 6E = 67 (only taking into account least significant byte)

Next step is to figure out what those spellData indices point to and where to find them.

User avatar
Ferital
Posts: 282
Joined: Thu Apr 05, 2018 8:01 am

Re: Where is item condition stored in memory?

Post by Ferital »

There are 51 spell effects in classic, all of which have a "cost type" taken from the following map:

Code: Select all

1, 2, 3, 4, 5, 4, 4, 2, 1, 6
5, 6, 1, 3, 3, 3, 1, 4, 2, 1
1, 1, 1, 3, 3, 3, 3, 3, 3, 1
3, 3, 1, 1, 1, 2, 2, 1, 1, 1
1, 1, 3, 4, 1, 2, 2, 2, 2, 2
2
Then to know the spell cost (which is also the amount of damage the item holding such effect takes when equipped, as guessed by Elenwel), you get 7 functions depending on this cost type.

The one you tried to reverse is probably this one:

Code: Select all

((g_current_spell->magnitude[g_current_spell_type_id].level_base + g_current_spell->magnitude[g_current_spell_type_id].level_high) / 2
/ g_current_spell->magnitude[g_current_spell_type_id].per_level * unknown_var_1 +
unknown_var_2 * ((g_current_spell->magnitude[g_current_spell_type_id].base_high + g_current_spell->magnitude[g_current_spell_type_id].base_low) / 2));

User avatar
Ferital
Posts: 282
Joined: Thu Apr 05, 2018 8:01 am

Re: Where is item condition stored in memory?

Post by Ferital »

The map I mentioned in my previous post wouldn't be complete without the list of spell effects, so here it is:

Code: Select all

char *g_spell_effect_names[51] =
{
  "Paralyze",
  "Continuous Damage",
  "Create Item",
  "Cure",
  "Damage",
  "Disintegrate",
  "Dispel",
  "Drain",
  "Elemental Resistance",
  "Fortify Attribute",
  "Heal",
  "Transfer",
  "Soul Trap",
  "Invisibility",
  "Levitate",
  "Light",
  "Lock",
  "Open",
  "Regenerate",
  "Silence",
  "Spell Absorption",
  "Spell Reflection",
  "Spell Resistance",
  "Chameleon",
  "Shadow",
  "Slowfall",
  "Free Action",
  "Jumping",
  "Climbing",
  NULL,
  "Water Breathing",
  "Water Walking",
  NULL,
  "Pacify",
  "Charm",
  "Shield",
  NULL,
  NULL,
  NULL,
  "Detect",
  "Identify",
  NULL,
  NULL,
  "Teleport",
  "Comprehend Languages",
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL
};
So, as you can see, some effect slots are unused and are probably a leftover of early stages of Daggerfall development.

Edit: looking at DFU code, it seems that Allofich already reverse engineered all of these spell cost computation functions years ago. Numidium, look at FormulaHelper.getCostFromSettings, you'll find the list of 7 functions (packed into one) I was talking about above.

User avatar
Ferital
Posts: 282
Joined: Thu Apr 05, 2018 8:01 am

Re: Where is item condition stored in memory?

Post by Ferital »

I submitted a PR.

User avatar
numidium3rd
Posts: 187
Joined: Sun Mar 25, 2018 12:34 am
Location: United States

Re: Where is item condition stored in memory?

Post by numidium3rd »

Thanks, Ferital! I was so focused on analyzing the assembly that I didn't think to see if we already had code written. Allofich proves to be a reversing wizard as always.

User avatar
Interkarma
Posts: 7236
Joined: Sun Mar 22, 2015 1:51 am

Re: Where is item condition stored in memory?

Post by Interkarma »

This is wonderful work everyone. Congratulations all on the progress! :D

Post Reply