Объявления

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

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

Герои Меча и Магии III: Возрождение Эрафии, Герои Меча и Магии III Дыхание Смерти, Герои Меча и Магии III Клинок Армагеддона, Герои Меча и Магии III Хроники Героев
offlineАватара пользователя
void_17  
имя: DM
Ветеран
Ветеран
 
Сообщения: 530
Зарегистрирован: 25 апр 2021, 15:05
Откуда: Оттуда
Пол: Мужчина
Поблагодарили: 116 раз.

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

Сообщение void_17 » 06 янв 2023, 14:54

Появилась идея как привлечь внимание к моддингу и внутреннему устройству игры, не отпугивая занудными ассемблерами.

Можно после того как выложим API сделать версию только для HiHook-ов.
Суть в том, чтобы можно было уже декомпилированную функцию изменить по-своему и вставить хайхук.

Файл выглядел бы так:
Код: Выделить всё
// Функция foo_bar_baz по адресу 0xABCDEF делает XYZ
void foo_bar_baz(void* ptr, unsigned int flag)
{
    ...
}
#if 0 // измените на 1 чтобы поставить хайхук
//Хайхуки на саму функцию
_PI->WriteHiHook(.... SPLICE_, ...);
//Хайхуки на вызовы функции
_PI->WriteHiHook(... CALL_, ... );
#endif


Таким образом люди, знающие хотя бы Си смогут делать свои моды для героев и не париться со всеми этими ассемблерами, FPU и прочим низкоуровневым др*чевом. Глобальные константы и таблицы можно просто подписать и редактировать ввиде Си-шных переменных.

Заодно такой подход даст людям, которые не хотят ковыряться в дизассемблерах и декомпилятарах, но сумеющие в понимание си-образного когда как работает игра.

Приоритетнее всего декомпилировать классы searchArray, army, combatManager, type_AI_spellcaster, type_AI_hex_chooser и пр. связанные с ИИ.
Вернуться к началу

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

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

Сообщение void_17 » 25 мар 2023, 17:18

Нашёл компилятор, который использовался в Macintosh версии игры.
(Metrowerks codewarrior pro 6.)

На удивление достаточно чистая реализация STL.
 
Эх, помог бы мне кто декомпилировать игру...

https://www.macintoshrepository.org/577 ... rior-pro-6
Вернуться к началу

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

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

Сообщение void_17 » 31 май 2023, 01:47

Полностью декомпилировал ai_tactical.cpp
Спасибо мак-базе.

upd.: накосячил в функции, вот исправленный вариант
 
