Объявления
Поздравляем
Roman2211


Друзья, если не получается зарегистрироваться, напишите на почту vdv_forever@bk.ru.
Я оторву свою задницу от всех дел и обязательно Вас активирую! :smile10:
Добро пожаловать на геройский форум! :smile25:

База данных IDA от void17

Герои Меча и Магии III: Возрождение Эрафии, Герои Меча и Магии III Дыхание Смерти, Герои Меча и Магии III Клинок Армагеддона, Герои Меча и Магии III Хроники Героев
offlineАватара пользователя
AlexSpl  
имя: Александр
Эксперт
Эксперт
 
Сообщения: 5587
Зарегистрирован: 17 сен 2010, 12:58
Пол: Мужчина
Награды: 14
Высшая медаль (1) Победителю турнира по HMM1_TE (2) Победителю этапа по HMM1 (1) Победителю этапа по HMM2 (1) Лучшему из лучших (1) 2 место 1 этапа по HMM1 (1)
3 место 1 этапа по HMM1 (1) 1 место 2 этапа по HMM2 (1) Победителю турнира по KB (2) Победителю турнира по KB (1) Грандмастер оффлайн-турниров (1) Боевой шлем (1)
Поблагодарили: 2185 раз.

Re: База данных IDA от void17

Сообщение AlexSpl » 27 ноя 2021, 01:44

По коду многого не поймёшь. Только вот такой вывод информации спасает со сложными структурами.
Вернуться к началу

offlineАватара пользователя
void_17  
имя: имя
Ветеран
Ветеран
 
Сообщения: 548
Зарегистрирован: 25 апр 2021, 15:05
Откуда: Оттуда
Пол: Мужчина
Поблагодарили: 132 раз.

Re: База данных IDA от void17

Сообщение void_17 » 27 ноя 2021, 01:49

Цитата:
Коэффициент для Shield:

(cуммарный урон от достающих рукопашников + суммарный урон от достающих стрелков) / (суммарный урон от достающих стрелков + суммарный урон от достающих рукопашников * коэффициент снижения урона для Shield)

Вот такой нехитрый коэффициент. И разумный.

Для Air Shield всё наоборот:

(суммарный урон от достающих стрелков + cуммарный урон от достающих рукопашников) / (cуммарный урон от достающих рукопашников + суммарный урон от достающих стрелков * коэффициент снижения урона для Air Shield)


Чую физмиг знатно пополнится этой зимой... Сколько же нас открытий ждет
Вернуться к началу

offlineАватара пользователя
AlexSpl  
имя: Александр
Эксперт
Эксперт
 
Сообщения: 5587
Зарегистрирован: 17 сен 2010, 12:58
Пол: Мужчина
Награды: 14
Высшая медаль (1) Победителю турнира по HMM1_TE (2) Победителю этапа по HMM1 (1) Победителю этапа по HMM2 (1) Лучшему из лучших (1) 2 место 1 этапа по HMM1 (1)
3 место 1 этапа по HMM1 (1) 1 место 2 этапа по HMM2 (1) Победителю турнира по KB (2) Победителю турнира по KB (1) Грандмастер оффлайн-турниров (1) Боевой шлем (1)
Поблагодарили: 2185 раз.

Re: База данных IDA от void17

Сообщение AlexSpl » 27 ноя 2021, 01:51

Да, и куча багов AI :smile12:
Вернуться к началу

offlineАватара пользователя
AlexSpl  
имя: Александр
Эксперт
Эксперт
 
Сообщения: 5587
Зарегистрирован: 17 сен 2010, 12:58
Пол: Мужчина
Награды: 14
Высшая медаль (1) Победителю турнира по HMM1_TE (2) Победителю этапа по HMM1 (1) Победителю этапа по HMM2 (1) Лучшему из лучших (1) 2 место 1 этапа по HMM1 (1)
3 место 1 этапа по HMM1 (1) 1 место 2 этапа по HMM2 (1) Победителю турнира по KB (2) Победителю турнира по KB (1) Грандмастер оффлайн-турниров (1) Боевой шлем (1)
Поблагодарили: 2185 раз.

Re: База данных IDA от void17

Сообщение AlexSpl » 27 ноя 2021, 01:57

