Wake of Gods Forum | Форум Во Имя Богов
Реверс игры для новичков - Printable Version

+- Wake of Gods Forum | Форум Во Имя Богов (http://wforum.heroes35.net)
+-- Forum: Герои Меча и Магии 3.5 WoG/ERA (/forumdisplay.php?fid=99)
+--- Forum: Библиотека по моддингу (/forumdisplay.php?fid=186)
+--- Thread: Реверс игры для новичков (/showthread.php?tid=5706)


Реверс игры для новичков - igrik - 22.07.2020 12:55

Тема создана для того, чтобы объяснить основы реверс-инжиниринга для HoMM3, а также описать базовые техники и способы нахождения необходимых вам данных во внутреннем коде игры (в exe)

Необходимые программы. (Click to View)

Содержание:
1. Приведение псевдокода Иды в удобочитаемый вид ©RoseKavalier (инструкция на английском). Внесение перечисляемых типов Enum
2. Принцип нахождения необходимых данных на примере поиска параметров бонуса Баллисты, зависящего от навыка Артилерия. Часть 1
2. Принцип нахождения необходимых данных на примере поиска параметров бонуса Баллисты, зависящего от навыка Артилерия. Часть 2
3. Находим данные элементов родных геройских диалогов (окно героя)
4. Определение причины краша игры с помощью HD_CRASH_LOG.TXT и Ида ©Zur13
5. Разбор причины вылета из игры, из-за неправильных манипуляций с UN:C ©XEPOMAHT
6. Запись стрима: feanor & Orzie исследуют код и создают плагин на Мистицизм и героя специалиста по Баллистике в рамках проекта The Succession Wars на ERA 2

Памятка установки хуков на WoG (Click to View)



RE: Исследование героев - Zur13 - 22.07.2020 13:17

(22.07.2020 12:55)igrik Wrote:  Ребята, которые очень хорошо шарят как писать код на ERM, но не знают как находить данные в исходном коде игры для UN:C. Я всё меньше и меньше могу позволить себе выделять время на игру, и как следствие этого - в недалёком будущем я намерен полностью уйти из модинга героев, поэтому я бы хотел не забирать свои знания с собой, а передать хотя бы чать их. Интересно ли вам научиться самим находить необходимые данные в exe игры без посторонней помощи?

Жаль конечно, но всякое бьвает, я тоже думал что уйду из модинга после ведьмака 3, а потом как-то зацепился за цивилизацию 6, а потом на тытрубе зацепился за теплое ламповое прохождение героев, вспомнил свой 2010 и первую попытку в ЕРМ, где-то тут на форумах лежит небольшой скрипт с того времени, оказывается что без модинга скучно и разминка для мозгов как-никак, да и время появляется пару часов в неделю 118

Да конечно интересно, но просто передать паре человек это не то, если есть возможность нужен отдельный туториал, именно так и названый, как найти нужные адреса и данные для UN:C. Просто проблема в чем, я например не готов сам засесть за ИДУ и потратить пол года пытаясь разобраться как там в героях всё устроено, у меня просто нет столько свободного времени да и сил наверное, это исходя из того что я асм только из универа помню, а структуру PE и организации ПО в памяти так и вообще не знаю 105


RE: Исследование героев - XEPOMAHT - 22.07.2020 13:41

(22.07.2020 12:55)igrik Wrote:  Интересно ли вам научиться самим находить необходимые данные в exe игры без посторонней помощи?

По опыту скажу что, этим можно научиться только на практике, а именно:

1. Собственно, основы работы с UN:C - умение читать/записывать основные типы данных (integer, float) и размеры (1,2,4-байтные числа), для экстрималов - запись программного кода 148 .
2. Умение работать в соответствующем ПО - шестнадцатеричный редактор, отладчик и великом и ужасном IDA Pro.
3. Практические знания Ассемблера со знанием принципов внутреннего устройства исполняемого кода.
4. Собственно, представлять примерно где что лежит в коде и как это быстро можно найти по косвенным признакам.

Смогут ли простые смертные это осилить (на изучение могут уйти годы 148 )?

Когда всё это есть, то можно свободно уже заканчивать с UN:C и начинать писать собственные бинарные патчи и DLL-плагины.

(22.07.2020 13:17)Zur13 Wrote:  нужен отдельный туториал, именно так и названый, как найти нужные адреса и данные для UN:C.

Нет (по крайней мере я не знаю) какого-то универсального способа нахождения любых данных в коде игры, которое можно было бы описать доступным и понятным туториалом. Для каждого - свой подход. Самое простое - данные, которые просто лежат по фиксированным адресам (работает метод научного тыка, более подробно - в теме "How to edit HotA?", как раз для слабоподготовленных моддеров, меняющих что-либо в содовском exe, при этом считающих, что изменяют код HoA 148 ). Посмотрим, может быть Игрик сможет разложить всё по полкам.


RE: Исследование героев - PerryR - 22.07.2020 13:43

Never really got into UN:C because I was not able to abstract examples. I'am not sure what knowledge is required to find the addresses, can everybody do it with the right tutorial or does it require the ability to read code?
I guess if you can just list the programs you use with 1 or 2 pictures and 1 or 2 examples like you did with the ballista damage. How to come from the idea to the finished ERM code.
So there is a starting point for people willing to learn/try it.
Thanks Ab
What XEPOMAHT writes already sounds complicated to me, so maybe UN:C is not for everybody.


RE: Исследование героев - XEPOMAHT - 22.07.2020 14:14

(22.07.2020 13:43)PerryR Wrote:  so maybe UN:C is not for everybody.

Да, в скрипте лучше использовать UN:C как можно реже, в случаях, когда по-другому никак. По факту, UN:C - вынужденная мера, так как ERM почти не развивается. Малограмотный моддер с помощью UN:C может наделать ошибок в игре, из-за чего скрипт может приводить к утечкам памяти и случайным вылетам. Если обычные ERM-команды рекомендуется использовать осторожно, то UN:C - втройне осторожно.


RE: Исследование героев - Berserker - 22.07.2020 16:41

igrik, ты внёс неоценимый вклад в развитие игры и мы всегда будем рады даже твоим кратковременным визитам. Ну и, конечно, желаем успехов в росте и саморазвитии на работе.
Возможно, небольшой туториал с картинками пригодился бы + базы для IDA. Пригодился бы многим.

XEPOMAHT, !!UN:C команды следует помещать в именованные функции, которые делать достаточно абстрагированными от деталей реализации.


RE: Исследование героев - daemon_n - 22.07.2020 18:30

igrik, Присоединюсь к списку желающих, хотя к тем, кто хорошо пишет на ЭРМ не отношусь112. Начал немного изучать с++ (на телефоне, да-да96-copy ), так что понял больше половины из того, что написал XEPOMAHT.

К сожалению, отсутствие времени ограничивает нас всех - кого-то больше, кого-то меньше, но для данного темы я постараюсь приложить необходимые усилия и жертвы, особенно если это мне может помочь с пониманием работы приложений и всего подобногоYes


RE: Исследование героев - RoseKavalier - 22.07.2020 18:36

igrik
Best of luck in your new endeavors! We have all been lucky to have you in the community and hope you will continue to drop by for fun))


RE: Как найти нужные адреса и данные для UN:C - wessonsm - 22.07.2020 21:40

На многое не претендую, выше правильно сказали, что все это требует времени, которого всегда не хватает. Но какие-то основы знать было бы полезно.


RE: Как найти нужные адреса и данные для UN:C - daemon_n - 22.07.2020 21:55

Немного граммар наци:
"Конечно же ДА!!!" не требует постановки запятой, поскольку здесь идёт уверенное утверждение.
Выкрутиться здесь можно, назвав предложение перечислением согласий, но это уж совсем за уши натянутоYes

Bes Wrote:ок-ок, исправил 20



RE: Как найти нужные адреса и данные для UN:C - Raistlin - 23.07.2020 00:37

igrik, это будет реально круто, жду с нетерпением)) Лично я относительно неплохо понимаю происходящее на высоком уровне, но вот как вытащить из последовательности байт что-то полезное, мне пока не очень ясно102 Ассемблер я знаю только на уровне арифметических операций и работы со строками, но при желании смогу подучить что-то еще.


RE: Как найти нужные адреса и данные для UN:C - igrik - 23.07.2020 01:59

Хорошо. Давайте начнем с малого и достаточо простого.
Например, постановка задачи недавно описанная Zur13
(19.07.2020 17:43)Zur13 Wrote:  Может ли кто-то подсказать: навык Артиллерия дает 50%,75%,100% шанс баллисте нанести двойной урони дает двойной выстрел. Как это отключить
Итак, разбиваем задачу на 2 этапа:
1. Отключить второй выстрел баллисты при навыке Артилерия
2. Убрать доп.урон с шансами 50%,75%,100%

