Всё. Вопрос закрыт. IDA поменяла сигнатуру моей функции.
А нет, рано обрадовался. Реально засада:
- Код: Выделить всё
int __thiscall type_AI_spellcaster::get_disease_value(
type_AI_spellcaster *this,
const army *stack,
type_enchant_data data)
{
int result; // eax
void *v6; // ecx
void *v7; // ecx
army *stackb; // [esp+18h] [ebp+8h]
army *stacka; // [esp+18h] [ebp+8h]
if ( ((1 << stack->sideIndex) & this->shootingThreats) == 0 )
return 0;
if ( this->combatParams.isOpponentDangerous )
return 0;
stackb = (army *)army::get_total_combat_value(
(army *)stack,
this->combatParams.enemyHeroAttack,
this->combatParams.enemyHeroDefense);
__asm
{
fild [ebp+stack]; Load Integer
fstp [ebp+var_8]; Store Real and Pop
fld [ebp+var_8]; Load Real
fmul ds:dbl_0063B7E0; Multiply Real
fsubr [ebp+var_8]; Subtract Real Reversed
fild [ebp+arg_10]; Load Integer
fstp [ebp+var_8]; Store Real and Pop
fmul [ebp+var_8]; Multiply Real
}
result = ftol(v6);
stacka = (army *)result;
if ( LOBYTE(data.army) )
{
combatManager::SpellCastWorkChance(gpCombatManager, SPL_WEAKNESS, this->side[0], stack, 0, 1, this->isCreatureCast);
__asm
{
fild [ebp+stack]; Load Integer
fstp [ebp+stack]; Store Real and Pop
fmul [ebp+stack]; Multiply Real
}
return ftol(v7);
}
return result;
}
До этого всё было OK. Реально ли как-то откатить? Похоже, теперь IDA считает ftol() функцией.
Ура, решил. Нужно было удалить сигнатуру ftol() и переанализировать. Теперь всё отлично:
- Код: Выделить всё
int __thiscall type_AI_spellcaster::get_disease_value(
type_AI_spellcaster *this,
const army *stack,
type_enchant_data data)
{
__int64 v5; // rax
int total_combat_value; // eax
float stacka; // [esp+18h] [ebp+8h]
if ( ((1 << stack->sideIndex) & this->shootingThreats) != 0 )
{
if ( this->combatParams.isOpponentDangerous )
{
LODWORD(v5) = 0;
}
else
{
total_combat_value = army::get_total_combat_value(
stack,
this->combatParams.enemyHeroAttack,
this->combatParams.enemyHeroDefense);
v5 = ((total_combat_value - total_combat_value * 0.9) * data.SpellDuration);
if ( LOBYTE(data.army) )
{
stacka = v5;
return (combatManager::SpellCastWorkChance(
gpCombatManager,
SPL_WEAKNESS,
this->side[0],
stack,
0,
1,
this->isCreatureCast)
* stacka);
}
}
}
else
{
LODWORD(v5) = 0;
}
return v5;
}
Правильная сигнатура ftol(): __int64 __usercall ftol@<edx:eax>(double a1@<st0>):
- Код: Выделить всё
__int64 __usercall ftol@<edx:eax>(double a1@<st0>)
{
return a1;
}
Прочитал про fistp. Есть разновидность, которая выталкивает из регистра FPU число не в пару регистров <edx:eax>, а только в eax. Вот она бы решила проблемы с такими странными локальными переменными, как __int64 v5; // rax, а главное, с дурацким приведением к __int64, но в Героях 3 реально используется вариант fistp, который выталкивает в пару регистров, а игнорировать edx в общем случае нельзя, хотя для округления в игре используется только eax. Эх, можно было бы это как-то декомпилятору подсказать...
В игре, короче, используется вариант DF /7 FISTP m64int - Store ST(0) in m64int and pop register stack. А нужен DB /3 FISTP m32int Store ST(0) in m32int and pop register stack. Можно даже хакнуть инструкцию в шестнадцатеричном редакторе, и я уверен, что декомпилированный листинг функций, использующих ftol(), будет намного читабельнее.
ftol() вызывается, когда программист пишет так, например:
- Код: Выделить всё
int i = 1;
i = (int)(i * 3.14);
Вот такой эквивалент ftol() нашёл:
- Код: Выделить всё
int ftol(float f)
{
int a = *(int*)(&f);
int sign = (a>>31);
int mantissa = (a&((1<<23)-1))|(1<<23);
int exponent = ((a&0x7fffffff)>>23)-127;
int r = ((unsigned int)(mantissa)<<8)>>(31-exponent);
return ((r ^ (sign)) - sign ) &~ (exponent>>31);
}
Это без переключения режимов округления и только для float (4 байта).
Чтобы заставить ftol() возвращать значение только в eax, нужно заменить fistp qword ptr [ebp-0Ch] (DF 7D F4) на DB 5D F4. Потом попробую создать IDA-базу с этим хаком и отпишусь о результатах.