Код: Выделить всё
if ( enemyArmy != AI_MeleeEnemy && ((1 << iArmy_Melee) & enemyArmy->field_544) != 0 )
{
   this->bitfield_18 |= 1 << iArmy;
   totalAvgDamage = army::get_average_damage(enemyArmy, Target, 0, enemyArmy->amountAlive, 0, 0);
   avgDamage = totalAvgDamage + ADJ(pB)->AI_MeleeEnemy[0].totalAvgDamage;
   ++ADJ(pB)->AI_MeleeEnemy[0].threatsNum;
   average_damage_A = ADJ(pB)->AI_MeleeEnemy[0].average_damage;
   ADJ(pB)->AI_MeleeEnemy[0].totalAvgDamage = avgDamage;
   if ( totalAvgDamage > average_damage_A )
   {
     ADJ(pB)->AI_MeleeEnemy[0].average_damage = totalAvgDamage;
     ADJ(pB)->AI_MeleeEnemy[0].Army = enemyArmy;
   }
   goto LABEL_22;
}

Таинственное поле field_544 стоит на пути полной декомпиляции конструктора, точнее, вызываемого им метода void __thiscall type_AI_spellcaster::find_enemy_attacks(type_AI_spellcaster *this).
Вернуться к началу

offlineАватара пользователя
void_17  
имя: имя
Ветеран
Ветеран
 
Сообщения: 548
Зарегистрирован: 25 апр 2021, 15:05
Откуда: Оттуда
Пол: Мужчина
Поблагодарили: 132 раз.

Re: База данных IDA от void17

Сообщение void_17 » 27 ноя 2021, 02:02

Может единичный флаг какой-то? По проверке в if-e похоже
Вернуться к началу

offlineАватара пользователя
AlexSpl  
имя: Александр
Эксперт
Эксперт
 
Сообщения: 5587
Зарегистрирован: 17 сен 2010, 12:58
Пол: Мужчина
Награды: 14
Высшая медаль (1) Победителю турнира по HMM1_TE (2) Победителю этапа по HMM1 (1) Победителю этапа по HMM2 (1) Лучшему из лучших (1) 2 место 1 этапа по HMM1 (1)
3 место 1 этапа по HMM1 (1) 1 место 2 этапа по HMM2 (1) Победителю турнира по KB (2) Победителю турнира по KB (1) Грандмастер оффлайн-турниров (1) Боевой шлем (1)
Поблагодарили: 2185 раз.

Re: База данных IDA от void17

Сообщение AlexSpl » 27 ноя 2021, 02:04

Скорее, битовое поле. Нужно будет тоже вывести на экран и посмотреть, что в нём хранится.

Ещё меня смущает индекс 0. Например, ++ADJ(pB)->AI_MeleeEnemy[0].threatsNum; Но массив заполняется для всех отрядов. Неужели указатель уже указывает на рассматриваемый отряд и индексация идёт именно от этого отряда? А динамически, вроде, нельзя зааджастить к началу массива. Будет переменный индекс, а зааджастить можно только на константу.

Всё, понял, в цикле идёт инкремент указателя :smile20: Т.е. сначала он указывает на 0-й элемент массива, а затем инкрементируется (или +1 размер структуры = 16 байт = 4 дворда).

Всё-таки shifted pointer - мегакрутая вещь :smile20: Вот тот же код без него:

Код: Выделить всё
if ( enemyArmy != AI_MeleeEnemy && ((1 << iArmy_Melee) & enemyArmy->field_544) != 0 )
{
   this->bitfield_18 |= 1 << iArmy;
   totalAvgDamage = army::get_average_damage(enemyArmy, Target, 0, enemyArmy->amountAlive, 0, 0);
   avgDamage = totalAvgDamage + *(pB - 320);
   ++*(pB - 324);
   average_damage_A = *(pB - 328);
   *(pB - 320) = avgDamage;
   if ( totalAvgDamage > average_damage_A )
   {
     *(pB - 328) = totalAvgDamage;
     *(pB - 332) = enemyArmy;
   }
   goto LABEL_22;
}

Согласитесь, есть разница?