Код: Выделить всё
bool type_AI_spellcaster::cast_spell( bool retreating )
{
   type_spell_choice best_choice{};
   long value = 0;

   long spell_power = gpCombatManager->iSideSpellPower[this->our_group];
   long spell_level = 0;
   long spell_duration = spell_power;
   const armyGroup * enemy_army_group = gpCombatManager->ArmyGroups[this->enemy_group];
   bool recanters_cloak_used = enemy_hero->IsWieldingArtifact(RECANTERS_CLOAK) || current_hero->IsWieldingArtifact(RECANTERS_CLOAK);
   bool resurrection_only = spells_not_required();
   if ( this->current_hero )
        spell_duration = spell_power + this->current_hero->GetSpellDurationBonus();
    if ( retreating )
        this->estimate.kills_only = true;

   for ( SpellID spell = K_FIRST_COMBAT_SPELL; spell <= K_LAST_COMBAT_SPELL; ++spell )
   {
      if ( this->current_hero->available_spells[spell] )
      {
         if ( ( akSpellTraits[spell].m_flags & SF_BATTLE_SPELL) != 0
              && (!retreating || (akSpellTraits[spell].m_flags & SF_DAMAGE_SPELL) != 0)
              && (gpCombatManager->ground_type != 2 || akSpellTraits[spell].m_level <= 1)
              && (!recanters_cloak_used || akSpellTraits[spell].m_level <= 2) )
         {
            spell_level = this->current_hero->get_spell_level(spell, gpCombatManager->ground_type);
            spell_cost = this->current_hero->GetManaCost(this->current_hero, spell, enemy_army_group, gpCombatManager->ground_type);
            if ( spell_cost <= this->current_hero->mana
                  && (!resurrection_only || spell == SPL_RESURRECTION || spell == SPL_ANIMATE_DEAD) )
            {
               type_spell_choice choice(spell, spell_level, spell_power, spell_duration);
               consider_spell(choice);
               if ( choice.value > 0 )
               {
                  if ( this->current_hero->mana < 7 * spell_cost )
                  {
                     value = choice.value * std::sqrt(this->current_hero->mana / spell_cost);
                  }
                  else
                  {
                     value = 5 * choice.value / 2;
                  }
                  choice.value = value * Random(75, 100) / 100;
                  if ( choice.value > best_choice.value )
                      best_choice = choice;
               }
            }
         }
      }
   }

   if ( best_choice.spell == SPL_NONE || !best_choice.cast_now && !retreating )
        return false;

    gpCombatManager->iNextAction           = 1;
    gpCombatManager->iNextActionExtra      = best_choice.spell;
    gpCombatManager->iNextActionGridIndex  = best_choice.target_hex;
    gpCombatManager->iNextActionGridIndex2 = best_choice.second_target_hex;
    return true;
Вложения
ai_tactical.cpp
(112.22 КБ) Скачиваний: 226
Последний раз редактировалось void_17 31 май 2023, 11:27, всего редактировалось 1 раз.
Вернуться к началу

offlineBen80  
имя: Сергей
Эксперт
Эксперт
 
Сообщения: 1315
Зарегистрирован: 18 июн 2017, 06:49
Пол: Не указан
Поблагодарили: 336 раз.

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

Сообщение Ben80 » 31 май 2023, 04:05

void_17 писал(а):

Полностью декомпилировал ai_tactical.cpp
Спасибо мак-базе.


Круто. Сразу посмотрел количество строк.
Я тоже балуюсь подобным делом - только для Героев 2, у меня пока примерно 10 000 строк декомпилята (а сделать нужно хотя бы 50 000 - это более чем половина игры, за исключением графики и прочих второстепенных вещей).

Все :: я заменяю на _ :smile1: Короче, я все свожу к процедурному программированию, добиваясь компиляции без ошибок. От ООП сознательно ухожу, поскольку "ни в зуб ногой", как у Высоцкого поется.
Вернуться к началу

offlineАватара пользователя
AlexSpl  
имя: Александр
Эксперт
Эксперт
 
Сообщения: 5539
Зарегистрирован: 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)
Поблагодарили: 2155 раз.

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

Сообщение AlexSpl » 31 май 2023, 09:25

Цитата:
Полностью декомпилировал ai_tactical.cpp

Круто. А где сами классы?

Кстати, не знаю, с какой именно версии (проверял на последней, то бишь 8.2), IDA Free разрешили декомпилировать x86-код. Правда, для декомпиляции потребуется подключение к облачному серверу (cloud-based фича), и работает это не всегда из-за недоступности серверов (вот сейчас стал проверять - ошибка cloud: Server is not available, пока писал сообщение - заработало снова; облака не очень надёжная штука).
Вернуться к началу

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

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

Сообщение void_17 » 31 май 2023, 09:32

Цитата:
Круто. А где сами классы?


 
Код: Выделить всё
class type_AI_attack_hex_chooser
{
   public:
      long get_attack_time()const;
      long get_best_hex()const;
      long get_hex_value()const;
      type_AI_attack_hex_chooser(army const * , army const * , long const * , searchArray * , type_AI_combat_parameters const &);
      long get_hex_attack_value(long const , long &);
      long get_attack_time(pathCell const *)const;
      void check_adjacent_hexes(long , long , long);
      bool find_attack_hex();

   private:
      const army *  attacker;
        int           speed;
        const army *  enemy;
        searchArray*  search_array;
        const int  *  enemy_attacks;
       int           retaliation_strength;
       int           our_strength;
       int           best_value;
       int           best_hex;
       int           best_attack_time;
       type_AI_combat_parameters* estimate;
};

//---------------------------------------------------------------------------
// type_AI_attack_hex_chooser class implementation
//---------------------------------------------------------------------------
inline long type_AI_attack_hex_chooser::get_attack_time( )const
{
   return best_attack_time;
}

//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
inline long type_AI_attack_hex_chooser::get_best_hex( )const
{
   return best_hex;
}

//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
inline long type_AI_attack_hex_chooser::get_hex_value( )const
{
   return best_value;
}

