Разработка

Запуск сервера: https://igroglaz.com/allods2/zapusk-servera-heta-v-allody-2

Как работать с Гидрой:

  1. качаем Гидру https://github.com/NationalSecurityAgency/ghidra/releases
  2. качаем Java https://www.java.com/ru/download/
  3. качаем JDK (сверху в меню выбираем Windows)  https://www.oracle.com/java/technologies/downloads/
  4. качаем файл https://github.com/igroglaz/A2Serv_ghidra
  5. создаем новый проект
  6. перетаскиваем файл в Гидру
  7. ???
  8. Profit!

Примечание от Zidane:
Главные классы CWinApp и CMainWindow можно в списке классов в гидре найти. А оттуда перейти уже на идентифицированные методы типа Run, WindowProc, OnIdle и т.д. Окно Symbol Tree в помощь по размеченным классам и неймспейсам.


1) Статьи с сайта ex-lend:

2) Статьи от ZZYZX:

3) Статьи с сайта -Vampire-:

  • Триггеры — самое подробное руководство о том, как пользоваться скриптами редактора карт
  • Делаем свой мод — руководство по построению собственных модов для Аллодов 2 (по опыту проекта Аллоды 2,5)
  • Квесты и магазины — как правильно настраивать квесты и магазины, чтобы избежать глюков

4) Новые разборы

Ссылки:

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

 

Добавить комментарий

🇬🇧 Attention! Comments with URLs/email are not allowed.
🇷🇺 Комментарии со ссылками/email удаляются автоматически.