Мы привыкли, что указатель всегда указывает на начало структуры и возможны только неотрицательные оффсеты, но компилятор не знаком с этим правилом и сдвигает начало так, чтобы ему было удобнее работать со структурой (точнее, для того, чтобы код быстрее выполнялся). Отсюда и возникают отрицательные смещения, которые по-прежнему находятся внутри структуры. shifted pointer позволяет сделать обратный сдвиг, не в самом коде, а в декомпилированном листинге, чтобы человек не путался, а читал код так, как он привык читать, т.е. когда указатель указывает на начало структуры, а не находится где-то посередине или вообще вне её.

* * *
Мне вот всегда интересно было, почему в коде проверки на CID_ARROW_ТОWER, ведь технически это не отряд. Как именно игра работает со Стрелковой башней? Три стрелковые башни ведь не препятствуют тому, чтобы игрок имел 20 отрядов на поле боя? И почему размер stack [2][21] = [2][20 + 1], а не просто [2][20]? Кто может быть 21-м отрядом?

* * *
Поправил выравнивание:

Код: Выделить всё
#pragma pack(push, 4)
struct type_AI_combat_parameters
{
  int attack_modifier;
  int defense_modifier;
  char isOpponentDangerous;
  char isBattleSimulated;
  int casterTotalCombatValue;
  int enemyTotalCombatValue;
  int casterActiveStacksCombatValue;
  int enemyActiveStacksCombatValue;
  int DangerRating;
  int side[2];
};
#pragma pack(pop)

__declspec(align(4)) не нужно здесь.
Вернуться к началу

offlineАватара пользователя
void_17  
имя: имя
Ветеран
Ветеран
 
Сообщения: 548
Зарегистрирован: 25 апр 2021, 15:05
Откуда: Оттуда
Пол: Мужчина
Поблагодарили: 132 раз.

Re: База данных IDA от void17

Сообщение void_17 » 27 ноя 2021, 11:57

Цитата:
__declspec(align(4)) не нужно здесь.


Это не я, это IDA. Автоматически генерит. Не знаю, как отключить. Только каждый раз вручную убирать... :smile7:
Вернуться к началу

offlineАватара пользователя
void_17  
имя: имя
Ветеран
Ветеран
 
Сообщения: 548
Зарегистрирован: 25 апр 2021, 15:05
Откуда: Оттуда
Пол: Мужчина
Поблагодарили: 132 раз.

Re: База данных IDA от void17

Сообщение void_17 » 27 ноя 2021, 11:59

AlexSpl, я НИЧЕГО не имею против shifted pointers. Эта крутая вещь, да, но она есть только в IDA.
А я хочу cpp файл написать, тобишь в Visual Studio. Их там на уровне макросов нет. Если можно их применять как-то, придумать бы как или найти в интернете определение макросов, тогда без проблем.
Вернуться к началу

offlineАватара пользователя
AlexSpl  
имя: Александр
Эксперт
Эксперт
 
Сообщения: 5587
Зарегистрирован: 17 сен 2010, 12:58
Пол: Мужчина
Награды: 14
Высшая медаль (1) Победителю турнира по HMM1_TE (2) Победителю этапа по HMM1 (1) Победителю этапа по HMM2 (1) Лучшему из лучших (1) 2 место 1 этапа по HMM1 (1)
3 место 1 этапа по HMM1 (1) 1 место 2 этапа по HMM2 (1) Победителю турнира по KB (2) Победителю турнира по KB (1) Грандмастер оффлайн-турниров (1) Боевой шлем (1)
Поблагодарили: 2185 раз.

Re: База данных IDA от void17

Сообщение AlexSpl » 27 ноя 2021, 16:29

Так на С++ Вы всё равно не будете писать, как в IDA - например, так: ++*(pB - 324); - Вы просто объявите указатель на type_AI_spellcaster и будете работать с ним. Вместо ++ADJ(pB)->AI_MeleeEnemy[0].threatsNum; у Вас будет ++p->AI_MeleeEnemy[i].threatsNum; (сорри, здесь, конечно, значение поля инкрементируется, но вместо инкрементации указателя далее по коду Вы будете работать с индексом массива i). Т.е. запись через shifted pointers гораздо ближе к реальному коду, чем без них.

