В общем, решил я исправить переполнение Опыта Существ в WoG на уровне плагина, так как предыдущее решение на erm скрипте было громоздким, требовало наличие скрипта да и просто не нравилось мне, каким бы гениальном оно ни было.
К делу:
относительно тривиальные случаи решились хуками (постить даже нет смысла), а где-то даже и
Code:
// © daemon_n
// исправление расчёта опыта при перемещении отряда существ из слота в слот
char *srcExpPatch = "8DB0 50058600 8B06 F76D 20 F77E 04 8906 8B4D 20 894E 04"; // слот-источник
/*
lea esi, [WOG__CrExpo__Table + eax] ; esi = &Table[src]
mov eax, [esi] ; eax = Expo
imul dword ptr [ebp+source_mon_number] ; edx:eax = Expo * src (64-bit)
idiv dword ptr [esi+4] ; eax = (Expo*src)/Num
mov [esi], eax ; Table[src].Expo = eax
mov ecx, [ebp+source_mon_number]
mov [esi+4], ecx ; Table[src].Num = src
*/
_PI->WriteHexPatch(0x0719993, srcExpPatch); // patch experience
_PI->WriteCodePatch(0x07199A9, "%n", 28); // nop extra code
char *dstExpPatch = "8DB1 50058600 8B06 F76D 24 F77E 04 8906 8B4D 24 894E 04"; // слот-назначение
/*
lea esi, [WOG__CrExpo__Table + ecx] ; esi = &Table[dst]
mov eax, [esi] ; eax = Expo
imul dword ptr [ebp+dst_mon_number] ; edx:eax = Expo * dst (64-bit)
idiv dword ptr [esi+4] ; eax = (Expo*dst)/Num
mov [esi], eax ; Table[dst].Expo = eax
mov ecx, [ebp+dst_mon_number]
mov [esi+4], ecx ; Table[dst].Num = dst
*/
_PI->WriteHexPatch(0x0719A55, dstExpPatch); // patch experience
_PI->WriteCodePatch(0x0719A6B, "%n", 28); // nop extra code
Осталось только разобраться с тем, как починить посещение героем гарнизон города.
Как оказалось, разработчики WoG придумали интересное решение:
в случае успеха объединения армии создаётся ещё 4 армии (одна пара -- для опыта, другая -- для артефактов);
в армию копируются корректные типы существ "сверху" и "снизу", но вместо количеств устанавливаются значения количеств опыта и артефактов;
затем просто дважды вызывается функция "соединить армии", которая корректно корректно складывает значения по соответствующим слотам в нужном порядке;
решение я оценил (хотя понял и не сразу) -- такого рода хаки и я люблю делать;
к сожалению, формула подсчёта опыта была написано некорректно (обнаружил реальный глитч, который позволяет через разделение армий абузить опыт бесконечно);
ну и сама проблема int32 overflow присутствует (даже если и происходит неявно: при объединении "армий");
Не знаю, что меня укусило, но я очень не хотел переписывать логику из erm-фикса (там я с нуля написал полную логику на объединение армий, но только для опыта, что заняло ооочень много кода ) C++, так что тупо пялил в IDA, чтобы придумать как можно более короткое решение (да-да, сэкономил бы ооочень много времени, если бы просто повторил алгоритм...)
в итоге пришёл к, как мне кажется, отличному решению:
- при итерации отрядов города и героя сохранять изначальный опыт по индексам в свой массив;
- вместо опыта в псевдоармии передавать смещённый на индекс отряда байт;
- игра сама считает битсеты при передаче такой армии, какие именно отряды объединились в итоговый слот;
- по битсету отряда из 14 слотов взять итоговый опыт и отряд;
Code:
struct ArmySlotExperience
{
int number;
int experience;
} armySlots[14];
_LHF_(WoG_InTowmArmyMerge_GuardIterator)
{
const int slotId = c->eax;
// сохраняем значения опыта и количества существ для армии защитника
armySlots[slotId].experience = IntAt(c->ebp - 0x10C);
armySlots[slotId].number = reinterpret_cast<_Army_ *>(0x2846BF0)->count[slotId];
// заменить значения опыта существа на уникальный индекс для сложения в битсет
c->ecx = 1 << slotId;
return EXEC_DEFAULT;
}
_LHF_(WoG_InTowmArmyMerge_VisitorIterator)
{
const int slotId = c->ecx + 7;
// сохраняем значения опыта и количества существ для армии визитёра
armySlots[slotId].experience = IntAt(c->ebp - 0x10C);
armySlots[slotId].number = reinterpret_cast<_Army_ *>(0x2846C28)->count[slotId - 7];
// заменить значения опыта существа на уникальный индекс для сложения в битсет
c->edx = 1 << slotId;
return EXEC_DEFAULT;
}
_LHF_(WoG_InTowmArmyMerge_ExperienceCreatorIterator)
{
INT64 resultExp = 0;
INT totalCreatures = IntAt(c->ebp - 0x14);
_Army_ &expArmy = *reinterpret_cast<_Army_ *>(c->ebp - 0xC8);
const int slotId = IntAt(c->ebp - 0x18);
const int slotsMerged = expArmy.count[slotId];
for (size_t i = 0; i < 14; i++)
{
if (const int slotBit = slotsMerged & (1 << i))
{
// сложение общего опыта, основываясь на начальных значениях
resultExp += static_cast<INT64>(armySlots[i].experience) * armySlots[i].number;
}
}
// установка итогового опыта делением на итоговое количество существ
c->eax = totalCreatures ? static_cast<int>(resultExp / totalCreatures) : 0;
c->return_address = 0x0759A2E;
return NO_EXEC_DEFAULT;
}
А вот файл с кодом всех исправлений, связанных с опытом, если кому надо.