Запуск сервера: https://igroglaz.com/allods2/zapusk-servera-heta-v-allody-2
Как работать с Гидрой:
- качаем Гидру https://github.com/NationalSecurityAgency/ghidra/releases
- качаем Java https://www.java.com/ru/download/
- качаем JDK (сверху в меню выбираем Windows) https://www.oracle.com/java/technologies/downloads/
- качаем файл https://github.com/igroglaz/A2Serv_ghidra
- создаем новый проект
- перетаскиваем файл в Гидру
- ???
- Profit!
Примечание от Zidane:
Главные классы CWinApp и CMainWindow можно в списке классов в гидре найти. А оттуда перейти уже на идентифицированные методы типа Run, WindowProc, OnIdle и т.д. Окно Symbol Tree в помощь по размеченным классам и неймспейсам.
1) Статьи с сайта ex-lend:
- базовая инфа: встройка .dll и деньги игрока
- фикс спидхака
- работа с графикой А2
- время прохождения тайлов
- id тайлов
- отрисовка обзора
- формат .alm
- значения ID предметов
- описание предметов
- анлок логина
- фикс воровства предметов из магазина
- список предметов с ID
- перенос охапкой
2) Статьи от ZZYZX:
3) Статьи с сайта -Vampire-:
- Триггеры — самое подробное руководство о том, как пользоваться скриптами редактора карт
- Делаем свой мод — руководство по построению собственных модов для Аллодов 2 (по опыту проекта Аллоды 2,5)
- Квесты и магазины — как правильно настраивать квесты и магазины, чтобы избежать глюков
4) Новые разборы
- Разбор формата заклинаний от ikacnep
Ссылки:
https://github.com/igroglaz/a2mgr — клиент
https://github.com/igroglaz/srvmgr — сервер
https://github.com/igroglaz/redhat — хэт
https://github.com/igroglaz/a2mgr_rom2me — редактор
https://github.com/igroglaz/A2Serv_ghidra — разбор исходников А2 в Ghidra
https://github.com/jewalky/UnityAllods — Аллоды 2 на Unity — порт с фокусом на сетевой игре
https://github.com/madwareru/orom-first-encounter — Open Rage Of Mage — порт с фокусом на кооп сингл кампании
https://github.com/igroglaz/rom2utils — утилиты для Аллодов 2
https://github.com/igroglaz/rom2maps — коллекция карт
https://github.com/serg-bloim/a2env/releases/tag/a2win7_v1 — общая среда разработки (хэт+сервер+исходники+IDE)
https://github.com/WarBeginner/a2-tools — инструмент работы с ресурсами А2
http://igroglaz.com/allods2/zapusk-servera-heta-v-allody-2 — гайд, как запустить хэт
FAQ:
Гидра показывает неверный дизасм. Например, по адресу 531b72 показывает какой-то левак, хотя там должна быть скорость
Так бывает, когда Гидра неверно декомпилирует код, размечая фунцию (которой нет) посередине другой функции. В частности, 531b72
— это Humanoid::VMethod18
. Лечение: перейти в начале нужной фунции (5315ec — это начало Humanoid::VMethod18
), и там дойти в Listing до первого места, где встречается вопросительный знак и это место декомпилить. Таким образом эту функцию можно «продолжить» в нужном месте. Чтобы это не возникало, попробуйте отключить в гидре в Analysis -> Auto-Analyze
: Function Start Identification и Aggressive Instruction Finder.
Адреса в патчах не совпадают с адресами в Гидре. Например, адрес в патче 00161AEE: 03 64
. Но в Гидре в этом месте (561AEE
— учитывая отступ +0x400000) — нету нужного значения.
Да, смещение от начала бинарного файла — 0x400000. Но также еще есть смещение от «секции»*. Поэтому там не точно +0x400000 — в Гидре 161AEE
находится по адресу 5626ee
. Чтобы найти смещения: Window
-> Memory Map
.
*Каждая программа, загружаемая в память, состоит из нескольких сегментов (или секций). В бинарном файле указано смещение каждого сегмента от начала файла и его адрес в памяти. Когда Гидра загружает файл, она отображает адреса виртуальной памяти (VA) для каждого сегмента, а не просто смещение от начала файла.
з.ы.
Патчи — зло. Надо их все переписать в инъкции (когда-нибудь).
Награды в таверне vs значения дропа вещей
Чтоб выдать предмет в награду, трактир генерит виртуальную «полку лавки» на сто предметов с ценой
от X
до X*2
,
где X — это награда за задание (найдено ikacnep)
При чем отдельно магические и обычные предметы.
Для немагических предметов 565566
local_48.MaxCost = quest_reward << 1;
local_48.MinCost = quest_reward;
local_48.MaxCount = 100;
local_48.MaxSamCount = 1;
local_48.flags = 0x1b7fffff;
ShopAssortiment::GenerateAssortiment(&local_c4,&local_48);local_48.flags = 0x1b7fffff;
Для магических 5656ca
local_48.MaxCost = (quest_reward * 3) / 2;
local_48.MinCost = (int)(quest_reward * 3 + (quest_reward * 3 >> 0x1f & 3U)) >> 2;
local_48.flags = 0x2bffffff;
ShopAssortiment::GenerateAssortiment(&local_c4,&local_48
);
Поэтому можно получить при награде 10.000 меч, который продастся в магазин за 10.000, а можно — который продастся за 20.000.
Далее теория (не проверено). Чтобы монстр мог дропнуть меч за 10.000, надо ставить treasureItem 20к.
Как сделать, чтобы магазин быстро обновлял свитки?
Как распаковать ресурсы утилкой res.exe?
Создаем папку C:\a2 , кладем туда res.exe и, например, SCENARIO.RES . Создаем там папку world. Запускаем команду
res x c:\a2\SCENARIO SCENARIO C:\a2\world
Как открыть сетевые карты из Аллодов 1?
Переименовать .all -> .alm и открыть в редакторе 🙂
Как портировать карты кампании Аллодов 1-2 в мультиплеер?
Как в редакторе положить в моба определенные вещи? (отвечает zhuxor)
TreasureItemMask работает точно так же как и настройки полок магазинов, там функция одна и та же на выборку предметов. При дропе сначала подбирается 100 предметов по маске, потом из них один рандомный.
С помощью редактора полок удобно смотреть в игре, какие предметы будут дропаться с мобов при определенных подборках флагов (галочек) и цен.
Вот на скрине настройка полок =:
<treasureItem>40</treasureItem> - это шанс дропа :)
<treasureItemMin>308700</treasureItemMin> - мин стоимость
<treasureItemMax>1353434</treasureItemMax> - макс.стоимость
<treasureItemMask>566755327</treasureItemMask> - маска (ниже в таблице расшифровка)
Это огр 5. Заходим в лавку — там 100 шмоток на полке которые могут упасть. В редакторе можно и 1000 предметов на полке выставить (больше, вроде, нельзя), но для дропа подбирается именно 100 предметов.
Таблица значений TreasureItemMask (спасибо ex-lend за разбор, Kobik за верификацию и разбор, ZZYZX за наводку):
материалы: 0x00000001: железо 0x00000002: бронза 0x00000004: сталь 0x00000008: серебро 0x00000010: золото 0x00000020: мифрил 0x00000040: адамант 0x00000080: метеорит 0x00000100: дерево 0x00000200: вол. дерево 0x00000400: кожа 0x00000800: толcт.кожа 0x00001000: драк.кожа 0x00002000: кристалл 0x00004000: none (никакие) |
типы предметов: 0x00400000: оружие 0x00800000: щиты 0x01000000: броня 0x02000000: одежда (для магов) 0x04000000: остальное (other) 0x08000000: посохи |
allowed extra: 0x10000000: common 0x20000000: magic виды предметов: 0x00008000: common 0x00010000: uncommon 0x00020000: rare 0x00040000: very rare 0x00080000: elven 0x00100000: bad 0x00200000: good
|
Как пользоваться калькулятором Windows 10 для выставления нужных вещей (спасибо Кобику):
Примеры масок:
557 842 431 — вся броня и одежда (не включает оружие, посохи, щиты и банки-свитки-книги).
Далее, есть парсер treasureItemMask от zhuxor:
#include #include #include #include enum ItemAssortmentFlags : uint32_t { Material_Iron = 0x1, Material_Bronze = 0x2, Material_Steel = 0x4, Material_Silver = 0x8, Material_Gold = 0x10, Material_Mithrill = 0x20, Material_Adamantium = 0x40, Material_Meteoric = 0x80, Material_Wood = 0x100, Material_MagicWood = 0x200, Material_Leather = 0x400, Material_HardLeather = 0x800, Material_DragonLeather = 0x1000, Material_Crystal = 0x2000, Material_None = 0x4000, // all Material_AllOf = 0x7FFF, Material_NeitherOf = ~Material_AllOf, Shape_Common = 0x8000, Shape_Uncommon = 0x10000, Shape_Rare = 0x20000, Shape_VeryRare = 0x40000, Shape_Elven = 0x80000, Shape_Bad = 0x100000, Shape_Good = 0x200000, // all Shape_AllOf = Shape_Common | Shape_Uncommon | Shape_Rare | Shape_VeryRare | Shape_Elven | Shape_Bad | Shape_Good, Shape_NeitherOf = ~Shape_AllOf, Type_Weapons = 0x400000, Type_Shields = 0x800000, Type_Armors = 0x1000000, Type_Clothes = 0x2000000, Type_Other = 0x4000000, Type_Staffs = 0x8000000, // all Type_AllOf = Type_Weapons | Type_Shields | Type_Armors | Type_Clothes | Type_Other | Type_Staffs, Type_NeitherOf = ~Type_AllOf, Extra_Common = 0x10000000, Extra_Magic = 0x20000000, // all Extra_AllOf = Extra_Common | Extra_Magic, Extra_NeitherOf = ~Extra_AllOf, Unknown_0x40000000 = 0x40000000, Unknown_0x80000000 = 0x80000000, }; void print_assortment_mask(uint32_t mask) { std::vector materials; if ((mask & Material_AllOf) == Material_AllOf) { materials.push_back("- all of -"); } else if (!(mask & Material_AllOf)) { materials.push_back("- neither of -"); } else { if (mask & Material_Iron) materials.push_back("Iron"); if (mask & Material_Bronze) materials.push_back("Bronze"); if (mask & Material_Steel) materials.push_back("Steel"); if (mask & Material_Silver) materials.push_back("Silver"); if (mask & Material_Gold) materials.push_back("Gold"); if (mask & Material_Mithrill) materials.push_back("Mithrill"); if (mask & Material_Adamantium) materials.push_back("Adamantium"); if (mask & Material_Meteoric) materials.push_back("Meteoric"); if (mask & Material_Wood) materials.push_back("Wood"); if (mask & Material_MagicWood) materials.push_back("MagicWood"); if (mask & Material_Leather) materials.push_back("Leather"); if (mask & Material_HardLeather) materials.push_back("HardLeather"); if (mask & Material_DragonLeather) materials.push_back("DragonLeather"); if (mask & Material_Crystal) materials.push_back("Crystal"); if (mask & Material_None) materials.push_back("None"); } std::vector shapes; if ((mask & Shape_AllOf) == Shape_AllOf) { materials.push_back("All"); } else if (!(mask & Shape_AllOf)) { materials.push_back("- neither of -"); } else { if (mask & Shape_Common) shapes.push_back("Common"); if (mask & Shape_Uncommon) shapes.push_back("Uncommon"); if (mask & Shape_Rare) shapes.push_back("Rare"); if (mask & Shape_VeryRare) shapes.push_back("VeryRare"); if (mask & Shape_Elven) shapes.push_back("Elven"); if (mask & Shape_Bad) shapes.push_back("Bad"); if (mask & Shape_Good) shapes.push_back("Good"); } std::vector types; if ((mask & Type_AllOf) == Type_AllOf) { types.push_back("- all of -"); } else if (!(mask & Type_AllOf)) { types.push_back("- neither of -"); } else { if (mask & Type_Weapons) types.push_back("Weapons"); if (mask & Type_Shields) types.push_back("Shields"); if (mask & Type_Armors) types.push_back("Armors"); if (mask & Type_Clothes) types.push_back("Clothes"); if (mask & Type_Other) types.push_back("Other"); if (mask & Type_Staffs) types.push_back("Staffs"); } std::vector extras; if (!(mask & Extra_AllOf)) { extras.push_back("- neither of -"); } else { if (mask & Extra_Magic) extras.push_back("Magic"); if (mask & Extra_Common) extras.push_back("Common"); } std::cout << "print_assortment_mask(" << std::hex << mask << "):\n"; std::cout << " Materials:\n"; for (std::string material : materials) std::cout << " " << material << '\n'; std::cout << " Shapes:\n"; for (std::string shape : shapes) std::cout << " " << shape << '\n'; std::cout << " Types:\n"; for (std::string type : types) std::cout << " " << type << '\n'; std::cout << " Extra:\n"; for (std::string extra : extras) std::cout << " " << extra << '\n'; } int main() { // 566755327 from data.xml print_assortment_mask(566755327); // ogre_treasure_mask == 566755327 uint32_t ogre_treasure_mask = Material_AllOf | Shape_Common | Shape_Uncommon | Shape_Rare | Shape_VeryRare | Type_Weapons | Type_Shields | Type_Armors | Extra_Magic; return 0; }
меняем значения print_assortment_mask(566755327);
и наслаждаемся 🙂
Пишите комменты!
How to auto-attack flying monsters with blizzard or fireball (by ex-lend)
Когда-то я нашел это место, но не стал распространять на сервер, т.к. данное исправление имеет смысл только в сингле, Вы догадываетесь почему.
allods2.exe 0017498B: 7E 90 0017498C: 2E 90 00174B84: 7E 90 00174B85: 2E 90 00174D14: 7E 90 00174D15: 2E 90 00174EA4: 7E 90 00174EA5: 2B 90
Убрано несколько условных переходов, если внимательно посмотреть в дизассемблере, можно выяснить какой за что отвечает. Хотя не представляю, зачем это… 😉
ex-lend: размер мешка определяется количеством знаков в суммарной стоимости содержимого:
Поправить это можно в a2server.exe:0051AF26
Примечание по хакам от Кобика:
quest_kill_N_mobs.xck
Patch for increase maxN in quest «kill N mobs»
В сервере см. функцию sub_562148. Выбор mахN на участке от 562996 до 562А03.
Исходно логика такая:
Если на карте 1 моб, то квест дают на 1-4шт
Если на карте 2-3 моба, то квест на 2-6шт
Если на карте 4-7 мобов, то квест на 2-7шт
Если на карте 8+ мобов, то квест на 2-10шт
Патч задевает только последнюю ветку:
Если на карте 8+ мобов, то кв на 5-25 шт
Для любителей убить много белок.
Адрес в a2serv1.exe | Было | Стало | ||
hex | команда | hex | команда | |
161DF0 | 6A | push 8 | 6A | push 14h |
161DF1 | 08 | 14 | ||
161DFA | 83 | add eax, 2 | 83 | add eax, 5 |
161DFB | C0 | C0 | ||
161DFC | 02 | 05 |
group_kill_reward.xck — Изменение награды за группу
Patch for change reward for group kill quests
Патч для изменения награды за убийство группы мобов. Есть еще такие же конструкции для квестов на перехват группы и еще какой-то квест (вероятно вырезание города?). Их пока не трогаем — во-первых редко встречаются, во-вторых надо для начала потестировать это, а дальше видно будет.
Было: Награда = 5 * max (mob.exp)
После патча: Награда = 2 * sum (mob.exp)
Адрес в a2serv1.exe | Было | Стало | ||
hex | команда | hex | команда | |
162A05 | 3B | cmp ecx, [ebp+var_460] | 8B | mov eax, [ebp+var_460] |
162A06 | 8D | 85 | ||
162A07 | A0 | A0 | ||
162A08 | FB | FB | ||
162A09 | FF | FF | ||
162A0A | FF | FF | ||
162A0B | 7E | jle short loc_56361C | 03 | add eax, ecx |
162A0C | 0F | C1 | ||
162A0D | 8B | mov edx, [ebp+var_434] | 90 | nop |
162A0E | 95 | 90 | nop | |
162A0F | CC | 90 | nop | |
162A10 | FB | 90 | nop | |
162A11 | FF | 90 | nop | |
162A12 | FF | 90 | nop | |
162A13 | 8B | mov eax, [edx+1Ch] | 90 | nop |
162A14 | 42 | 90 | nop | |
162A15 | 1C | 90 | nop | |
162B85 | 05 | множитель | 02 | множитель |
quest_availability.xck — Фикс на доступность квестов
Расширение диапазона доступности квестов
MobPower = защита + 25(если летает) + 50 (если колдует)
PlayerPower = максимальный навык * 3
До фикса доступны квесты:
PlayerPower — 50 < MobPower < PlayerPower + 75
После фикса:
PlayerPower — 100 < MobPower < PlayerPower + 75
Адрес в a2serv1.exe | К чему относится | Было hex |
Стало hex |
161AD6 | Убить N мобов | 32 | 64 |
1621DD | Убить моба | 32 | 64 |
1626E4 | Убить группу | 32 | 64 |
162F05 | Перехват моба | 32 | 64 |
163406 | Перехват группы | 32 | 64 |