А ещё у меня родилась крутая идея для разработчиков IDA - aliases. Часто бывает, что программа начинает работать, например, с аргументами, как с переменными, после того, как значения аргументов стали не нужны. Или использует одну и ту же переменную для работы с совершенно разными типами данных. Например, если у нас объявлена локальная переменная double a, то компилятор вполне может разобрать её на два инта и использовать половинки совершенно в других целях. Так вот, моя идея в том, чтобы можно было назначать переменной сколько угодно много имён и даже назначать отдельные имена частям переменной. Например, если программа работает со старшим двордом double a, нужно разрешить программисту задать alias этому старшему дворду, например, int b. Тогда чтение кода гораздо упростится и не будет всех этих SHIDWORD() и приведений к (__int64) в 32-разрядном коде. Технически это, наверное, через union сделать можно. Это не в духе С++, но и компилятор играет не по правилам С++ :smile2: Я предлагаю любой тип для внутреннего использования IDA делать union'ом, чтобы по правому клику аналитик кода мог выбрать правильный тип в каждом конкретном случае.

Кстати, IDA умеет работать с union, но нужно прописывать типы вручную. Вот вопиющий пример безобразия, творимого компилятором. Здесь union ой как нужны:

Код: Выделить всё
int __thiscall type_AI_spellcaster::get_frenzy_value(
        type_AI_spellcaster *this,
        const army *stack,
        type_enchant_data data)
{
  army *v3; // edi
  army *v4; // esi
  int Speed; // eax
  char v6; // bl
  int total_hit_points; // eax
  INT32 hitPoints; // ecx
  int v9; // eax
  int v10; // eax
  int v11; // ebx
  bool can_shoot; // al
  int v13; // ecx
  __int64 v14; // rax
  signed int numberAlive; // [esp-Ch] [ebp-44h]
  double v17; // [esp+Ch] [ebp-2Ch]
  double v18; // [esp+14h] [ebp-24h]
  int *v19; // [esp+1Ch] [ebp-1Ch]
  int a5; // [esp+20h] [ebp-18h] BYREF
  double v21; // [esp+24h] [ebp-14h]
  int average_damage; // [esp+2Ch] [ebp-Ch]
  char a3[8]; // [esp+30h] [ebp-8h]

  v4 = stack;
  HIDWORD(v21) = this;
  v3 = *&stack->spellsData._f_530[8];
  if ( !v3 )
    goto LABEL_18;
  Speed = army::GetSpeed(stack);
  if ( army::get_AI_target_time(v4, Speed) > 1 )
    goto LABEL_18;
  a3[4] = army::can_shoot(v4, 0);
  stack = army::get_total_hit_points(v4, 0);
  total_hit_points = army::get_total_hit_points(v3, 0);
  hitPoints = v4->info.hitPoints;
  a5 = total_hit_points;
  v6 = a3[4];
  average_damage = army::get_average_damage(v4, v3, a3[4], &stack->_f_000[hitPoints - 1] / hitPoints, 1, 0);
  v19 = (HIDWORD(v21) + 32);
  type_AI_combat_parameters::simulate_attack((HIDWORD(v21) + 32), v4, &stack, v3, &a5, a3[4], 0);
  if ( !stack )
    goto LABEL_18;
  v9 = army::get_average_damage(v4, v3, v6, &stack->_f_000[v4->info.hitPoints - 1] / v4->info.hitPoints, 1, 0);
  if ( v6 && (*&v4->info.flags & CF_TWO_ATTACKS) != 0 )
    v9 /= 2;
  *&a3[4] = average_damage + v9;
  numberAlive = v4->numberAlive;
  *a3 = (average_damage + v9) / average_damage;
  can_shoot = army::can_shoot(v4, 0);
  average_damage = army::get_average_damage(v4, v3, can_shoot, numberAlive, 1, 0);
  v18 = *a3;
  v17 = average_damage;
  v11 = (v17 * *a3);
  v10 = army::get_total_hit_points(v3, 0);
  *&a3[4] = v10;
  if ( v11 > v10 )
  {
    v11 = v10;
    v18 = *&a3[4] / v17;
  }
  if ( v11 > average_damage )
  {
    v13 = HIDWORD(v21);
    HIDWORD(v21) = *(HIDWORD(v21) + 60);
    if ( data.spellDuration < SHIDWORD(v21) )
    {
      *&data.spellDuration = data.spellDuration;
      *&data.spellDuration = *&data.spellDuration / SHIDWORD(v21);
    }
    else
    {
      *&data.spellDuration = 0x3FF0000000000000i64;
    }
    if ( (*&v4->info.flags & CF_DONE) != 0
      && (*&data.spellDuration = *&data.spellDuration - 1.0 / SHIDWORD(v21), *&data.spellDuration < 0.0) )
    {
      v21 = 0.0;
    }
    else
    {
      v21 = *&data.spellDuration;
    }
    *&data.spellDuration = army::get_total_combat_value(v4, *v19, *(v13 + 36));
    return ((sqrt(v18) - 1.0) * *&data.spellDuration * v21);
  }
  else
  {
LABEL_18:
    LODWORD(v14) = 0;
  }
  return v14;
}