struct type_enchant_data
{

   public:

      type_enchant_data( SpellID new_spell,
                     TSkillMastery new_mastery,
                     int new_power,
                     int new_duration );
      int get_mastery_value();
      bool valid()
      {
         return !( spell == 0 && mastery == eMasteryNone && !power );
      }

   public:

      SpellID       spell;
      TSkillMastery mastery;
      int           power;
      int           duration;
      bool          check_resistance;
};

struct type_spell_choice : type_enchant_data
{

   public:

      type_spell_choice();
      type_spell_choice(SpellID,TSkillMastery,long,long);

   public:

      int  target_hex;
      int  second_target_hex;
      int  value;
      bool cast_now;
};

struct type_AI_enemy_data
{
     const army* enemy;
     int         damage;
     int         count;
     int         total_damage;
};

class type_AI_spellcaster
{

   protected:
      void initialize(combatManager *,long);
   public:
      type_AI_spellcaster(combatManager *,long,bool);
      virtual ~type_AI_spellcaster() // virtual destructor is used to destroy enemy_caster
      {
         if ( this->enemy_caster && this->owns_enemy_caster )
            enemy_caster->~type_AI_spellcaster();
      }
   protected:
      type_AI_spellcaster(type_AI_spellcaster *,combatManager *,long,bool);

      bool is_last_action() const;
      bool should_attack_now(army const *) const;
      long get_damage_value(SpellID,long,hero const *,army const *) const;
      long get_damage_spell_value(army const *,type_enchant_data) const;
      long get_group_damage_value(SpellID,long,long,hero *) const;
      long get_mass_damage_effect(long,long) const;
      long get_area_effect_value(SpellID,long,TSkillMastery,long) const;
      void consider_area_effect(type_spell_choice &) const;
      long get_chain_lightning_value(long,TSkillMastery,army *) const;
      void consider_chain_lightning(type_spell_choice &) const;
      void consider_mass_damage(type_spell_choice &) const;
      long get_age_value(army const *,type_enchant_data) const;
      long get_attack_boost_value(army const *,army const *,long,long,double) const;
      long get_attack_boost_value(army const *,army const *,long,double) const;
      long get_bless_value(army const *,type_enchant_data) const;
      long get_frenzy_value(army const *,type_enchant_data) const;
      long get_attack_skill_value(army const *,army const *,long,long) const;
      long get_blood_lust_value(army const *,type_enchant_data) const;
      long get_mirth_value(army const *,type_enchant_data) const;
      long get_sorrow_value(army const *,type_enchant_data) const;
      long get_fortune_value(army const *,type_enchant_data) const;
      long get_defense_boost_value(army const *,army const *,long,double) const;
      long get_defense_skill_value(army const *,long,long) const;
      long get_disease_value(army const *,type_enchant_data) const;
      long get_prayer_value(army const *,type_enchant_data) const;
      long get_precision_value(army const *,type_enchant_data) const;
      long get_air_shield_value(army const *,type_enchant_data) const;
      long get_shield_value(army const *,type_enchant_data) const;
      long get_slayer_value(army const *,type_enchant_data) const;
      long get_tough_skin_value(army const *,type_enchant_data) const;
      long get_disruptive_ray_value(army const *,type_enchant_data) const;
      long get_weakness_value(army const *,type_enchant_data) const;
      long get_misfortune_value(army const *,type_enchant_data) const;
      long get_blind_value(army const *,type_enchant_data) const;
      long get_move_order_change_value(army const *) const;
      long get_muck_and_mire_value(army const *,type_enchant_data) const;
      long get_poison_value(army const *,type_enchant_data) const;
      long get_speed_value(army const *,long,long) const;
      long get_haste_value(army const *,type_enchant_data) const;
      long get_protection_value(army const *,TSpellSchool,long,long,long) const;
      long get_air_protection_value(army const *,type_enchant_data) const;
      long get_fire_protection_value(army const *,type_enchant_data) const;
      long get_earth_protection_value(army const *,type_enchant_data) const;
      long get_water_protection_value(army const *,type_enchant_data) const;
      double get_duration(long,bool) const;
      long get_cancel_value(army &,bool) const;
      long get_dispel_value(army const *,type_enchant_data) const;
      long get_cure_value(army const *,type_enchant_data) const;
      long get_antimagic_value(army const *,type_enchant_data) const;
      long get_backlash_value(army const *,type_enchant_data) const;
      long get_counterstrike_value(army const *,type_enchant_data) const;
      long get_fire_shield_value(army const *,type_enchant_data) const;
      long get_traitor_value(army const *,army const *) const;
      long get_berserk_value(army const *,type_enchant_data) const;
      long get_hypnotize_value(army const *,type_enchant_data) const;
      void consider_single_enchantment(type_spell_choice &,long) const;
      void consider_enchantment(type_spell_choice &,long) const;
      void consider_teleport(type_spell_choice &) const;
      void consider_resurrect(type_spell_choice &) const;
      void consider_sacrifice(type_spell_choice &,army const *,long) const;
      void consider_sacrifice(type_spell_choice &) const;
      long get_clone_value(army const *,type_enchant_data) const;
      long get_curse_value(army const *,type_enchant_data) const;
      long get_forgetfulness_value(army const *,type_enchant_data) const;
      long unimplemented(army const *,type_enchant_data) const;
      type_enchant_func get_enchantment_function(SpellID const)const;
      void consider_earthquake(type_spell_choice &) const   ;
      void consider_summon(type_spell_choice &) const;
      void consider_spell(type_spell_choice &) const;
      void set_melee_enemies();
      void set_worst_enemies();
      void add_enemy(type_AI_enemy_data &,army const *,army const *,bool);
      void find_enemy_attacks();