Для начала нам нужно установить необходимые программы. (Click to View)
Итак, открываем вогобазу через Иду.
Что мы видим: (Click to View)
1 - список функций, которая нашла Ида в нашем exe. На секундочку - эта вогобаза разобрана по исходнику SOD 3.2. Обязательно имейте это ввиду. Это означает, что воговских функций и адресов вы тут не найдёте! Как их искать я объясню позже
2 - это ASM код. Слева вы можете увидеть как идут адреса в шестнадцетеричной системе ".text:00401000". Как с ними работать для ERM? Сначала нам нужно научиться их переводить в десятеричную систему исчисления (ERM работает только с последней). Итак, открываем калькулятор видновс -> вид -> программист -> нажимаем чекбокс "Hex" -> копируем и вставляем например адрес 00401000 (часто пишут его как 401000h или 0x401000) -> нажимаем на чекбокс "Dec" -> и получаем адрес, представленный в десятеричной системе исчисления, т.е.: 4198400. Т.е. код для UN:C будет иметь вид !!UN:C4198400/<еще изучим>/<еще изучим>;
3 - обязательно запомните такую кнопку, как TAB (F5). С помоьщью этой волшебной клавиши, мы можем видеть так называемый С-шный "псевдокод", который намного более читабелен для человека. Ещ` одно нажатие на TAB заставит Иду перейти в ASM код.

Одно замечание на счет ASMа: не поленитесь, изучите что такое команды PUSH, POP, RETN, MOV, CALL, JMP, JNZ, JE. Этого будет хватать на 70% предварительного понимания ASM кода.

Теперь начнем практиковаться.
В ASM коде нажмите клавишу G (или вкладка "Jump->jump to address (7-мая по списку)"), вставте зачение 00475800 в появившееся диалоговое окно, нажмите Ok и сделайте 3-4 скролла средней кнопкой мыши (далее СКМ) вверх.
Вы увидите такое окно: (Click to View)
Мы попали в игровую функцию "Битва: новый раунд". (BattleMgr_NewRound)
Нажимает TAB. Ида декомпелирует и вредоставляет вашему обозрению "превдокод", который выглядит так:
Полистайте скролом и просмотрите этот код. Он более-менее описан уже за вас)
Итог: мы и научились переводить адрес в десятеричную систему из шестнадцетеричной (обратно делается так же, не сложно. Думаю вы и сами догадаетесь.), водить адрес для перехода на участок в exe, и переходить в декомпелированную функцию.

Теперь начнем решать нашу задачу.
Давайте подумаем - а за что нам зацепиться?
Так это баллиста, и вопрос состоял как "отключить второй выстрел", давайте попробуем найти функцию самого выстрела.
Для этого переходим в окно 1 (function name->скролл мышью до конца вверх->клавиша ALT+T->ищем "shoot"->ok)
Ида кидает на с на функцию "AI_Art_ShootingArt_Value". Это не наша. Кликаем в коно №1 (function name) и нажимаем далее "Ctr+T"
"AI_Battle_Defend_Shooter" - опять не наша функция. Опять "Ctr+T"
и так 5-6 раз.
В одном из случаев мы попадаем в функцию "BattleStack__PrepareShoot", т.е. подготовка для выстрела. Для тех, кто не нашел жмем "G" и вводим адрес 43FE80.
Дважды кликаем на неё (функцию BattleStack__PrepareShoot") в окне №1, и после этого в окне №2 откроется псевдокод функции подготовки выстрела. Исследуем его скроллом вниз и находим функцию "BattleStack_Shot". Да, тут Shot написано неправильно, и именно поэтому мы не смогли его найти в поиске раньше. Но вы должны понимать, что вогобаза наполнялась людми, поэтому грамматические ашибки неизбежны. Это именно тот случай. В общем, не важно. Важно что мы добрались до функции выстрела. Детальнее просмотрев код функции подготовки к выстрелу, мы обнаруживаем, что тут целых 3 вызова функции стрельбы.
[spoiler]Image: IDA1.jpg
Давайте разбираться (0043FF79):
Code:
BattleStack_Shot(stackAtt, target); // первый выстрел стека
        if ( ((unsigned int)stackAtt->Flags >> 15) & 1 && target->Count > 0 ) // флаг 15? (2^15=32768 см.ERM хелп флаги монстров: ага, двойная атака. Понятно. "И" цель->количество больше нуля (значит если стек жив))
          BattleStack_Shot(stackAtt, target); // делаем второй выстрел.

          // смотрим далее
        if ( stackAtt->creature_id == 146 && target->Count > 0 ) // ага, катапульта (id=146 и опять если цель жива)
        {
          v10 = stackAtt->SpellDuration[60]; // ERM help-> заклинания: 60=гипноз
          v11 = stackAtt->Side; // проверяем сторону баллисты
          v12 = v10 ? 1 - v11 : stackAtt->Side; // тернарный оператор: если под гипнозом, то инвертируем сторону, иначе сторона баллисты
          if ( *((_DWORD *)&o_BattleMgr->HeroA + v12) ) // тут мы с помощью определения стороны вычисляем номер героя (см.BA:H)
          {
            v13 = v10 ? 1 - v11 : stackAtt->Side; // тернарный оператор: но зачем дублировать код? я хз (опять получаем сторону). Возможно обратная перепроверка на стек под гипнозом...
            if ( *(_BYTE *)(*((_DWORD *)&o_BattleMgr->HeroA + v13) + 221) > 1 ) (проверяем у героя навык артилерии (герой)+221(201+20_Артилерия) > баз.уровня)
              BattleStack_Shot(stackAtt, target); // значит стреляем во второй раз баллистой
          }
        }

Какие выводы из этой функции?!:
1. Баллиста не имеет флага атаковать дважды. Интересненько, значит можно дать ей этот флаг, и при навыке артиллерии она будет стрелять трижды.
2. Код имеет проверку на "гипноз". WTF? На боевые машины дейстует гипноз? Я хз - нужно проверять. Но в любом случае проверка есть, а это значит,что баллиста под гипнозом по "своим" не будет стрелять во второй раз. Тут уже поле для тестов))

Так как же нам запретить второй выстрел баллисты?
Кликаем мышью (ЛКМ) на цифру 146 в псевдокоде и нажимаем TAB (переходим в ASM код)
Видим, строку
Code:
0043FF9A   cmp     dword ptr [esi+34h], 92h
где 92h = 146 (id баллисты).
По хорошему, не имея опыта конкеретно тут нужно запускать Olly, в ней запускать игру (желтое поле снизу срава после запуска игры -> эмакаем F9 сколько потребуется раз.) Запускаем карту (обычно тестовую). Переходим в Olly, жмем Ctrl+G, вводим наш адрес 0043FF9A и видим такую картину:
Image: IDA1.jpg
Тут в выделенной строке считаем кол-во байт (начиная с нуля!!)
На третьем счёте видим число 92000000 (это 4 байта!!)
Code:
0043FF9A  |> \817E 34 92000000         CMP DWORD PTR DS:[ESI+34],92
Значит, чтобы изменить номер монстра со второым выстрелом под втор.навыком артилерии, можно изменить число по адресу 0043FF9A +3 (4 байта).
Заодно сразу переведем в десятичную систему. Итого
!!UN:C4456349/4/146.
По идее можно изменить число 146 на любое другое.
Так, у нас 196 существ в игре. Значит можно поставить число 250.
Но! Важно помнить, что есть такой прекрасный мод как Тифон!
Но у нас не однобайтовое число, которое может содержать значения от 0...256 (и/или 127/-128).
А плюс в Тифоне существ 0...1000.

Можно запилить существо с id 1001 и дело в шляпе для первого вороса.
Хотя, правильнее вообще вырезать это действие (второй выстрел артилерии).
Но слишком много коньяка, да и рука болит от свеженабитой татухи.
А как это сделать более корректно (вырезать функционал второго выстрела, не меняя id монстра)? Это уже совсем другая история...

Продолжение следует...

PS: Для эксперимента, можно подменить номер баллисты на номер городской башни, и протестировать как они будут вести себя под навыком артилерии.
Бонусов 50%,75%,100% при этом не будет, а будут только у первого выстрела баллисты.


RE: Как найти нужные адреса и данные для UN:C - XEPOMAHT - 23.07.2020 02:54

(23.07.2020 01:59)igrik Wrote:  Как устанавливать - я не буду объяснять. Думаю, вы и сами справитесь.

Только для обладателей Windows 6.0 и выше. Для счастливых пользователей Windows XP/2003 максимальная версия IDA - 6.5 (проверял, более новые версии ничерта не работают, лично я пользуюсь 6.1), соответственно, ida-база будет немного другая, нежели у Игрика.

(23.07.2020 01:59)igrik Wrote:  мы можем видеть так называемый С-шный "псевдокод", который намного более читабелен для человека.

Заметьте, только для человека, хорошо знающего Си. Например, я это прочитать без комментариев Игрика не смог бы: куча странных и специфических знаков + английский язык, не зная ни того, ни другого код абсолютно нечитаемый для меня. На ассемблере, когда под рукой есть геройские структуры и имена функций - не сложно в ollydbg, подсматривая в базу IDA (там мне удобен вид иерархии для просмотра функции).

(23.07.2020 01:59)igrik Wrote:  function name->скролл мышью до конца вверх->клавиша ALT+T->ищем "shoot"->ok

А я просто открываю блокнотом H3era.dbgmap и ищу "shot" там 148 (IDA 100 лет ищет, перекапывая всю немаленькую базу, по крайней мере у меня так...).

(23.07.2020 01:59)igrik Wrote:  2. Код имеет проверку на "гипноз". WTF? На боевые машины дейстует гипноз? Я хз - нужно проверять. Но в любом случае проверка есть, а это значит,что баллиста под гипнозом по "своим" не будет стрелять во второй раз.

Гипноз вообще проверяется практически при любом действии отряда, это считай из разряда стандартных проверок в бою.

(23.07.2020 01:59)igrik Wrote:  А как это сделать более корректно (вырезать функционал второго выстрела, не меняя id монстра)?

Тут уже менять сам код - ставить прыжок вместо проверки. Т.е. поменять 2 байта в игре (если без подчистки мусора, что всё-таки рекомендуется делать, чтобы не путать отладчик).


RE: Как найти нужные адреса и данные для UN:C - RoseKavalier - 23.07.2020 04:26

Great start!

My small contributions:
The functions window (1) allows CTRL+F filter which is fast, unlike the disassembly window.

IDA allows import of types (Local types Shift+F1, then Insert), enumeration, structure, types... there is no need to consult outside sources once everything useful is added, you can get something very readable without comments:
Code:
CombatMonster_Shoot_43F620(shooter, target);
        if ( (shooter->flags >> CF_DOUBLEATTACK) & 1 && target->Count > 0 )
          CombatMonster_Shoot_43F620(shooter, target);// shoot second time
        if ( shooter->creatureID == BALLISTA && target->Count > 0 )
        {
          hypnotized = shooter->spellDuration[SPELL_HYPNOTIZE];
          side = shooter->Side;
          hypnotizeSide = hypnotized ? 1 - side : shooter->Side;
          if ( P_CombatManager_699420->Hero[hypnotizeSide] )
          {
            hypnotizeSide2 = hypnotized ? 1 - side : shooter->Side;
            if ( P_CombatManager_699420->Hero[hypnotizeSide2]->secSkills[ARTILLERY] > 1 )
              CombatMonster_Shoot_43F620(shooter, target);
          }
        }

Code:
enum eCreatureFlags
{
  CF_DOUBLE_WIDE = 0x0,
  CF_FLYER = 0x1,
  CF_SHOOTER = 0x2,
  CF_EXTENDED = 0x3,
  CF_ALIVE = 0x4,
  CF_DESTROYWALLS = 0x5,
  CF_SIEGEWEAPON = 0x6,
  CF_KING1 = 0x7,
  CF_KING2 = 0x8,
  CF_KING3 = 0x9,
  CF_MINDIMMUNITY = 0xA,
  CF_NOOBSTACLEPENALTY = 0xB,
  CF_NOMELEEPENALTY = 0xC,
  CF_unk2000 = 0xD,
  CF_FIREIMMUNITY = 0xE,
  CF_DOUBLEATTACK = 0xF,
  CF_NORETALIATION = 0x10,
  CF_NOMORALE = 0x11,
  CF_UNDEAD = 0x12,
  CF_ATTACKALLAROUND = 0x13,
  CF_MAGOG = 0x14,
  CF_CANNOTMOVE = 0x15,
  CF_SUMMON = 0x16,
  CF_CLONE = 0x17,
  CF_MORALE = 0x18,
  CF_WAITING = 0x19,
  CF_DONE = 0x1A,
  CF_DEFENDING = 0x1B,
  CF_SACRIFICED = 0x1C,
  CF_NOCOLORING = 0x1D,
  CF_GRAY = 0x1E,
  CF_DRAGON = 0x1F,
};



RE: Как найти нужные адреса и данные для UN:C - V_Maiko - 23.07.2020 07:04

Fantastic! RoseKavalier, you would be a very important pillar in ERA modding, you should help Berserker and the rest more often, they have all the sources so you can read them and be able to help completely in ERA Yes
Igrik doesn't have much time left for modding, so you would be worthy to carry his knowledge.

You're a powerful hero modifying hexadecimal functions in HotA and SoD, now it's time for you to be more united to the ERA family 177


RE: Как найти нужные адреса и данные для UN:C - PerryR - 23.07.2020 09:51

Thanks for this contribution igrik. On the weekend I will study.
Can you give me your privat phone number so I can call you during night when I have a question about UN:C 96-copy?


RE: Как найти нужные адреса и данные для UN:C - daemon_n - 23.07.2020 10:55

Отличный мануал! Спасибо! Понял почти все, так что осталось лишь попрактиковаться, в идеале же, ещё и научить потом какого-нибудь так же делать166


RE: Как найти нужные адреса и данные для UN:C - wessonsm - 23.07.2020 11:54

igrik, можно что-то конкретное спросить?
Мне нужно научиться находить элементы диалогов (любых) и изменять их (перемещать, менять размер, отключать, включать). Думаю, это не очень сложно, хотя..
В общем, интерфейс изменять.


RE: Как найти нужные адреса и данные для UN:C - XEPOMAHT - 23.07.2020 12:18

(23.07.2020 11:54)wessonsm Wrote:  Мне нужно научиться находить элементы диалогов (любых) и изменять их (перемещать, менять размер, отключать, включать). Думаю, это не очень сложно, хотя..
В общем, интерфейс изменять.

Для ЭРЫ - больная мозоль из-за того, что все диалоги в игре так или иначе модифицируются HD-модом (опять вечная проблема совместимости). Сами адреса находятся очень просто в шестнадцатеричном редакторе по графическим элементам, используемым в диалоге (def'ы, pcx-ы), в основном всё это уходит на стек на вызовах функциях-сборщиках элементов диалога в его конструкторе. Ну и если запомнишь порядок аргументов функции и их назначение (и то и то подписано в комментариях IDA-базы), то можно пробовать делать бинарный патч на это дело (через UN:C может оказаться поздновато, т.к. ERM выполняется самое раннее - только в инструкции карты, а при старте самой игры - только в MoP, а Берсеркер так не хочет, ибо противоречит концепции ERM).


RE: Как найти нужные адреса и данные для UN:C - daemon_n - 23.07.2020 12:55

XEPOMAHT, а если перейти на lua?


RE: Как найти нужные адреса и данные для UN:C - XEPOMAHT - 23.07.2020 13:29

(23.07.2020 12:55)daemon_n Wrote:  а если перейти на lua?

В WoG 3.59 часть диалогов перевели на lua, итог:

1. Полная несовместимость с HD-модом.
2. Изменение-добавление по силам только профессиональным программистам. Обычным моддерам типа меня не особо что-то понятно во всём этом винегрете на lua:

3. Глючность (нормальная поддержка lua в Третьих Героях - скорее из разряда древневоговских мифов, чем реальности).


RE: Как найти нужные адреса и данные для UN:C - Berserker - 23.07.2020 15:52

RoseKavalier, igrik, отличные руководства! 132

XEPOMAHT, а при чём hd-мод до своих DL-диалогов?


RE: Как найти нужные адреса и данные для UN:C - XEPOMAHT - 23.07.2020 16:04

(23.07.2020 15:52)Berserker Wrote:  а при чём hd-мод до своих DL-диалогов?

DL-диалоги, как и все остальные диалоги, центрируются HD-модом под нужное разрешение (без этого, они будут на экране в соответствии с 800х600).


RE: Как найти нужные адреса и данные для UN:C - Zur13 - 23.07.2020 16:32

igrik, супер вроде всё понятно, вечером буду пробовать свою новую проблему порешать 132

RoseKavalier, great addition, but I didn't get it where to get that structs and how to attach them to IDA, maybe you could share your IDA base with structures attached?

P.S. может тему закрепить или вообще в новый раздел модинг для новичков?


RE: Как найти нужные адреса и данные для UN:C - RoseKavalier - 23.07.2020 18:47

My database is the same as everyone else's but modified to use H3API terminology. Here are a few screens to show the process of adding enumerations (same way for structures) - you can copy them from H3API, ERM, or wherever you please.

Cleaning up IDA code
Hope igrik doesn't mind))

We start with igrik's database. I found the function using the FUNCTIONS WINDOW and Ctrl+F using the term 'shoot'. I've numbered some things that can be modified to enhance comprehension of the current section.

Adding an enumeration/structure to IDA. The easiest is SHIFT+F1 but it may take some time to get used to hotkeys.

Now Insert a new local type or use right-click menu.

You get a window like this where you can either create or just copy-paste.

When you are done, the enum/struct exists in local types and is not available globally, so:
* scroll to the bottom
* select your new type
* press enter (or double-click it)

This makes it accessible globally and you will see it in the ENUMS tab.

Go back to PSEUDOCODE section, and set the enuM.

Now you'll get text instead of `15`, while not perfect it shows what the code actually intends to do.

Now let's copy some more enums and repeat the process.

Don't forget to add the type globally!

After you add some more enums, you may get more than one option when choosing what to assign, just pick the one that makes sense.

Now all enums are set for this portion of code, things are looking better; however due to how nwc wrote the BattleMgr original class, there is still some incomprehensible junk next to HeroA... it takes some time to recognize such patterns but this is because the original is an array of 2 _Hero_* instead of being split into attacking and defending hero.

Double-click the HeroA term to be taken to it in the STRUCTURES tab.

We edit this term's tYpe or with right-click...

Finally 'HeroA' no longer makes sense, reName it to 'Heroes'.

Go back to PSEUDOCODE and refresh the decompilation (F5). Now +221 is transformed to secondary skills offset which is again easier to understand.

One last step, add the secondary skills enum, and assign it to the value within.

End result... there is still some work to rename v.. terms but usually this is only necessary when you face complicated pieces of code or are working on disassembling something new.

Actual time was about 3 minutes because I had to look up the enumerations. Now that they exist in this database, I can go elsewhere and do this process in 15 seconds and have code that is readable without relying on outside sources.

I don't ERM (and don't plan to) so I can never even fill half of igrik's shoes, but I hope this is useful. As with everything else, you get better by practice: the more you do the better you'll get at recognizing patterns and assigning the correct values/structures.


RE: Как найти нужные адреса и данные для UN:C - Berserker - 23.07.2020 20:39

RoseKavalier, really useful, thanks.


RE: Как найти нужные адреса и данные для UN:C - V_Maiko - 24.07.2020 03:15

I still think that the one who can master their knowledge is RoseKavalier Rolleyes


RE: Как найти нужные адреса и данные для UN:C - Zur13 - 24.07.2020 03:26

Я тут сделал первую попытку что-то самостоятельно поправить, оказывается я еще кое-что помню из ассемблера, хе-хе. Попробую свои похождения описать в виде небольшого туториала, приступать нужно после того как освоились с тем что описано в туториале igrik.

Возможно получилось немного сумбурно, но я вначале пошел не по тому пути и попытался найти нужное место через исходники ВоГ, но в итоге оказалось что вся нужная информация есть в креш лог файле, но первый этап будет полезен для других задач, когда игра не падает.

У меня задача была такая: при попытке передвинуть боевую машину на поле битвы используя команду BM:P игра падает, нужно это исправить патчем.

Общая информация по ресурсам для этого туториала:

1. В сборке ERA 2.9.14 есть исходники WoG и ERA

HoMM 3 Complete ERA 2.9.14\Tools\Era\Sources\Era 2.6x Delphi 2007\
HoMM 3 Complete ERA 2.9.14\Tools\WoG Sources

2. При падении игра генерирует лог файл с описанием того что произошло при падении, в корне папки с игрой появляется файл HD_CRASH_LOG.TXT

3. Для третьего этапа можно подсматривать нужные команды по ссылкам

Краткое описание ассемблерных комманд
Краткое описание ассемблерных комманд с кодами операций
Подробное описание ассемблерных комманд с кодами операций



Итак часть первая как по ЕРМ ресиверу BM:P найти участок кода игры в ИДА, который его обрабатывает.



Итак часть вторая как с помощью крешлога найти место падения игры.



На данном этапе мы знаем, что падение вызвано тем что у перемещаемого монстра (подводы боеприпасов) нет звукового файла который должен проигрываться при перемещении. Правильным вариантом в этом случае было бы назначение стеку боевых машин звука перемещения или другие правки которые приведут к тому же результату (больше подробностей чуть ниже в сообщении XEPOMAHT). Но в учебных целях мы попробуем исправить это с помощью правки найденого выше участка кода игры "на лету" из ЕРМ скрипта.

Итак часть третья как пропатчить КДЕ2 под FreBSD, шутка Sm , как исправить креш патчем кода с помощью UN:C.



Вопрос знатокам, насколько плохо то что я сделал в третьей части и можно ли такое проворачивать?


RE: Как найти нужные адреса и данные для UN:C - igrik - 24.07.2020 10:19

Zur13, у тебя (в третьем варианте) достаточно хорошее решение. Ты же восстанавливаешь параметр, после того как сделал необходимые тебе действия. Я думаю это наиболее правильных ход, применимый для данной ситуации.
По началу оно все сумбурно выходит. Но это дело только практики)

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

XEPOMAHT, напомни - в чем проблема у тебя поставить Win 7? В слабом ПК? А XP не потащит? Просто использование вогобазы в текстовом режиме то еще извращение.

RoseKavalier, I am delighted, and I never cease to be amazed at your knowledge. Your help is invaluable!


RE: Как найти нужные адреса и данные для UN:C - Zur13 - 24.07.2020 11:29

(24.07.2020 10:19)igrik Wrote:  Zur13, у тебя (в третьем варианте) достаточно хорошее решение. Ты же восстанавливаешь параметр, после того как сделал необходимые тебе действия. Я думаю это наиболее правильных ход, применимый для данной ситуации.
По началу оно все сумбурно выходит. Но это дело только практики)

Стараюсь вот по возможности. Хороший туториал мне с ним оказалось легко освоиться 132

А эти адреса не изменяются при применении разного рода бинарных патчей или еще чего-либо? Насколько это решение надежно?

И еще вопрос, то фактически на чем я застрял при подходе с помощью исходников, как ИДА подключить к героям в режиме дебага? У меня при попытке выполнить Attach to process к героям ИДА падает вместе с героями. Хотелось с помощью точки останова посмотреть то место кода которое я нашел с помощью исходников ВоГ.


RE: Реверс игры для чайников - igrik - 24.07.2020 12:08

Продолжим...
Нам необходимо решить вторую часть вопроса: убрать доп.урон у баллисты шансами 50%,75%,100%

Итак, поехали.
Мы остановились на адресе 0043FF79. С него и начнем.
Если вы куда-то переместились в другое место, то кликаем ЛКМ в закладку IDA-View, жмем горячую клавишу G, вставляем в окно с вводом адреса 0043FF79 и жмем OK. В итоге мы перепрыгнули на первый вызов функции стрельбы стеком "BattleStack_Shoot". Дважды кликаем по этой надписи "BattleStack_Shoot" и декомпилируем код (F5)

Смотрим на заголовок функции:
void - тип возвращаемого результата функции: нас пока что не интересует
__thiscall - соглашение о вызове: нас пока не интересует
BattleStack_Shoot - название функции: и так понятно

(_BattleStack_ *shooter, _BattleStack_ *target): аргументы функции
Это структуры стеков в битве (BM:Z) стрелка и цели. Просто запомним это

Дальше включаем логику: мы ищем увеличение урона, значит нам необходимо найти участок кода с расчетом урона, либо функцию которая расчитывает наносимый урон, и которая вызывается в нашей функции стрельбы.

Исследуем код функции стрельбы:
1. Сначала видим расчет удачи, и ее шансы. Нас не интересует - идем дальше.
2. Дальше видим вызов функции отрисовки полёта снаряда "Battle_Stack_DrawShot_Bullet". Проходим мимо
3. Видим код декремента (вычитания единицы) боеприпасов у стрелка, и проверку на наличие артефакта №5 (повода боеприпасов). Интересно, но всё равно идем дальше
4. Далее пошёл первый if(Magog). Теперь вы знаете где сделана проверка на стрельбу по площади. Тут нужно быть осторожным в правках. ВОГ мог модифицировать код в данном участке. Вспоминаем есть ли приколы в воге, связанные с магогами? Вроде нет. Можно загрузить Ольку (Olly Debager) по данному адресу 0043F729 и помотреть на чужие хаки
Вроде всё чисто. Это было чисто для интереса, поэтому идем дальше. Ах нет! Тут мог вклиниться Тифон, если он включён! И я даже более чем уверен, что Тифон ставит сюда свою проверку. Но проверять уже не будем)

5. Листаем дальше, тут мы видим у Магов загрузку звука фаербола, загрузку дефа анимации и т.п. Листаем до следующего интересного места (хотя тут всё интересно, если честно).
6. Итак, находим проверку на Личей (стр.206)!
else if ( monType == LICH || monType == POWER_LICH )// Lich, Power Lich
Вспоминаем: в воге облаком Личей стреляет еще Драколич. Ради интереса проверяем в отладчике Ольке
И что мы тут видим? А код то разный: в Иде (exe SoD 3.2) и Олли (exe ERA аля TE)
Забегая далеко вперед скажу вам сразу, что если вы увидите адреса в пределах 0x701000-0x785000 при вызове функций - это установил хук наш родной Вог. Вот так выглядит эта нетривиальная проверка:
Так что при попытке подмене стрелка с облаком смерти по адресу 0043FA1F !!UN:C4454945/1/x; вы получите неожиданный вылет из игры. Вот здорово! Поэтому если не проверять изменяемые вами данные в отладчике, можно долго биться головой об стену.
Надеюсь я вас предупредил о такого рода неожиданностях.

7. Поехали дальше - пролистываем (и конечно же изучаем код действия стрельбы Лича) до следующего "else"
8. Нажмите TAB и обратите внимание, что в ASM коде мы переместились от проверки на Лича всего на несколько строк, а декомпилированном коде TAB (F5) мы пролистали порядка 20-30 строк. Такое бывает)) Привыкайте.
Если сбились с адреса, то это 0043FA38
Переходим опять в декомпилированный код и что мы видим:
Видим такую логику: если цель есть, считаем базовый урон, считаем урон по цели, наносим урон цели.
9. Нам нужно найти бонус Баллисты. Заходим сначала в первую функцию - расчет базового урона. Двойно клик по "Battle_Stack_CalcDamage". Изучаем её, и понимаем простую логику:
    - если на стрелке висит забывчивость, режем кол-во стрелков вдвое
    - если баллиста - урон умножаем на силу атаки героя. Это не наш случай. Идем дальше
    - проверки на заклинания Благословления и Проклятия (увеличиваем или режем величину урона)
    - генерирем силу урона в пределах от Min...Max
    - возвращаем силу урона
Итог: решения нашей задачи мы не нашли.

Нам нужно проверять следующую функцию. Жмем G и вводим адрес 0043FA3C (прыгаем туда, где были в начале п.8)
10. Делаем двойной клик по BattleStack_CalculateDamageToMonster и внутри этой функции стразу же видим функцию BattleStack_Calc_Damage_BonusesplyDoubleDamage. Название уже говорит о бонусах. Здорово. Нам сюда
11. Делаем двойной клик по функции насчета бонусов BattleStack_Calc_Damage_BonusesplyDoubleDamage. В ней есть проверка на дабблурон Черных рыцарей. Листаем дальше и находим проверку на баллисту (146) 004435B2
Изучаем детально код. Тут и кроется решение нашей задачи. Решений можно придумать 3-4 вида. Все они отличаются: можно подменить номер монстра, можно вырезать этот код вообще, поставив безусловный переход. Можно обнулить шансы бонусов балисты "BallistaDamageBonus 0063B810", можно изменить номер вторичного навыка, и сделать скажем зависимость от Магии Земли (да, давайте её еще сильнее усилимSm )
В общем достаточно большое поле для манёвров.

Итак, задачу мы решили.

В следующий раз возьмёмся на нахождение адресов элементов диалогов, что на порядок сложнее нашей текущей задачи.

(24.07.2020 11:29)Zur13 Wrote:  А эти адреса не изменяются при применении разного рода бинарных патчей или еще чего-либо? Насколько это решение надежно?

И еще вопрос, то фактически на чем я застрял при подходе с помощью исходников, как ИДА подключить к героям в режиме дебага? У меня при попытке выполнить Attach to process к героям ИДА падает вместе с героями. Хотелось с помощью точки останова посмотреть то место кода которое я нашел с помощью исходников ВоГ.
Конечно адреса могут меняться, если кто-то это намеренно сделает. Во второй части я показал один из таких примеров.

Я Иду ни разу не подключал к героям. Тут не подскажу. Только Олли.


RE: Как найти нужные адреса и данные для UN:C - XEPOMAHT - 24.07.2020 12:45

(24.07.2020 03:26)Zur13 Wrote:  Вопрос знатокам, насколько плохо то что я сделал в третьей части и можно ли такое проворачивать?

Увы, с точки зрения программного кода это называется костылями. Так делать не надо (и учить плохому других наверное тоже).

Давайте будем разбираться.

Как найти более-менее правильно причину ошибки?

1. Смотрим содержимое EIP в логе ошибки. По примеру - 4B4BAA.
2. Открываем базу IDA и переходим по этому адресу. Видим это:

mov ecx, [eax+1Ch]

3. Смотрим лог дальше. Он нам говорит следующее:

read of address: 0x0000001C

4. Анализируем. Игра что-то пытается прочитать по адресу 1С, чего не должно быть (т.к. данные ниже адреса 400000 к коду игры никакого отношения не имеют). Смотрим причину - в eax почему-то записан ноль. Хорошо, смотрим строчку выше:

mov eax, [esi+170h]

Теперь осталось узнать, что такое esi. Вспоминаем, что в esi обычно сишный компилятор записывает данные для длительного хранения. Хорошо, тыкаем в IDA по esi и в коде сразу все места, в которых участвует esi, выделятся желтым цветом.

5. Анализируем далее. По идее тут надо бы идти к источнику esi, но это очень долго (т.к. адрес, который в него записан, может передаваться через сотню функций, без опыта пока что туда лучше не лезть). Попробуем побыстрее. Смотрим функции, для которых что-то считывается из esi. Так, это различные операции с отрядами на поле боя. Может тогда комбатменеджер? Нет, он в нашей функции получается отдельно. Может структура стека??? Хорошо, открываем структуру и сравниваем адресацию.

Выше в функции можно найти следующие строчки:

mov eax, [esi+38h]
mov dword ptr [esi+3Ch], 2
mov dword ptr [esi+40h], 0
mov eax, [esi+44h]

В структуре это:

Позиция dd ?; 38
def_group_ix dd ?; 3C - animation
def_frame_ix dd ?; 40 - animationFrame
Mon2SqDirectionForDWCr dd ?; 44 - secondHexOrientation

Смотрим названия функций, для которых эти данные используются. Думаю логично. Скорее всего esi - и есть структура стека.

Так мы разобрали небольшой участок кода, приступим к исправлению ошибки.

6. Смотрим опять то, с чего начали, но уже с комментариями:

mov eax, [esi+170h] - по структуре - получение Загруженный_звук_перемещения
mov ecx, [eax+1Ch] - тут нужна структура загруженного звука (или что-то вроде того), но у нас её нет, ну и ладно, не страшно.

7. Собственно, чтобы игра не падала, можно:
а) записать в структуру какую-нибудь загруженную озвучку, например скопировать туда адрес звука выстрела/урона/защиты/смерти, что короче подойдёт (если не будет резать слух) - выполнимо и ERM-ом в триггере перед экраном битвы (когда все звуки загружены).
б) ставить перехват кода на это место, проверять 0 в eax, если там ноль, то записывать в ecx ноль (т.е. чтобы функция остановки звука не останавливала звук, которого нет, т.к. там простая проверка на 0 по аргументу, который идёт на функцию) - бинарным патчем или уже dll.
в) грузить всю озвучку для боевой машины - так же искать место в коде, отвечающую за это дело, конкретно в месте проверки на боевые машины (там либо флаг боевой машины, либо номера монстров, в ощем тут опять применить навыки поиска адресов от Игрика). Ну и не забыть добавить саму озвучку в архивы с игрой (если её там нет).

(24.07.2020 10:19)igrik Wrote:  напомни - в чем проблема у тебя поставить Win 7? В слабом ПК?

Все виндовозы, начиная с финальной версии Win 6, попросту виснут при первом запуске их установщика. Первая бета-версия Вислы устанавливалась и даже работала (но на ней невозможно работать).

(24.07.2020 10:19)igrik Wrote:  А XP не потащит?

На XP IDA 7 так же не работает (у меня не вышло заставить её работать).

(24.07.2020 10:19)igrik Wrote:  Просто использование вогобазы в текстовом режиме то еще извращение.

У меня вог-база сохранена в IDA 6.1 (пересохранял с версии 5.5).


RE: Как найти нужные адреса и данные для UN:C - Zur13 - 24.07.2020 13:32

(24.07.2020 12:45)XEPOMAHT Wrote:  
(24.07.2020 03:26)Zur13 Wrote:  Вопрос знатокам, насколько плохо то что я сделал в третьей части и можно ли такое проворачивать?

Увы, с точки зрения программного кода это называется костылями. Так делать не надо (и учить плохому других наверное тоже).

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

Не критика, я просто прочитал описанный способ поиска проблемы без использования псевдокода, и это для меня оказалось сложным, при том что я в этом месте довольно много провел времени в ИДА, думаю для человека совсем без опыта боюсь это окажется совершенно непонятно 105

Правка: попытаюсь чуть подробнее описать места, которые для меня показались сложными

1. "Теперь осталось узнать, что такое esi. Вспоминаем, что в esi обычно сишный компилятор записывает данные для длительного хранения.", я с сишным компилятором не работал на уровне ассемблерного кода, потому для меня это неочевидно, к примеру если я буду разбиратть другой участок кода и окажется что сишный компилятор использует для длительного хранения не только esi, ну или еще есть какие-то штуки которые стоит знать о сишном компиляторе для успешного анализа ассемблерного кода? Может стоит привести какую-то ссылку где можно больше про это почитать?

2. "В структуре это:", тут я не совсем понял как можно перейти к структуре из приведеного участка кода, куда нужно смотреть или нажимать?


RE: Как найти нужные адреса и данные для UN:C - XEPOMAHT - 24.07.2020 15:08

(24.07.2020 13:32)Zur13 Wrote:  я просто прочитал описанный способ поиска проблемы без использования псевдокода, и это для меня оказалось сложным, при том что я в этом месте довольно много провел времени в ИДА, думаю для человека совсем без опыта боюсь это окажется совершенно непонятно.

Опыт приходит со временем. Например у меня по-другому: не могу прочитать псевдокод, он мне просто не понятен (мозг уже подсажен на Ассемблер, а кучи Си-шных символов заводят меня в тупик), поэтому работаю только с чистым диассемблированным кодом. Да и самой IDA я пользуюсь только когда нужно посмотреть код в именованном виде, а не в совсем сыром. Все работы - только в Ollydbg (то же добавление новых городов было сделано чисто через Ollydbg+исходники WoG 3.59, без использования IDA).


(24.07.2020 13:32)Zur13 Wrote:  Может стоит привести какую-то ссылку где можно больше про это почитать?

Если кратко, то eax, ecx - "горячие" регистры, которые компилятор в коде пытается перезаписывать в первую очередь (т.е. долго в них ничего не хранится, а если стоит вызов функции, то после него там уже в 99% случаев или мусор, или данные, возвращаемые функцией).
Ну а ebx, edi, esi - для более длительного хранения, обычно компилятор после вызова функции сохраняет их в стеке (если нужно) и восстанавливает после выполнения функции в её самом конце.

Естественно, что сторонний код может быть написан по-всякому (например в воговском коде может быть всё что попало - там компилятор уже собирает код наиболее безопасно, сохраняя все регистры по 100 раз, ну а в MoP-коде обычно ищутся свободные регистры, там не любят сохранять всё подряд).

(24.07.2020 13:32)Zur13 Wrote:  тут я не совсем понял как можно перейти к структуре из приведеного участка кода, куда нужно смотреть или нажимать?

Никуда не нажимать, тут чисто ручная работа (да простят меня лентяи). Блокнотом открываем структуру и сравниваем её со значениями из кода.


RE: Как найти нужные адреса и данные для UN:C - igrik - 24.07.2020 15:38

(24.07.2020 13:32)Zur13 Wrote:  Правка: попытаюсь чуть подробнее описать места, которые для меня показались сложными

1. "Теперь осталось узнать, что такое esi. Вспоминаем, что в esi обычно сишный компилятор записывает данные для длительного хранения.", я с сишным компилятором не работал на уровне ассемблерного кода, потому для меня это неочевидно, к примеру если я буду разбиратть другой участок кода и окажется что сишный компилятор использует для длительного хранения не только esi, ну или еще есть какие-то штуки которые стоит знать о сишном компиляторе для успешного анализа ассемблерного кода? Может стоит привести какую-то ссылку где можно больше про это почитать?

2. "В структуре это:", тут я не совсем понял как можно перейти к структуре из приведеного участка кода, куда нужно смотреть или нажимать?
1. Не нужно настолько глубоко анализировать ассамблерный код, если ты его плохо знаешь. Я его анализирую только в редких случаях - если я не могу до конца понять всевдокод. Но псевдокод намного более читабелен. Используй его.

2. Смотри. Ты просто наводишь мышь на непонятное тебе выражение. В данном случае мы говорим о "Mon". Тебе подсвечивается выражение так
Нажимаешь на "Mon" ПКМ->jump to local type...
Тебя перекидывает на очередное окно, говоря что это.
Даблклик, и мы понимаем, что это структура монстра _BattleStack_*. Потому ты будешь это понимать сходу, но нужен небольшой опыт.
Лучше всего структуры, да и вообще многие места описаны в H3API от RoseKavalier. Это действительно самое богатое API для HoMM3, из которых я видел вживую. Правда у него идут уже свои названия H3CombatMonster = _BattleStack_*.

PS: например вот только что я увидел из заголовочников RoseKavalier, что поле shooter->field_70 это shooter->isLucky


RE: Реверс игры для новичков - Berserker - 24.07.2020 17:35

Zur13, при вылете я обычно делаю так:
Открываю: Debug\Era\exception context.txt

Здесь тоже виден адрес вылета, регистры, стэк. Но именование функций лучше, чем в HD-логе, поскольку используются отладочные карты символов из папки DebugMaps. Для Эры это часто указание файлов и строк в исходниках (иногда ошибается, указав огромное смещение от функции):
Само исключение описывается в виде понятной текстовой ошибки. Для каждого 4-байтового значения даётся помимо шестнадцатеричного представления ещё десятеричное (int: xxx) и значение, которое лежит по адресу, если использовать текущее число как адрес другого числа или строки (pint: xxx, str: "...."). Таким образом видны строки перед вылетом и потенциальные указатели.

Далее стоит открывать Debug\Era\erm tracking.erm и посмотреть последние выполненные ЕРМ-события и команды, где видны имена файлов и строки.
Наконец, если подозреваем, что дело в некорректном значении переменных, можно заглянуть в erm memory dump.txt, где будет всё, от строк и до именованных глобальных переменных.

И здесь можно глянуть, что за моды и патчи использовались при вылете:
mod list.txt
patch list.txt
pe modules.txt


RE: Реверс игры для новичков - igrik - 24.07.2020 17:50

Находим элементы диалогов.

Так тут всё чуточку сложнее. Для начала нужно знать о некоторых факторах.
1. У нас есть прекрасный HD мод, основная цель и задача которого как раз таки и заключается в работе с диалоговыми окнами для изменения разрешения игры. Чаще всего HD мод просто изменяет координаты расположения диалога, для того чтобы последнее распологалось в центре экрана при разрешениях отличных от 800x600. Но есть и исключения: яркий пример окно встречи героев при включенном твике <UI.Ext.SwapMgr> {0,1,2} = 1 или 2.
2. Есть плагин wog native dialogs, который тоже не имеется в вогобазе. Да и стандартные воговские диалоговые окна, такие как ВогОпции, Диалог Командира, Опыта армий, сброса артефактов на землю и т.п. вы разобрать не сможете, да и ERMу они впринципе неподвластны.

Это означает, что мы можем что-то найти и изменить только в диалогах SOD (которые есть в ERA). Сразу же отсюда отметаем диалоги, которые работают до старта ERM (до начала загрузки карты), такие как: выбор сценария, кампании, настройка случайной карты, рекроды, загрузить игру и т.п.

Начнем, пожалуй с самого наиболее используемого диалога - окно героя.
Наша задача - повесить дефы морали и удачи героя ему на плечи Sm Давайте пробовать.

Для начала, пожалуй, нужно описать вам универсальный способ выхода на момент создания диалога.
Итак, в игре есть универсальная функция, которая максимально часто используется при создании диалогов 0041AFA0 DlgLvl1_Ctor.


1. Открываем ИДУ
2. Нажимаем G и вбиваем адрес функции 0041AFA0 DlgLvl1_Ctor
3. Перепрыгиваем на неё и переходим в ASM код в строку заголовка функции
4. Нажимаем CRTL+X и видим, как ИДА указала нам все места вызова этой функции. И мы видим множество диалогов.
Всё читаемо? Это результат тонны часов людей, которые разбирали эту базу.
Лет десять ранее это бы выглядело вот так
Ни черта не понятно, не правда ли?))

Во всех этих функциях ищем что-то похожее на название диалога героя.
Нашли Dlg_HeroInfo_Create (у вас она называется скорее всего под другому, потому что я потихоньку всё же пилю её). Нажимаем ОК. Нас перекидывает на адрес 004DE9B3. Нажимаем F5 и видим длиннющую функцию создания диалога.

Теперь нужно узнать айдишники (id) дефов морали и удачи героев. Открываем справку ERM-> триггер CM-> подкоманда I -> условно обозначеное "место" -> кликаем в "место" -> ищем "Области в окне героя" -> тут и ищем айдишники морали и удачи. Нашли: id 116 и 117.

Переходим в ИДУ, нажимем ALT+T, вводим значение 116, ОК.
Нас перебросило на переменную v116. Так ида объявила переменную, но это совершенно не то. Жмем далее (Ctrl+T), и так 3 раза. Нас перекинуло куда-то дальше. Смотрим детально куда:
и видим место создания какого-то дефа.
Сравниваем название: DlgDef_BuildAndLoadDef(v231, 182, 184, 44, 44, 116, aImrlb_def, 0, 0, 0, 0, 16);
Здорово, мы нашли то что скали.
Заходим в лод в дате, ищем этот файл (деф), вытаскиваем его, и определяем его размеры: 44х44. Отлично, зная размеры мы можем их найти тут.
DlgDef_BuildAndLoadDef(v231, 182, 184, 44, 44, 116, aImrlb_def, 0, 0, 0, 0, 16);
Значит перед ними, возможно, идут координаты установки дефа. (... Это схема простая ...)
Ставим курсор в "182" и нажимаем Tab, в асм коде мы видим "push 182" (или push B6h) и видим рядом адрес 004E0D01. Первый адрес нашли. Теперь будем проверять)

В этот раз, чисто для практики используем Olly.
Для начала нужно сделать так, чтобы игра у вас не запускалась в полноэкранном режиме!! Это очень важно. Олька часто останавливается сама при запуске игры, и вы хорошенько за***тесь, прежде чем запустите игру.
Поэтому пользуемся HD модом, и желательно в 32 битном режиме с разрешением 1180х664. Настроили. Запускаем игру и жмем F4. Игра перешла в оконный режим. Отлично. Это нам и нужно. Закрываем игру.

Далее. Запускаем Ольку, указываем ей путь до исполняемого файла "h3era HD.exe" и ждём. Первая прогрузка будет долгая (Оля там чё-то обрабатывает и создает дампы). В следующие разы будет быстрее. Внимательно смотрим на нижний правый угол: только начал загружаться Вог, и Олька сходу остановилась сама.
Жмём F9 и не паримся. Если еще будут такие же остановки, жмем F9 столько раз, сколько это нужно.
Это можно загнать в исключения, но я за 3-4 года пользования ей так и не сделал этого. Пофиг.

Итак, игра запустилась. Запускаем нашу тестовую карту (обычно у каждого модера такая есть), если нет, то запускаем стандартный Аррогенс))

Опять переходим в Ольку, копируем адрес 004E0D01, нажимаем Ctrl+G (бесит, когда работаешь одновременно с Идой), вставляем адрес, ОК, и нас перекинуло в нужное место.
Теперь смотрим внимательно! и видим
Code:
004E0D01  68 B6000000   PUSH 0B6
Тут важно число B6000000 (182 в десятичной). По нему, мы можем определить, что число четырехбайтовое, а значит мы может запихнуть в него огроменное число. Намного сложнее работать с однобайтовыми числами. Много туда не влезает. И это порою бывает очень удручающе... Ну да ладно

На адресе 004E0D01 нажимем "пробел", вылезло окно редактора кода на ASMе. Теперь будем править координату Х иконки морали, для этого придумыаем число, например 405, но в Олю можно вводить только шестнадцатериные числа. Не проблема, переводить их мы уже умеем). Открываем калькулятор, и получем число 405 = 195(h). Вставляем это число, нажимаем ОК и открываем окно героя. Смотрим что получилось
Круто!
Поехали проводить ту же процедуру и с координатой Y, она в коде находится на одну строку выше 004E0CFC.
Ремарка, нужно обязательно поставить галочку "Fill rest with NOPs", иначе вы сломаете код (и начнете процедуру заново в Ольке), потому что Оля захочет записать это число как однобайтовое, и запишет его, а "хвост" останется и получится какая-то лапша, которая обязательно вылетит к чертовой матери. Ставьте галку)
Впихиваем координату 43 (PUSH 2B). Проверяем: мы повесили мораль на плечо герою

Ту же самую процедуру делаем и с Удачей (надеюсь вы справитесь сами).
Должно получиться вот так:

Адаптируем код на ERM, но тут не обязательно париться с NOPами

Image: index.png


RE: Реверс игры для новичков - daemon_n - 24.07.2020 19:39

igrik, спасибо огромное за гайд! Его бы и за пределы форума распространить, поскольку далеко не все программисты сидят здесь.105


RE: Реверс игры для новичков - RoseKavalier - 25.07.2020 04:10

I've used igrik's method quite a bit for discovering dialog items... easy documentation could be generated with a plugin by hooking dialog creation methods
e.g. Main menu dialog:
Code:
address  x   y   id  name
0x4fba62 540  10 101 mmenung.def
0x4fbacb 532 132 102 mmenulg.def
0x4fbb34 524 251 103 mmenuhs.def
0x4fbb9d 557 359 104 mmenucr.def
0x4fbc06 586 469 105 mmenuqt.def
Of course there would be some manual work needed afterwards to categorize and document.


RE: Реверс игры для новичков - daemon_n - 25.07.2020 11:09

RoseKavalier, nice! There weren't any numbers of theirs sizes near x/y positions?


RE: Реверс игры для новичков - RoseKavalier - 25.07.2020 17:45

Well yes, but is it useful when you have everything else?

How about something like this?
Image: 4nnoXOy.png


RE: Реверс игры для новичков - daemon_n - 25.07.2020 19:30

RoseKavalier, for this buttun it is, cause it answers for mouse click position and def cadre changing


RE: Реверс игры для новичков - wessonsm - 26.07.2020 00:10

daemon_n, если я не ошибаюсь, позиции и размеры кнопок главного меню одинаковые в WoG и в Complete, если нужны адреса, то они у меня есть, могу предоставить, мне только нужно привести это всё в читаемый вид, чтобы выложить.


RE: Реверс игры для новичков - daemon_n - 26.07.2020 01:43

wessonsm, но еще есть сод, и разные языки, и разные позиции, и разные размерыYes, и credit.smk, и credit.bik


RE: Реверс игры для новичков - wessonsm - 11.02.2022 06:09

В базе IDA такой код:
Code:
text:00567DDE                     lea     ecx, [ebx-0B4h];

   
Я уверен, что здесь -0B4h это смещение координаты дефа, которое я хочу изменить, то есть lea здесь выполняет арифметическую операцию.
Я хочу сделать бинарный патч, который меняет это смещение.
Но в 16-ричном виде команда имеет вид 8D 8B 4C FF FF FF, здесь нет и намека на нужное мне значение 0B4.
Как быть? Как изменить команду к примеру скажем на lea ecx, [ebx-0A4h] бинарником?


RE: Реверс игры для новичков - XEPOMAHT - 11.02.2022 06:57

(11.02.2022 06:09)wessonsm Wrote:  Но в 16-ричном виде команда имеет вид 8D 8B 4C FF FF FF, здесь нет и намека на нужное мне значение 0B4.
Как быть? Как изменить команду к примеру скажем на lea ecx, [ebx-0A4h] бинарником?

Значение отрицательное. Соответственно минус A4h = ffffff5ch. Т.е. бинарным патчем меняешь 1 байт 4С на 5С в указанном тобой примере.


RE: Реверс игры для новичков - wessonsm - 11.02.2022 07:03

XEPOMAHT, точно! Я забыл о записи отрицательных чисел.
Спасибо.


RE: ERA III - daemon_n - 18.05.2022 20:25

(18.05.2022 16:21)wessonsm Wrote:  Случайно обнаружил ошибку в 9999 era - stdlib.erm (вылетало).


Имя дефа или pcx - это тоже строка, поэтому последнее условие нужно изменить на


wessonsm, не могу подтвердить - аргументом передаётся int
https://github.com/daemon1995/h3era_plugins/blob/main/NewHeroDlgItems/dllmain.cpp#L349

ну или вот сама ф-ция
int __thiscall Dlg_SendItemCommand(_Dlg_ *dlg, int cmdtype, int cmdsubtype, int itemind, int newparval)

всё равно ждём в дискорде - там активно ведём разработку Spiteful


RE: ERA III - wessonsm - 18.05.2022 21:17

daemon_n, мне кажется, для строки этот int означает указатель на строку (адрес строки), разве нет?
И как тогда передать в функцию имя дефа или pcx (скажем somedef.def), какое число использовать?
А вообще я ориентировался по описанию DL:A в хелпе (так как функционал аналогичный) - там имя файла это текстовая переменная.


RE: ERA III - daemon_n - 18.05.2022 21:19

wessonsm, вот без преобразования int и передаётся по умолчанию

я тоже могу ошибаться - проверить можно легко, использовав ф-цию на практике. Скажем, имя def файла в первом слоте существ в окне героя.


RE: ERA III - wessonsm - 18.05.2022 22:02

daemon_n, можно передать адрес числом, но для этого нужно сначала его узнать.
Хоть это и 1 строка в скрипте, но все же.
А вот такой вариант вполне работает без дополнительных уловок, если внести предлагаемое исправление (я проверял):
!!FU(H3Dlg_SendCmdToItem):P(DlgAdress)/(ItemId)/(DLG_CMD_SET_PCX)/^somepcx.pcx^;
А без исправления будет вылет, так как игра попытается прочитать что-то по несуществующему адресу.


RE: ERA III - Berserker - 18.05.2022 22:29

Дп, всё верно. Для строк нужен адрес.


RE: ERA III - igrik - 19.05.2022 19:29

(18.05.2022 21:17)wessonsm Wrote:  daemon_n, мне кажется, для строки этот int означает указатель на строку (адрес строки), разве нет?
И как тогда передать в функцию имя дефа или pcx (скажем somedef.def), какое число использовать?
Все верно - это указатель на строку.
В героях строка может быть двух видов: массив символов, либо класс std::string (в героях он чуточку по своему сделан, но в целом это тоже самое). В обоих случаях они оба лежат в так называемой "куче" - такая область оперативной памяти (а есть ещё "стек"). И для получения доступа к ним и нужен указатель.
Следуем дальше: в функцию ты передаешь не саму строку (потому что вся строка может занимать очень очень много места (например всю "Войну и Мир")), а лишь указатель на строку, который весит всего 4 байта - это и есть тип int, хотя я не понимаю почему он знаковый (как по мне он должен быть uint). А программа сама потом по этому указателю найдёт данные в куче. Очень часто вы можете встретить проверку на null, которая означает что если адрес в указателе лежит - данные есть, если в указателе 0, то к данным невозможно обратиться

wessonsm, daemon_n, если вы занимаетесь реверсом - вам нужно очень хорошо понимать разницу между значимыми типами и ссылочными типами данных.
А для этого нужно понимать разницу что такое стек и куча.
Указатель (4 байта) лежит в стеке.
Данные (размер может быть очень большой) лежат в куче.
К стеку скорость доступа многократно выше, чем к куче. Но стек имеет очень маленький размер в сравнении с кучей.

Настоятельно рекомендую просмотреть весь этот плейлист для более глубокого понимания этих важных вещей.


RE: Реверс игры для новичков - daemon_n - 01.06.2022 05:45

Лично мне больше понравился этот плейлист на данную тему.


RE: Реверс игры для новичков - wessonsm - 03.09.2022 06:26

Расширение и перенос структуры в памяти как делается?
Я так понимаю, нужно пропатчить все места в коде игры, где идет обращение к полям этой структуры?
А как тогда убедиться, что нашли действительно ВСЕ такие места?
Ведь значительная часть кода не разобрана даже.


RE: Реверс игры для новичков - daemon_n - 03.09.2022 09:17

wessonsm, берём адрес указателя на структуру и пишем туда указатель на свою, расширенную структуру.


RE: Реверс игры для новичков - wessonsm - 03.09.2022 11:01

(03.09.2022 09:17)daemon_n Wrote:  wessonsm, берём адрес указателя на структуру и пишем туда указатель на свою, расширенную структуру.
Это надо осмыслить, желательно на примере.
Есть какой-то пример кода хотя бы один?


RE: Реверс игры для новичков - Berserker - 03.09.2022 11:35

1) Выделить память динамически (VirtualAlloc | malloc | new | GetMem in Delphi) или статически uint8_t newStruct[1000];

2) Пропатчить все обращения в коде к старому адресу структуры на новый.
Найти все обращения к структуре не просто. Olly Debugger 1 — ПКМ на команде ≠ Search For — All Constants — вводим базовый адрес структуры.
Или нажимаем на строку с командой и обращением к структуре, а затем ПКМ — Find all references to — Address constant.
Но это только часть. Если есть обращение к N-ному элементу структуры, то компилятор заранее рассчитывает адрес как базовый + индекс элемента * размер элемента.
Тут, возможно, ПКМ в IDA и что-нибудь вроде find all x-references. Может, комрады подскажут. Патчить нужно соответственно, на новый адрес + (адрес в коде - старый адрес).

3) Уведомить Эру, что структура перенесена.
RedirectMemoryBlock (старый адрес структуры/массива, старый размер всего массива структур/структуры, новый адрес структуры/массива).


RE: Реверс игры для новичков - daemon_n - 03.09.2022 11:45

Berserker, сколько ни видел обращений к структурам, они все по ссылке идут.
Так понимаю, уведомить Эру надо, потому что она раньше всех плагинов на адреса цепляется?


RE: Реверс игры для новичков - Berserker - 03.09.2022 12:47

Да ладно. Куча статических структур, весь вог в статике. Если память выделяется динамически, то проще всё, конечно. Просто перезаписываем адрес. Вот только если память очищается время от времени, выделять нужно её геройской функцией malloc.

Эра патчит много вог-кода. А там статические адреса. UN:C тоже работает со статическими. Вот где хранится единственно верный указатель на массив информации о монстрах в игре?
В OnAfterWoG достаточно вызвать RedirectMemoryBlock. Для бОльшей совместимости могу адреса следующих структур получать при отсутствии перенаправлений из указателей:

Code:
procedure OnAfterStructRelocations (Event: GameExt.PEvent); stdcall;
begin
  SecSkillNames := GameExt.GetRealAddr(SecSkillNames);
  SecSkillDescs := GameExt.GetRealAddr(SecSkillDescs);
  SecSkillTexts := GameExt.GetRealAddr(SecSkillTexts);
  MonInfos      := GameExt.GetRealAddr(MonInfos);
  ArtInfos      := ppointer(ArtInfos)^;
  Spells        := GameExt.GetRealAddr(Spells);
end;

SecSkillNames: PSecSkillNames = Ptr($698BC4);
SecSkillDescs: PSecSkillDescs = Ptr($698C34);
SecSkillTexts: PSecSkillTexts = Ptr($698D88);
Spells: PSpells = Ptr($7BD2C0);
MonInfos: PMonInfos = Ptr($7D0C90);
ArtInfos: PArtInfos = Ptr($660B68);

Только указатели в студию. Вторичные навыки (имена, описания), заклинания, монстры, артефакты.


RE: Реверс игры для новичков - XEPOMAHT - 03.09.2022 15:04

(03.09.2022 06:26)wessonsm Wrote:  Расширение и перенос структуры в памяти как делается?

Зависит от того, где хранится структура и как к ней обращается код игры. Самый простой способ переноса - для динамических стуктур в динамической памяти, размер которых заранее не определён (там можно обойтись бинарным патчем, т.к. всё делает сам движок игры при создании структуры в памяти, т.е. патчится только конструктор и дезруктор). Посложнее - статические структуры в статической/динамической памяти - там придётся выделить память и перенести всю адресацию: в зависимости от сложности это может быть всего 1 адрес (когда игра всегда получает доступ с помощью базовой адресации, что очень удобно для переноса структуры) или например 2000. Ну и самое сложное в Третьих Героях - структуры, которые игра хранит в стеке: для переноса этих структур потребуется расширение выделяемой стековой памяти или перенос в динамическую память с заменой всех участков кода. В общем, рекомендую начинать с самого простого (в исходниках плагинов на новые артефакты или новых монстров), а потом уже переходит к более сложному.

(03.09.2022 06:26)wessonsm Wrote:  Я так понимаю, нужно пропатчить все места в коде игры, где идет обращение к полям этой структуры?

Да, абсолютно все.

(03.09.2022 06:26)wessonsm Wrote:  А как тогда убедиться, что нашли действительно ВСЕ такие места?

Методом научного тыка. Переносим и смотрим, работает ли вообще игра, если что-то начинает работать не так или вообще не работает - значит перенесено не всё. Когда появится опыт, то количество ошибок будет меньше.

(03.09.2022 06:26)wessonsm Wrote:  Ведь значительная часть кода не разобрана даже.

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

(03.09.2022 11:45)daemon_n Wrote:  сколько ни видел обращений к структурам, они все по ссылке идут.

Далеко не все. Оригинальных базовых адресов с десяток вряд ли наберётся. А вот если одна структура является частью тонны других структур, то её вообще очень затратно переносить (например структуру слотов армии вряд ли кто-то захочет переносить - ради добавления 8-го слота придётся переделать чуть ли не половину игрового кода, что может сделать только конченный психопат-задрот).

(03.09.2022 11:45)daemon_n Wrote:  Так понимаю, уведомить Эру надо, потому что она раньше всех плагинов на адреса цепляется?

До сих пор не понимаю, зачем это было сделано в Эре. Та же HoA совмещается с HD-модом по части перенесённых структур с помощью базовых адресов, что было унаследовано и в версии HD-мода для Эры, за счёт чего HD-мод видит новых монстров, новые артефакты и даже новые города.


RE: Реверс игры для новичков - Berserker - 03.09.2022 19:24

SecSkillNames: PSecSkillNames = Ptr($698BC4);
SecSkillDescs: PSecSkillDescs = Ptr($698C34);
SecSkillTexts: PSecSkillTexts = Ptr($698D88);
Spells: PSpells = Ptr($7BD2C0);
MonInfos: PMonInfos = Ptr($7D0C90);
ArtInfos: PArtInfos = Ptr($660B68);

Это список прямых адресов. Можешь написать надёжные, по твоему мнению, адреса указателей на данные статичные адреса.


RE: Реверс игры для новичков - daemon_n - 03.09.2022 23:20

Berserker, получается, надо переписывать вог 96-copy
Там такая муть, честно говоря. Что эти командиры, что опыт армии, что этот Erm...
Вынести бы это всё в отдельный плагин, а сама ERA основывалась бы на SoD



RE: Реверс игры для новичков - XEPOMAHT - 04.09.2022 04:47

(03.09.2022 19:24)Berserker Wrote:  SecSkillNames: PSecSkillNames = Ptr($698BC4);

mov dword [4E6C00h], Таблица_имён_навыков
mov byte [4E6C4Bh], Количество_навыков

(03.09.2022 19:24)Berserker Wrote:  SecSkillDescs: PSecSkillDescs = Ptr($698C34);

mov dword [4E6C10h], Таблица_описаний_навыков

(03.09.2022 19:24)Berserker Wrote:  SecSkillTexts: PSecSkillTexts = Ptr($698D88);

mov dword [4E6C1Ah], Таблица_текстов_навыков

(03.09.2022 19:24)Berserker Wrote:  Spells: PSpells = Ptr($7BD2C0);

4. Структура заклинаний:

* Базовый адрес = [687FA8h]
* Количество заклинаний = байт [402902h] (c чит-кода на все заклинания, лимит 128, всё равно больше никто никогда не сделает)

(03.09.2022 19:24)Berserker Wrote:  MonInfos: PMonInfos = Ptr($7D0C90);

1. Структура монстров:

* Базовый адрес = [6747B0h]
* Количество монстров в игре = [5C8047h]

(03.09.2022 19:24)Berserker Wrote:  ArtInfos: PArtInfos = Ptr($660B68);

3. Структура артефактов:

* Базовый адрес = [660B68h]
* Количество артефактов = [44CCA8h]/4-2 (из загрузчика artraits.txt, так как количество артефактов игра никуда не пишет и нигде не проверяет)

(03.09.2022 19:24)Berserker Wrote:  Это список прямых адресов. Можешь написать надёжные, по твоему мнению, адреса указателей на данные статичные адреса.

Вполне подходят из загрузчиков текстовиков, т.к. их код вряд ли кто-то станет менят.

(03.09.2022 23:20)daemon_n Wrote:  
Berserker, получается, надо переписывать вог 96-copy
Там такая муть, честно говоря. Что эти командиры, что опыт армии, что этот Erm...
Вынести бы это всё в отдельный плагин, а сама ERA основывалась бы на SoD

В ERA+ в процессе. Но чистого СоДа не будет никогда, т.к. жизненно необходимый минимум воговского кода всё равно останется. Да, практически весь игровой воговский код, содержащий наибольшее количество косяков и костылей, скоро будет поглощён Тифоном, но непосредственно сам воговский движок, дополняющий содовский, останется на месте, т.к. он реализован вполне приемлимо (правда, он работает медленно по сравнению с СоДовским, но на современных ПК это особо не заметно, т.к. я постепенно убираю самую корявую и медлительную часть ВоГа - игровой воговский код, который на чистом ассемблере работает в несколько раз быстрее, что позитивно сказывается на скорости игры на моём старом ПК).


RE: Реверс игры для новичков - Berserker - 04.09.2022 07:10

XEPOMAHT, спасибо, применю в следующей обновлении.


RE: Реверс игры для новичков - Berserker - 04.09.2022 08:07

https://dropmefiles.com/O5OPm

DLL с адресами от Хероманта. Проверил базовый запуск игры и вступление в бой.


RE: Реверс игры для новичков - wessonsm - 04.09.2022 08:56

(04.09.2022 08:07)Berserker Wrote:  https://dropmefiles.com/O5OPm

DLL с адресами от Хероманта. Проверил базовый запуск игры и вступление в бой.

Не получается скачать.


RE: Реверс игры для новичков - Berserker - 04.09.2022 08:59

Куда загружать временно подскажи )


RE: Реверс игры для новичков - wessonsm - 04.09.2022 09:02

Berserker, куда угодно, хоть и на ЯД)
Всегда с dropmefiles нормально скачивалось, а сейчас при нажатии на кнопку "скачать" на полсекунды вываливается окошко "для разархивирования используйте пароль 123" и мгновенно исчезает, загрузка не начинается.
Попробовать другим браузером, что ли.

Отбой, ложная тревога ))
Оперой скачалось. Что-то не так с моим Firefox - я не могу скачать даже файл, который сам загрузил для проверки.


RE: Реверс игры для новичков - daemon_n - 04.09.2022 09:07

wessonsm, вот ты и нашёлся, робот Spiteful



RE: Реверс игры для новичков - Berserker - 04.09.2022 09:10

Да, ботяра, долго мы тебя ловили 85

https://tmpfiles.org/34561/eraupdate.exe


RE: Реверс игры для новичков - wessonsm - 04.09.2022 09:15

Спасибо ))