Понятно же, что в 32-битной архитектуре нет регистра rax (__int64 v14; // rax). Здесь пример того, как аргумент используется в качестве локальной переменной, и из-за его типа получается вот такой кривой код:

Код: Выделить всё
v13 = HIDWORD(v21);
HIDWORD(v21) = *(HIDWORD(v21) + 60);
if ( data.spellDuration < SHIDWORD(v21) )
{
  *&data.spellDuration = data.spellDuration;
  *&data.spellDuration = *&data.spellDuration / SHIDWORD(v21);
}
else
{
  *&data.spellDuration = 0x3FF0000000000000i64;
}

Попробую переписать через union позже. Вот увидите, что тут происходит совсем не то, о чём Вы могли подумать :smile2:
Вернуться к началу

offlineАватара пользователя
AlexSpl  
имя: Александр
Эксперт
Эксперт
 
Сообщения: 5587
Зарегистрирован: 17 сен 2010, 12:58
Пол: Мужчина
Награды: 14
Высшая медаль (1) Победителю турнира по HMM1_TE (2) Победителю этапа по HMM1 (1) Победителю этапа по HMM2 (1) Лучшему из лучших (1) 2 место 1 этапа по HMM1 (1)
3 место 1 этапа по HMM1 (1) 1 место 2 этапа по HMM2 (1) Победителю турнира по KB (2) Победителю турнира по KB (1) Грандмастер оффлайн-турниров (1) Боевой шлем (1)
Поблагодарили: 2185 раз.

Re: База данных IDA от void17

Сообщение AlexSpl » 27 ноя 2021, 21:15

Это просто жесть. Такой реюз переменных, что декомпилированный код мало чем отличается от ассемблерного :smile1: Как бороться дальше с реюзом переменных, не знаю :smile5: Это пока самая сложная функция. Не в плане сложности вообще, а в плане сложности превратить её декомпилированный листинг в такой, который можно понять. Но уже всяко лучше того, что было.

Код: Выделить всё
int __thiscall type_AI_spellcaster::get_frenzy_value(
        type_AI_spellcaster *this,
        army *army,
        int a2,
        int a3,
        int a4,
        double adjustedCombatValue)
{
  army *frenzyStack; // esi
  army *frenzyTarget; // edi
  int speed; // eax
  int targetHealth; // eax
  INT32 hitPoints; // ecx
  char canShoot; // bl
  int avgDamageAfterAttack; // eax
  bool canShoot_0; // al
  int adjustedDamage; // ebx
  int targetHealth_1; // eax
  type_AI_spellcaster *spellCaster; // ecx
  int frenzyWeight; // eax
  signed int numberAlive; // [esp-Ch] [ebp-44h]
  double avgDamage_1; // [esp+Ch] [ebp-2Ch]
  double killedEnemyRatio; // [esp+14h] [ebp-24h]
  int *attackValue; // [esp+1Ch] [ebp-1Ch]
  int targetHealth_0; // [esp+20h] [ebp-18h] BYREF
  double K; // [esp+24h] [ebp-14h]
  int avgDamage; // [esp+2Ch] [ebp-Ch]
  char a[8]; // [esp+30h] [ebp-8h]
  int health; // [esp+40h] [ebp+8h] FORCED BYREF

  frenzyStack = army;
  HIDWORD(K) = this;
  frenzyTarget = army->spellsData.frenzyTarget;
  // Если нет цели, вес заклинания 0
  if ( !frenzyTarget )
    return 0;
  speed = army::GetSpeed(army);
  // Если отряд под Frenzy не может добраться до цели за 1 ход, вес заклинания 0
  if ( army::get_AI_target_time(frenzyStack, speed) > 1 )
    return 0;
  // Может ли отряд под Frenzy стрелять?
  // (a[4] читать как canShoot)
  a[4] = army::can_shoot(frenzyStack, 0);
  // Получаем здоровье отряда под Frenzy
  health = army::get_total_hit_points(frenzyStack, 0);
  // Получаем здоровье цели
  targetHealth = army::get_total_hit_points(frenzyTarget, 0);
  hitPoints = frenzyStack->info.hitPoints;
  targetHealth_0 = targetHealth;
  canShoot = a[4];
  avgDamage = army::get_average_damage(frenzyStack, frenzyTarget, a[4], (hitPoints + health - 1) / hitPoints, 1, 0);
  // this->attackValue
  attackValue = (HIDWORD(K) + 32);
  // Симулируем атаку
  type_AI_combat_parameters::simulate_attack(
    (HIDWORD(K) + 32),
    frenzyStack,
    &health,
    frenzyTarget,
    &targetHealth_0,
    a[4],
    0);
  if ( !health )
    return 0;
  // Средний урон после атаки
  avgDamageAfterAttack = army::get_average_damage(
                           frenzyStack,
                           frenzyTarget,
                           canShoot,
                           (frenzyStack->info.hitPoints + health - 1) / frenzyStack->info.hitPoints,
                           1,
                           0);
  if ( canShoot && (*&frenzyStack->info.flags & CF_TWO_ATTACKS) != 0 )
    avgDamageAfterAttack /= 2;
  *&a[4] = avgDamage + avgDamageAfterAttack;
  numberAlive = frenzyStack->numberAlive;
  // 1 + avgDamageAfterAttack / avgDamage
  *a = (avgDamage + avgDamageAfterAttack) / avgDamage;
  canShoot_0 = army::can_shoot(frenzyStack, 0);
  avgDamage = army::get_average_damage(frenzyStack, frenzyTarget, canShoot_0, numberAlive, 1, 0);
  // Коэффициент потерь
  killedEnemyRatio = *a;
  avgDamage_1 = avgDamage;
  adjustedDamage = (avgDamage_1 * *a);
  targetHealth_1 = army::get_total_hit_points(frenzyTarget, 0);
  *&a[4] = targetHealth_1;
  if ( adjustedDamage > targetHealth_1 )
  {
    adjustedDamage = targetHealth_1;
    // Помним, что в числителе - здоровье отряда противника
    killedEnemyRatio = *&a[4] / avgDamage_1;
  }
  if ( adjustedDamage <= avgDamage )
    return 0;
  // Вот отсюда начинается боль. Понять что это такое можно, посмотрев на структуру type_AI_spellcaster
  // Но поменять тип нельзя, иначе "поплывёт" всё остальное. Реюз переменных - жуткое зло
  spellCaster = HIDWORD(K);
  HIDWORD(K) = *(HIDWORD(K) + 60);
  if ( SLODWORD(adjustedCombatValue) < SHIDWORD(K) )
  {
    adjustedCombatValue = SLODWORD(adjustedCombatValue);
    adjustedCombatValue = adjustedCombatValue / SHIDWORD(K);
  }
  else
  {
    adjustedCombatValue = 1.0;
  }
  if ( (*&frenzyStack->info.flags & CF_DONE) != 0
    && (adjustedCombatValue = adjustedCombatValue - 1.0 / SHIDWORD(K), adjustedCombatValue < 0.0) )
  {
    K = 0.0;
  }
  else
  {
    K = adjustedCombatValue;
  }
  adjustedCombatValue = army::get_total_combat_value(frenzyStack, *attackValue, spellCaster->defenseValue);
  *&frenzyWeight = ((sqrt(killedEnemyRatio) - 1.0) * adjustedCombatValue * K);
  return frenzyWeight;
}

Нужно глянуть, как её Ghidra декомпилирует :smile1:
Последний раз редактировалось AlexSpl 27 ноя 2021, 23:43, всего редактировалось 1 раз.
Вернуться к началу

Пред.След.

Вернуться в Общий раздел

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 1

cron