   public:

      bool cast_spell(bool);
      long get_ogre_mage_value(army const *) const;
      long get_caliph_value(army const *) const;

   protected:

      void check_simulation();
      bool spells_not_required() const;   

   public:

      using type_enchant_func = decltype(unimplemented)*;

   public:

      hero*                     current_hero;
      hero*                     enemy_hero;
      int                       our_group;
      int                       enemy_group;
      int                       enemy_can_attack;
      int                       can_be_attacked;
      bool                      win_likely;
      bool                      is_creature_spell;
      type_AI_combat_parameters estimate;
      type_AI_spellcaster*      enemy_caster;
      bool                      owns_enemy_caster;
      type_AI_enemy_data        melee_enemies[20];
      type_AI_enemy_data        ranged_enemies[20];
      type_AI_enemy_data        worst_enemies[20];

};


Цитата:
IDA Free разрешили декомпилировать x86-код.

А мне это не особо нужно. IDA 7.7 хватает за глаза, к тому же я для декомпиляции в основном MacOS-базой пользуюсь. Её в разы проще декомпилировать.
Вернуться к началу

offlineАватара пользователя
AlexSpl  
имя: Александр
Эксперт
Эксперт
 
Сообщения: 5539
Зарегистрирован: 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)
Поблагодарили: 2155 раз.

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

Сообщение AlexSpl » 05 авг 2023, 01:22

Решил добавить недостающие функции оценки* в свой плагин и пересмотрел код ai_tactical.cpp. У Вас static_cast, nullptr и прочие auto, которые не были доступны разработчикам Героев 3. На какой версии студии будет работать Ваш API? Хотелось бы поддержки лампового C++98** :smile4: Буду ждать. Можно релизнуть даже неполный. Мододелам всё равно только основные классы нужны.

Кстати, круто это Вы разобрались с using type_enchant_func = decltype(unimplemented)*;. Лично проверил дамп, чтобы убедиться, что этот тип не Вы выдумали :smile12: Я до этого думал, что unimplemented - это метод-заглушка для новых функций оценки. Теперь понятно, что unimplemented нужен только для объявления сигнатуры типа. Тройку писали реально скилловые ребята.

*) Можно переписать все, чтобы сделать из AI вообще архимага :smile20:
**) Вместо этого, например: result = bad_value_multiplier * static_cast<double>(value) + good_value_multiplier * static_cast<double>(change + value); можно просто result = bad_value_multiplier * value + good_value_multiplier * (change + value); Все касты сделает сам компилятор. Если брать смешанное деление двух целых, когда результат должен быть дробным, можно привести к double/float только числитель (или знаменатель). Это уже в С++11 добавили (почти) не нужный контроль за приведением типов.

* * *
Можно крутую статью написать о том, как работает тактический AI в Героях 3, просто читая код (возможно, даже я соберусь с силами и снова возьму перо). 10 лет назад такой инфой зачитывались бы, да и сейчас, думаю, будет интересно почитать старичкам-оффлайнщикам, FizMiG будет в восторге. Сейчас по тактическому AI инфы, считай, нет. Одни догадки.
Вернуться к началу

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

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

Сообщение void_17 » 05 авг 2023, 07:25

Цитата:
Решил добавить недостающие функции оценки* в свой плагин и пересмотрел код ai_tactical.cpp. У Вас static_cast, nullptr и прочие auto, которые не были доступны разработчикам Героев 3. На какой версии студии будет работать Ваш API?


Этот декомпилят не для API, он, скажем так, для чтения и анализа.
Я предпочитаю современный C++ с RAII, std::array вместо сишных массивов, std::string насколько это возможно и использование умных указателей вместо new+delete.

API будет поддерживать C++98, C++03 а также compile-time свитчи для работы в режиме C++11/C++17 и даже C++20. Ключевые контейнеры по типу vector, string и map притерпели изменения в новых реализациях MSVC, поэтому буду бесщадно копировать из реализации STL(но в режиме С++98/03 будет браться стандартная реализация понятное дело). Все они будут иметь приставку old_, вместо std::
например old_vector.
Это для портабильности кода понятное дело.

Кстати у C++20 есть очень удобная для моддинга std::bit_cast, который к сожалению реализован внутри компилятора, по крайней мере у MSVC.

Цитата:
using type_enchant_func = decltype(unimplemented)*

На самом деле название оригинальное, из дампа, только там древний синтаксис с typedef-ом, а у меня универсальный и удобочитаемый современный using. Я использовал для выделения типа функции unimplemented потому что это функция по задумке была бы неизменна в своей сигнатуре.

Цитата:
Вместо этого, например: result = bad_value_multiplier * static_cast<double>(value) + good_value_multiplier * static_cast<double>(change + value); можно просто result = bad_value_multiplier * value + good_value_multiplier * (change + value); Все касты сделает сам компилятор. Если брать смешанное деление двух целых, когда результат должен быть дробным, можно привести к double/float только числитель (или знаменатель).


Да я это знаю, просто для точности декомпилята мне нужно было. Забыл убрать когда выкладывал сюда.

Цитата:
Можно крутую статью написать о том, как работает тактический AI в Героях 3, просто читая код

Да, была идейка, но руки не дойдут никак. Буду рад если моя работа пойдет в физмиг. Кстати следовало бы поменять в физмиге воговские названия типов на оригинальные. Я считаю сейчас воговские названия неактуальны.
Вернуться к началу

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

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

Сообщение void_17 » 05 авг 2023, 07:36

Да, к слову, у MSVC есть build tools для WinXP, которые спокойно поддерживают C++17, а там есть и std::filesystem. Это v141_xp.
Вероятно вам хочется чтобы ваш мод работал на любой винде, v141_xp и C++17 хватит за глаза, я проверил, работает даже на ExaGear эмуляторе, а для меня это главное, т.к. я сейчас на даче играю в героев с планшета.(Недельку отложу разработку API)

Цитата:
Буду ждать. Можно релизнуть даже неполный. Мододелам всё равно только основные классы нужны.


Хочу релизнуть к концу августа бета версию и дать вам(тебе, igrik, ben и другим) на растерзание, чтобы отполировать до готового варианта и пускать в свободное плавание всем моддерам на радость. Вы сможете открыть pull request-ы и работать. Потерпите немного только, я еще и базу обновлю.
Вернуться к началу

offlineАватара пользователя
AlexSpl  
имя: Александр
Эксперт
Эксперт
 
Сообщения: 5539
Зарегистрирован: 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)
Поблагодарили: 2155 раз.

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

Сообщение AlexSpl » 05 авг 2023, 07:44

Здорово :smile20:

Цитата:
Да, к слову, у MSVC есть build tools для WinXP, которые спокойно поддерживают C++17, а там есть и std::filesystem. Это v141_xp.

Я бы просто хотел поддержки современных стандартов в 2008-й студии без лишних наворотов от Microsoft. Я пробовал работать и в 2019-й и в 2022-й, но совершенно не вижу их преимуществ над 2008-й, если всё, что ты хочешь, это написать плагин.
Вернуться к началу

Пред.След.

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

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

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