Files
sfall/sfall/HookScripts.cpp
T
NovaRain fc3b7180af Backported the code of COMBATDAMAGE hook from 4.x
Moved instadeath crit fix from COMBATDAMAGE hook to BugFixes.cpp.
Completed the compute_attack struct.
Updated version number.
2019-07-15 09:59:58 +08:00

1624 lines
40 KiB
C++

/*
* sfall
* Copyright (C) 2008, 2009, 2010 The sfall team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "main.h"
#include <string>
#include <vector>
#include "Define.h"
#include "FalloutEngine.h"
#include "HookScripts.h"
#include "Inventory.h"
#include "LoadGameHook.h"
#include "Logging.h"
#include "PartyControl.h"
#include "ScriptExtender.h"
static const int maxArgs = 16;
static const int maxRets = 8;
static const int maxDepth = 8;
static const int numHooks = HOOK_COUNT;
struct sHookScript {
sScriptProgram prog;
int callback; // proc number in script's proc table
bool isGlobalScript; // false for hs_* scripts, true for gl* scripts
};
static std::vector<sHookScript> hooks[numHooks];
struct {
DWORD hookID;
DWORD argCount;
DWORD cArg;
DWORD cRet;
DWORD cRetTmp;
DWORD oldArgs[maxArgs];
DWORD oldRets[maxRets];
} savedArgs[maxDepth];
static DWORD callDepth;
static DWORD currentRunHook = -1;
DWORD InitingHookScripts;
static DWORD args[maxArgs]; // current hook arguments
static DWORD rets[maxRets]; // current hook return values
static DWORD argCount;
static DWORD cArg; // how many arguments were taken by current hook script
static DWORD cRet; // how many return values were set by current hook script
static DWORD cRetTmp; // how many return values were set by specific hook script (when using register_hook)
#define hookbegin(a) pushadc __asm call BeginHook popadc __asm mov argCount, a
#define hookend pushadc __asm call EndHook popadc
#define HookBegin pushadc __asm call BeginHook popadc
//#define HookEnd pushadc __asm call EndHook popadc
static void _stdcall BeginHook() {
if (callDepth && callDepth <= maxDepth) {
// save all values of the current hook if another hook was called during the execution of the current hook
int cDepth = callDepth - 1;
savedArgs[cDepth].hookID = currentRunHook;
savedArgs[cDepth].argCount = argCount; // number of arguments of the current hook
savedArgs[cDepth].cArg = cArg; // current count of taken arguments
savedArgs[cDepth].cRet = cRet; // number of return values for the current hook
savedArgs[cDepth].cRetTmp = cRetTmp;
memcpy(&savedArgs[cDepth].oldArgs, args, argCount * sizeof(DWORD)); // values of the arguments
if (cRet) memcpy(&savedArgs[cDepth].oldRets, rets, cRet * sizeof(DWORD)); // return values
// for debugging
/*dlog_f("\nSaved cArgs/cRets: %d / %d(%d)\n", DL_HOOK, savedArgs[cDepth].argCount, savedArgs[cDepth].cRet, cRetTmp);
for (unsigned int i = 0; i < maxArgs; i++) {
dlog_f("Saved Args/Rets: %d / %d\n", DL_HOOK, savedArgs[cDepth].oldArgs[i], ((i < maxRets) ? savedArgs[cDepth].oldRets[i] : -1));
}*/
}
callDepth++;
#ifndef NDEBUG
dlog_f("Begin running hook, current depth: %d, current executable hook: %d\n", DL_HOOK, callDepth, currentRunHook);
#endif
}
static void _stdcall EndHook() {
#ifndef NDEBUG
dlog_f("End running hook %d, current depth: %d\n", DL_HOOK, currentRunHook, callDepth);
#endif
callDepth--;
if (callDepth) {
if (callDepth <= maxDepth) {
// restore all saved values of the previous hook
int cDepth = callDepth - 1;
currentRunHook = savedArgs[cDepth].hookID;
argCount = savedArgs[cDepth].argCount;
cArg = savedArgs[cDepth].cArg;
cRet = savedArgs[cDepth].cRet;
cRetTmp = savedArgs[cDepth].cRetTmp; // also restore current count of the number of return values
memcpy(args, &savedArgs[cDepth].oldArgs, argCount * sizeof(DWORD));
if (cRet) memcpy(rets, &savedArgs[cDepth].oldRets, cRet * sizeof(DWORD));
// for debugging
/*dlog_f("Restored cArgs/cRets: %d / %d(%d)\n", DL_HOOK, argCount, cRet, cRetTmp);
for (unsigned int i = 0; i < maxArgs; i++) {
dlog_f("Restored Args/Rets: %d / %d\n", args[i], ((i < maxRets) ? rets[i] : -1));
}*/
}
} else {
currentRunHook = -1;
}
}
static void _stdcall RunSpecificHookScript(sHookScript *hook) {
cArg = 0;
cRetTmp = 0;
if (hook->callback != -1) {
RunScriptProcByNum(hook->prog.ptr, hook->callback);
} else {
RunScriptProc(&hook->prog, start);
}
}
static void _stdcall RunHookScript(DWORD hook) {
cRet = 0;
if (hooks[hook].size()) {
if (callDepth > 8) {
DebugPrintf("\n[SFALL] The hook ID: %d cannot be executed.", hook);
dlog_f("The hook %d cannot be executed due to exceeded depth limit\n", DL_MAIN, hook);
return;
}
currentRunHook = hook;
dlog_f("Running hook %d, which has %0d entries attached, depth: %d\n", DL_HOOK, hook, hooks[hook].size(), callDepth);
for (int i = hooks[hook].size() - 1; i >= 0; i--) {
RunSpecificHookScript(&hooks[hook][i]);
// for debugging
/*dlog_f("> Hook: %d, script entry: %d done\n", DL_HOOK, hook, i);
dlog_f("> Check cArg/cRet: %d / %d(%d)\n", DL_HOOK, cArg, cRet, cRetTmp);
for (unsigned int i = 0; i < maxArgs; i++) {
dlog_f("> Check Args/Rets: %d / %d\n", DL_HOOK, args[i], ((i < maxRets) ? rets[i] : -1));
}*/
}
} else {
cArg = 0;
#ifndef NDEBUG
dlog_f(">>> Try running hook ID: %d\n", DL_HOOK, hook);
#endif
}
}
static void __declspec(naked) ToHitHook() {
__asm {
hookbegin(7);
mov args[4], eax; // attacker
mov args[8], ebx; // target
mov args[12], ecx; // body part
mov args[16], edx; // source tile
mov eax, [esp+4]; // attack type
mov args[20], eax;
mov eax, [esp+8]; // is ranged
mov args[24], eax;
mov eax, args[4];
push [esp+8];
push [esp+8];
call determine_to_hit_func_;
mov args[0], eax;
pushad;
push HOOK_TOHIT;
call RunHookScript;
popad;
cmp cRet, 1;
jl end;
mov eax, rets[0];
end:
hookend;
retn 8;
}
}
static const DWORD AfterHitRollAddr = 0x423898;
static void __declspec(naked) AfterHitRollHook() {
__asm {
hookbegin(5);
mov args[0], eax; //was it a hit?
mov ebx, [esi];
mov args[4], ebx; //Attacker
mov ebx, [esi+0x20];
mov args[8], ebx; //Target
mov ebx, [esi+0x28];
mov args[12], ebx; //bodypart
mov ebx, [esp+0x18];
mov args[16], ebx; //hit chance
pushad;
push HOOK_AFTERHITROLL;
call RunHookScript;
popad;
cmp cRet, 1;
jl end;
mov eax, rets[0];
cmp cRet, 2;
jl end;
mov ebx, rets[4];
mov [esi+0x28], ebx;
cmp cRet, 3;
jl end;
mov ebx, rets[8];
mov [esi+0x20], ebx;
end:
mov ebx, eax;
hookend;
cmp ebx, ROLL_FAILURE;
jmp AfterHitRollAddr;
}
}
static void __declspec(naked) CalcApCostHook() {
__asm {
hookbegin(4);
mov args[0], eax;
mov args[4], edx;
mov args[8], ebx;
call item_w_mp_cost_;
mov args[12], eax;
pushad;
push HOOK_CALCAPCOST;
call RunHookScript;
popad;
cmp cRet, 1;
jl end;
mov eax, rets[0];
end:
hookend;
retn;
}
}
// this is for using non-weapon items, always 2 AP in vanilla
static void __declspec(naked) CalcApCostHook2() {
__asm {
hookbegin(4);
mov args[0], ecx; // critter
mov args[4], edx; // attack type (to determine hand)
mov args[8], ebx;
mov eax, 2; // vanilla value
mov args[12], eax;
pushad;
push HOOK_CALCAPCOST;
call RunHookScript;
popad;
cmp cRet, 1;
jl end;
mov eax, rets[0];
end:
hookend;
retn;
}
}
static void __declspec(naked) CalcDeathAnimHook() {
__asm {
hookbegin(4);
mov args[24], ebx;
test ebx, ebx;
jz noweap
mov ebx, [ebx+0x64];
and ebx, 0xfff;
jmp weapend;
noweap:
dec ebx;
weapend:
mov args[0], ebx;
mov ebx, args[24];
mov args[4], eax;
mov args[8], edx;
mov args[12], ecx;
mov args[20], 0;
pushad;
push HOOK_DEATHANIM1;
call RunHookScript;
cmp cRet, 1;
jl end1;
sub esp, 4;
mov edx, rets[0];
mov args[0], edx;
mov eax, esp;
call obj_pid_new_;
add esp, 4;
cmp eax, 0xffffffff;
jz end1;
mov eax, [esp-4];
mov args[20], 1;
mov args[24], eax;
end1:
popad;
mov eax, [esp+8];
mov ebx, [esp+4];
push eax;
push ebx;
mov eax, args[4];
mov ebx, args[24];
call pick_death_;
mov args[16], eax;
mov eax, args[16];
mov argCount, 5;
pushad;
push HOOK_DEATHANIM2;
call RunHookScript;
popad;
cmp cRet, 1;
jl skip2;
mov eax, rets[0];
mov args[16], eax;
skip2:
mov eax, args[16];
push eax;
mov eax, args[20];
test eax, eax;
jz aend;
mov eax, args[24];
xor edx, edx;
call obj_erase_object_;
aend:
pop eax;
hookend;
retn 8;
}
}
static void __declspec(naked) CalcDeathAnimHook2() {
__asm {
hookbegin(5);
call check_death_; // call original function
mov args[0], -1; // weaponPid, -1
mov ebx, [esp+60];
mov args[4], ebx; // attacker
mov args[8], esi; // target
mov ebx, [esp+12]
mov args[12], ebx; // dmgAmount
mov args[16], eax; // calculated animID
pushad;
push HOOK_DEATHANIM2;
call RunHookScript;
popad;
cmp cRet, 1;
jl skip;
mov eax, rets[0];
mov args[16], eax;
skip:
mov eax, args[16];
hookend;
retn;
}
}
// 4.x backport
static void __fastcall ComputeDamageHook_Script(TComputeAttack &ctd, DWORD rounds, DWORD multiplier) {
BeginHook();
argCount = 12;
args[0] = (DWORD)ctd.target; // Target
args[1] = (DWORD)ctd.attacker; // Attacker
args[2] = ctd.targetDamage; // amountTarget
args[3] = ctd.attackerDamage; // amountSource
args[4] = ctd.targetFlags; // flagsTarget
args[5] = ctd.attackerFlags; // flagsSource
args[6] = (DWORD)ctd.weapon;
args[7] = ctd.bodyPart;
args[8] = multiplier; // damage multiplier
args[9] = rounds; // number of rounds
args[10] = ctd.knockbackValue;
args[11] = ctd.hitMode; // attack type
RunHookScript(HOOK_COMBATDAMAGE);
if (cRet > 0) {
ctd.targetDamage = rets[0];
if (cRet > 1) {
ctd.attackerDamage = rets[1];
if (cRet > 2) {
ctd.targetFlags = rets[2]; // flagsTarget
if (cRet > 3) {
ctd.attackerFlags = rets[3]; // flagsSource
if (cRet > 4) ctd.knockbackValue = rets[4];
}
}
}
}
EndHook();
}
static void __declspec(naked) ComputeDamageHook() {
__asm {
push ecx;
push ebx; // store dmg multiplier args[8]
push edx; // store num of rounds args[9]
push eax; // store ctd
call compute_damage_;
pop ecx; // restore ctd (eax)
pop edx; // restore num of rounds
call ComputeDamageHook_Script; // stack - arg multiplier
pop ecx;
retn;
}
}
static void __declspec(naked) OnDeathHook() {
__asm {
hookbegin(1);
mov args[0], eax;
call critter_kill_;
pushad;
push HOOK_ONDEATH;
call RunHookScript;
popad;
hookend;
retn;
}
}
static void __declspec(naked) OnDeathHook2() {
__asm {
hookbegin(1);
mov args[0], esi;
call partyMemberRemove_;
pushad;
push HOOK_ONDEATH;
call RunHookScript;
popad;
hookend;
retn;
}
}
// 4.x backport
static void __fastcall FindTargetHook_Script(DWORD* target, DWORD attacker) {
BeginHook();
argCount = 5;
args[0] = attacker;
args[1] = target[0];
args[2] = target[1];
args[3] = target[2];
args[4] = target[3];
RunHookScript(HOOK_FINDTARGET);
if (cRet > 0) {
if (rets[0] != -1) target[0] = rets[0];
if (cRet > 1 && rets[1] != -1) target[1] = rets[1];
if (cRet > 2 && rets[2] != -1) target[2] = rets[2];
if (cRet > 3 && rets[3] != -1) target[3] = rets[3];
}
EndHook();
}
static void __declspec(naked) FindTargetHook() {
__asm {
push eax;
call qsort_;
pop ecx; // targets (base)
mov edx, esi; // attacker
jmp FindTargetHook_Script;
}
}
static void __declspec(naked) UseObjOnHook() {
__asm {
hookbegin(3);
mov args[0], edx; // target
mov args[4], eax; // user
mov args[8], ebx; // object
pushad;
push HOOK_USEOBJON;
call RunHookScript;
popad;
cmp cRet, 1;
jl defaulthandler;
cmp rets[0], -1;
jz defaulthandler;
mov eax, rets[0];
jmp end;
defaulthandler:
call protinst_use_item_on_;
end:
hookend;
retn;
}
}
static void __declspec(naked) UseObjOnHook_item_d_take_drug() {
__asm {
hookbegin(3);
mov args[0], eax; // target
mov args[4], eax; // user
mov args[8], edx; // object
pushad;
push HOOK_USEOBJON; // useobjon
call RunHookScript;
popad;
cmp cRet, 1;
jl defaulthandler;
cmp rets[0], -1;
jz defaulthandler;
mov eax, rets[0];
jmp end;
defaulthandler:
call item_d_take_drug_;
end:
hookend;
retn;
}
}
static void __declspec(naked) UseObjHook() {
__asm {
hookbegin(2);
mov args[0], eax; // user
mov args[4], edx; // object
pushad;
push HOOK_USEOBJ;
call RunHookScript;
popad;
cmp cRet, 1;
jl defaulthandler;
cmp rets[0], -1;
je defaulthandler;
mov eax, rets[0];
jmp end;
defaulthandler:
call protinst_use_item_;
end:
hookend;
retn;
}
}
static const DWORD RemoveObjHookRet = 0x477497;
static void __declspec(naked) RemoveObjHook() {
__asm {
mov ecx, [esp + 8]; // call addr
hookbegin(5);
mov args[0], eax; // source
mov args[4], edx; // item
mov args[8], ebx; // count
mov args[12], ecx;
xor esi, esi;
xor ecx, 0x47761D; // from item_move_func_
cmovz esi, ebp; // target
mov args[16], esi;
push edi;
push ebp;
pushad;
push HOOK_REMOVEINVENOBJ;
call RunHookScript;
popad;
hookend;
sub esp, 0x0C;
jmp RemoveObjHookRet;
}
}
// 4.x backport
static DWORD __fastcall BarterPriceHook_Script(register TGameObj* source, register TGameObj* target, DWORD callAddr) {
int computeCost = BarterComputeValue(source, target);
BeginHook();
argCount = 9;
args[0] = (DWORD)source;
args[1] = (DWORD)target;
args[2] = computeCost;
TGameObj* bTable = (TGameObj*)*ptr_btable;
args[3] = (DWORD)bTable;
args[4] = ItemCapsTotal(bTable);
args[5] = ItemTotalCost(bTable);
TGameObj* pTable = (TGameObj*)*ptr_ptable;
args[6] = (DWORD)pTable;
int pcCost = ItemTotalCost(pTable);
args[7] = pcCost;
args[8] = (DWORD)(callAddr == 0x474D51); // check offers button
RunHookScript(HOOK_BARTERPRICE);
bool isPCHook = (callAddr == 0x47551F);
int cost = isPCHook ? pcCost : computeCost;
if (cRet > 0) {
if (isPCHook) {
if (cRet > 1) cost = rets[1]; // new cost for pc
} else if ((int)rets[0] > -1) {
cost = rets[0]; // new cost for trader
}
}
EndHook();
return cost;
}
static void __declspec(naked) BarterPriceHook() {
__asm {
push edx;
push ecx;
//-------
push [esp + 8]; // address on call stack
mov ecx, eax; // source
call BarterPriceHook_Script; // edx - target
pop ecx;
pop edx;
retn;
}
}
static DWORD offersGoodsCost; // keep last cost
static void __declspec(naked) PC_BarterPriceHook() {
__asm {
push edx;
push ecx;
//-------
push [esp + 8]; // address on call stack
mov ecx, dword ptr ds:[_obj_dude]; // source
mov edx, dword ptr ds:[_target_stack]; // target
call BarterPriceHook_Script;
pop ecx;
pop edx;
mov offersGoodsCost, eax;
retn;
}
}
static const DWORD OverrideCostRet = 0x474D44;
static void __declspec(naked) OverrideCost_BarterPriceHack() {
__asm {
mov eax, offersGoodsCost;
jmp OverrideCostRet;
}
}
static void __declspec(naked) MoveCostHook() {
__asm {
hookbegin(3);
mov args[0], eax;
mov args[4], edx;
call critter_compute_ap_from_distance_;
mov args[8], eax;
pushad;
push HOOK_MOVECOST;
call RunHookScript;
popad;
cmp cRet, 1;
jl end;
mov eax, rets[0];
end:
hookend;
retn;
}
}
static const DWORD _obj_blocking_at = 0x48B84E;
static void __declspec(naked) HexMBlockingHook() {
__asm {
hookbegin(4);
mov args[0], eax;
mov args[4], edx;
mov args[8], ebx;
push next;
push ecx;
push esi;
push edi;
push ebp;
mov ecx, eax;
jmp _obj_blocking_at;
next:
mov args[12], eax;
pushad;
push HOOK_HEXMOVEBLOCKING;
call RunHookScript;
popad;
cmp cRet, 1;
jl end;
mov eax, rets[0];
end:
hookend;
retn;
}
}
static void __declspec(naked) HexABlockingHook() {
__asm {
hookbegin(4);
mov args[0], eax;
mov args[4], edx;
mov args[8], ebx;
call obj_ai_blocking_at_;
mov args[12], eax;
pushad;
push HOOK_HEXAIBLOCKING;
call RunHookScript;
popad;
cmp cRet, 1;
jl end;
mov eax, rets[0];
end:
hookend;
retn;
}
}
static void __declspec(naked) HexShootBlockingHook() {
__asm {
hookbegin(4);
mov args[0], eax;
mov args[4], edx;
mov args[8], ebx;
call obj_shoot_blocking_at_;
mov args[12], eax;
pushad;
push HOOK_HEXSHOOTBLOCKING;
call RunHookScript;
popad;
cmp cRet, 1;
jl end;
mov eax, rets[0];
end:
hookend;
retn;
}
}
static void __declspec(naked) HexSightBlockingHook() {
__asm {
hookbegin(4);
mov args[0], eax;
mov args[4], edx;
mov args[8], ebx;
call obj_sight_blocking_at_;
mov args[12], eax;
pushad;
push HOOK_HEXSIGHTBLOCKING;
call RunHookScript;
popad;
cmp cRet, 1;
jl end;
mov eax, rets[0];
end:
hookend;
retn;
}
}
static void __declspec(naked) ItemDamageHook() {
__asm {
hookbegin(6);
mov args[0], eax; //min
mov args[4], edx; //max
mov args[8], edi; //weapon
mov args[12], ecx; //critter
mov args[16], esi; //type
mov args[20], ebp; //non-zero for weapon melee attack
test edi, edi;
jnz skip;
add args[16], 8;
skip:
pushad;
push HOOK_ITEMDAMAGE;
call RunHookScript;
popad;
cmp cRet, 0;
je runrandom;
mov eax, rets[0];
cmp cRet, 1;
je end;
mov edx, rets[4];
runrandom:
call roll_random_;
end:
hookend;
retn;
}
}
// code backported from 4.x
int __fastcall AmmoCostHook_Script(DWORD hookType, TGameObj* weapon, DWORD &rounds) {
int result = 0;
BeginHook();
argCount = 4;
args[0] = (DWORD)weapon;
args[1] = rounds; // rounds in attack
args[3] = hookType;
if (hookType == 2) { // burst hook
rounds = 1; // set default multiply for check burst attack
} else {
result = ItemWComputeAmmoCost(weapon, &rounds);
if (result == -1) goto failed; // failed computed
}
args[2] = rounds; // rounds as computed by game (cost)
RunHookScript(HOOK_AMMOCOST);
if (cRet > 0) rounds = rets[0]; // override rounds
failed:
EndHook();
return result;
}
static void __declspec(naked) AmmoCostHook() {
__asm {
xor ecx, ecx; // type of hook (0)
cmp dword ptr [esp + 0x1C + 4], 46; // ANIM_fire_burst
jl skip;
cmp dword ptr [esp + 0x1C + 4], 47; // ANIM_fire_continuous
jg skip;
mov ecx, 3; // hook type burst
skip:
xchg eax, edx;
push eax; // rounds in attack
call AmmoCostHook_Script; // edx - weapon
retn;
}
}
DWORD _stdcall KeyPressHook(DWORD dxKey, bool pressed, DWORD vKey) {
if (!IsMapLoaded()) {
return 0;
}
DWORD result = 0;
BeginHook();
argCount = 3;
args[0] = (DWORD)pressed;
args[1] = dxKey;
args[2] = vKey;
RunHookScript(HOOK_KEYPRESS);
if (cRet != 0) dxKey = result = rets[0];
InventoryKeyPressedHook(dxKey, pressed, vKey);
EndHook();
return result;
}
void _stdcall MouseClickHook(DWORD button, bool pressed) {
if (!IsMapLoaded()) {
return;
}
BeginHook();
argCount = 2;
args[0] = (DWORD)pressed;
args[1] = button;
RunHookScript(HOOK_MOUSECLICK);
EndHook();
}
static void __declspec(naked) UseSkillHook() {
__asm {
hookbegin(4);
mov args[0], eax; // user
mov args[4], edx; // target
mov args[8], ebx; // skill id
mov args[12], ecx; // skill bonus
pushad;
push HOOK_USESKILL;
call RunHookScript;
popad;
cmp cRet, 1;
jl defaulthandler;
cmp rets[0], -1;
je defaulthandler;
mov eax, rets[0];
jmp end;
defaulthandler:
call skill_use_;
end:
hookend;
retn;
}
}
static void __declspec(naked) StealCheckHook() {
__asm {
hookbegin(4);
mov args[0], eax; // thief
mov args[4], edx; // target
mov args[8], ebx; // item
mov args[12], ecx; // is planting
pushad;
push HOOK_STEAL;
call RunHookScript;
popad;
cmp cRet, 1;
jl defaulthandler;
cmp rets[0], -1;
je defaulthandler;
mov eax, rets[0];
jmp end;
defaulthandler:
call skill_check_stealing_;
end:
hookend;
retn;
}
}
// 4.x backport
static long __stdcall PerceptionRangeHook_Script(int type) {
long result;
__asm {
HookBegin;
mov args[0], eax; // watcher
mov args[4], edx; // target
call is_within_perception_;
mov result, eax; // check result
}
args[2] = result;
args[3] = type;
argCount = 4;
RunHookScript(HOOK_WITHINPERCEPTION);
if (cRet > 0) result = rets[0];
EndHook();
return result;
}
static void __declspec(naked) PerceptionRangeHook() {
__asm {
push ecx;
push 0;
call PerceptionRangeHook_Script;
pop ecx;
retn;
}
}
static void __declspec(naked) PerceptionRangeSeeHook() {
__asm {
push ecx;
push 1;
call PerceptionRangeHook_Script;
pop ecx;
cmp eax, 2;
jne nevermind; // normal return
dec eax;
mov dword ptr[esp + 0x2C - 0x1C + 4], eax; // set 1, skip blocking check
dec eax;
nevermind:
retn;
}
}
static void __declspec(naked) PerceptionRangeHearHook() {
__asm {
push ecx;
push 2;
call PerceptionRangeHook_Script;
pop ecx;
retn;
}
}
// 4.x backport
static int __fastcall SwitchHandHook_Script(TGameObj* item, TGameObj* itemReplaced, DWORD addr) {
if (itemReplaced && ItemGetType(itemReplaced) == item_type_weapon && ItemGetType(item) == item_type_ammo) {
return -1; // to prevent inappropriate hook call after dropping ammo on weapon
}
BeginHook();
argCount = 3;
args[0] = (addr < 0x47136D) ? 1 : 2; // slot: 1 - left, 2 - right
args[1] = (DWORD)item;
args[2] = (DWORD)itemReplaced;
RunHookScript(HOOK_INVENTORYMOVE);
int result = PartyControl_SwitchHandHook(item);
if (result != -1) {
cRetTmp = 0;
SetHSReturn(result);
}
result = (cRet > 0) ? rets[0] : -1;
EndHook();
return result;
}
/*
This hook is called every time an item is placed into either hand slot via inventory screen drag&drop
If switch_hand_ function is not called, item is not placed anywhere (it remains in main inventory)
*/
static void __declspec(naked) SwitchHandHook() {
__asm {
pushadc;
mov ecx, eax; // item being moved
mov edx, [edx]; // other item
mov eax, [esp + 12]; // back address
push eax;
call SwitchHandHook_Script;
cmp eax, -1; // ret value
popadc;
jne skip;
jmp switch_hand_;
skip:
retn;
}
}
/* Common inventory move hook */
static int __fastcall InventoryMoveHook_Script(DWORD itemReplace, DWORD item, int type) {
BeginHook();
argCount = 3;
args[0] = type; // event type
args[1] = item; // item being dropped
args[2] = itemReplace; // item being replaced here
RunHookScript(HOOK_INVENTORYMOVE);
int result = (cRet > 0) ? rets[0] : -1;
EndHook();
return result;
}
static const DWORD UseArmorHack_back = 0x4713AF; // normal operation (old 0x4713A9)
static const DWORD UseArmorHack_skip = 0x471481; // skip code, prevent wearing armor
// This hack is called when an armor is dropped into the armor slot at inventory screen
static void __declspec(naked) UseArmorHack() {
__asm {
mov ecx, ds:[_i_worn]; // replacement item (override code)
mov edx, [esp + 0x58 - 0x40]; // item
push ecx;
push 3; // event: armor slot
call InventoryMoveHook_Script; // ecx - replacement item
cmp eax, -1; // ret value
pop ecx;
jne skip;
jmp UseArmorHack_back;
skip:
jmp UseArmorHack_skip;
}
}
static void __declspec(naked) MoveInventoryHook() {
__asm {
pushadc;
xor eax, eax;
mov ecx, eax; // no item replace
cmp dword ptr ds:[_curr_stack], 0;
jle noCont;
mov ecx, eax; // contaner ptr
mov eax, 5;
noCont:
push eax; // event: 0 - main backpack, 5 - contaner
call InventoryMoveHook_Script; // edx - item
cmp eax, -1; // ret value
popadc;
jne skip;
jmp item_add_force_;
skip:
retn;
}
}
// Hooks into dropping item from inventory to ground
// - allows to prevent item being dropped if 0 is returned with set_sfall_return
// - called for every item when dropping multiple items in stack (except caps)
// - when dropping caps it called always once
// - if 0 is returned while dropping caps, selected amount - 1 will still disappear from inventory (fixed)
static DWORD nextHookDropSkip = 0;
static int dropResult = -1;
static const DWORD InvenActionObjDropRet = 0x473874;
static void __declspec(naked) InvenActionCursorObjDropHook() {
if (nextHookDropSkip) {
nextHookDropSkip = 0;
goto skipHook;
} else {
__asm {
pushadc;
xor ecx, ecx; // no itemReplace
push 6; // event: item drop ground
call InventoryMoveHook_Script; // edx - item
mov dropResult, eax; // ret value
popadc;
cmp dword ptr [esp], 0x47379A + 5; // caps call address
jz capsMultiDrop;
}
}
if (dropResult == -1) {
skipHook:
_asm call obj_drop_;
}
_asm retn;
/* for only caps multi drop */
capsMultiDrop:
if (dropResult == -1) {
nextHookDropSkip = 1;
_asm call item_remove_mult_;
_asm retn;
}
_asm add esp, 4;
_asm jmp InvenActionObjDropRet; // no caps drop
}
static void __declspec(naked) InvenActionExplosiveDropHack() {
__asm {
pushadc;
xor ecx, ecx; // no itemReplace
push 6; // event: item drop ground
call InventoryMoveHook_Script; // edx - item
cmp eax, -1; // ret value
popadc;
jnz noDrop;
mov dword ptr ds:[_dropped_explosive], ebp; // overwritten engine code (ebp = 1)
mov nextHookDropSkip, ebp;
retn;
noDrop:
add esp, 4;
jmp InvenActionObjDropRet; // no drop
}
}
static int __fastcall DropIntoContainer(DWORD ptrCont, DWORD item, DWORD addrCall) {
int type = 5; // event: move to container (Crafty compatibility)
if (addrCall == 0x47147C + 5) { // drop out contaner
ptrCont = 0;
type = 0; // event: move to main backpack
}
return InventoryMoveHook_Script(ptrCont, item, type);
}
static const DWORD DropIntoContainer_back = 0x47649D; // normal operation
static const DWORD DropIntoContainer_skip = 0x476503;
static void __declspec(naked) DropIntoContainerHack() {
__asm {
pushadc;
mov ecx, ebp; // contaner ptr
mov edx, esi; // item
mov eax, [esp + 0x10 + 12]; // call address
push eax;
call DropIntoContainer;
cmp eax, -1; // ret value
popadc;
jne skipdrop;
jmp DropIntoContainer_back;
skipdrop:
mov eax, -1;
jmp DropIntoContainer_skip;
}
}
static const DWORD DropIntoContainerRet = 0x471481;
static void __declspec(naked) DropIntoContainerHandSlotHack() {
__asm {
call drop_into_container_;
jmp DropIntoContainerRet;
}
}
//static const DWORD DropAmmoIntoWeaponHack_back = 0x47658D; // proceed with reloading
static const DWORD DropAmmoIntoWeaponHack_return = 0x476643;
static void __declspec(naked) DropAmmoIntoWeaponHook() {
__asm {
pushadc;
mov ecx, ebp; // weapon ptr
mov edx, [esp + 16]; // item var: ammo_
push 4; // event: weapon reloading
call InventoryMoveHook_Script;
cmp eax, -1; // ret value
popadc;
jne donothing;
jmp item_w_can_reload_;
//mov ebx, 1; // overwritten code
//jmp DropAmmoIntoWeaponHack_back;
donothing:
add esp, 4; // destroy return address
xor eax, eax; // result 0
jmp DropAmmoIntoWeaponHack_return;
}
}
static void __declspec(naked) PickupObjectHack() {
__asm {
cmp edi, ds:[_obj_dude];
je runHook;
xor edx, edx; // engine handler
retn;
runHook:
push eax;
push ecx;
mov edx, ecx;
xor ecx, ecx; // no itemReplace
push 7; // event: item pickup
call InventoryMoveHook_Script; // edx - item
mov edx, eax; // ret value
pop ecx;
pop eax;
inc edx; // 0 - engine handler, otherwise cancel pickup
retn;
}
}
static void __declspec(naked) InvenPickupHook() {
__asm {
call mouse_click_in_;
test eax, eax;
jnz runHook;
retn;
runHook:
cmp dword ptr ds:[_curr_stack], 0;
jnz skip;
mov edx, [esp + 0x58 - 0x40 + 4]; // item
xor ecx, ecx; // no itemReplace
push 8; // event: drop item on character portrait
call InventoryMoveHook_Script;
cmp eax, -1; // ret value
je skip;
xor eax, eax; // 0 - cancel, otherwise engine handler
skip:
retn;
}
}
/* Common InvenWield hook */
static bool InvenWieldHook_Script(int flag) {
argCount = 4;
args[3] = flag; // invenwield flag
RunHookScript(HOOK_INVENWIELD);
bool result = (cRet == 0 || rets[0] == -1);
EndHook();
return result; // True - use engine handler
}
static void __declspec(naked) InvenWieldFuncHook() {
__asm {
HookBegin;
mov args[0], eax; // critter
mov args[4], edx; // item
mov args[8], ebx; // slot
pushad;
}
// right hand slot?
if (args[2] != INVEN_TYPE_RIGHT_HAND && ItemGetType((TGameObj*)args[1]) != item_type_armor) {
args[2] = INVEN_TYPE_LEFT_HAND;
}
InvenWieldHook_Script(1); // wield flag
__asm {
test al, al;
popad;
jz skip;
jmp invenWieldFunc_;
skip:
mov eax, -1;
retn;
}
}
// called when unwielding weapons
static void __declspec(naked) InvenUnwieldFuncHook() {
__asm {
HookBegin;
mov args[0], eax; // critter
mov args[8], edx; // slot
pushad;
}
// set slot
if (args[2] == 0) { // left hand slot?
args[2] = INVEN_TYPE_LEFT_HAND;
}
args[1] = (DWORD)GetItemPtrSlot((TGameObj*)args[0], args[2]); // get item
InvenWieldHook_Script(0); // unwield flag
__asm {
test al, al;
popad;
jz skip;
jmp invenUnwieldFunc_;
skip:
mov eax, -1;
retn;
}
}
static void __declspec(naked) CorrectFidForRemovedItemHook() {
__asm {
HookBegin;
mov args[0], eax; // critter
mov args[4], edx; // item
mov args[8], ebx; // item flag
pushad;
}
// set slot
if (args[2] & 0x2000000) { // right hand slot
args[2] = INVEN_TYPE_RIGHT_HAND;
} else if (args[2] & 0x1000000) { // left hand slot
args[2] = INVEN_TYPE_LEFT_HAND;
} else {
args[2] = INVEN_TYPE_WORN; // armor slot
}
InvenWieldHook_Script(0); // unwield flag (armor by default)
__asm {
test al, al;
popad;
jz skip;
jmp correctFidForRemovedItem_;
skip:
mov eax, -1;
retn;
}
}
DWORD _stdcall GetHSArgCount() {
return argCount;
}
DWORD _stdcall GetHSArg() {
return (cArg == argCount) ? 0 : args[cArg++];
}
void _stdcall SetHSArg(DWORD id, DWORD value) {
if (id < argCount) args[id] = value;
}
DWORD* _stdcall GetHSArgs() {
return args;
}
void _stdcall SetHSReturn(DWORD value) {
if (cRetTmp < maxRets) {
rets[cRetTmp++] = value;
}
if (cRetTmp > cRet) {
cRet = cRetTmp;
}
}
void _stdcall RegisterHook(DWORD script, DWORD id, DWORD procNum) {
if (id >= numHooks) return;
for (std::vector<sHookScript>::iterator it = hooks[id].begin(); it != hooks[id].end(); ++it) {
if (it->prog.ptr == script) {
if (procNum == 0) hooks[id].erase(it); // unregister
return;
}
}
if (procNum == 0) return; // prevent registration to first location in procedure when reusing "unregister" method
sScriptProgram *prog = GetGlobalScriptProgram(script);
if (prog) {
dlog_f("Global script %08x registered as hook id %d\n", DL_HOOK, script, id);
sHookScript hook;
hook.prog = *prog;
hook.callback = procNum;
hook.isGlobalScript = true;
hooks[id].push_back(hook);
}
}
static void LoadHookScript(const char* name, int id) {
if (id >= numHooks || isGameScript(name)) return;
char filename[MAX_PATH];
sprintf(filename, "scripts\\%s.int", name);
bool fileExist;
__asm {
lea eax, filename
call db_access_
mov fileExist, al
}
if (fileExist) {
sScriptProgram prog;
dlog("> ", DL_HOOK);
dlog(name, DL_HOOK);
LoadScriptProgram(prog, name);
if (prog.ptr) {
dlogr(" Done", DL_HOOK);
sHookScript hook;
hook.prog = prog;
hook.callback = -1;
hook.isGlobalScript = false;
hooks[id].push_back(hook);
AddProgramToMap(prog);
} else {
dlogr(" Error!", DL_HOOK);
}
}
}
static void HookScriptInit2() {
dlogr("Loading hook scripts:", DL_HOOK|DL_INIT);
char* mask = "scripts\\hs_*.int";
DWORD *filenames;
__asm {
xor ecx, ecx
xor ebx, ebx
lea edx, filenames
mov eax, mask
call db_get_file_list_
}
LoadHookScript("hs_tohit", HOOK_TOHIT);
HookCall(0x421686, &ToHitHook); // combat_safety_invalidate_weapon_func_
HookCall(0x4231D9, &ToHitHook); // check_ranged_miss_
HookCall(0x42331F, &ToHitHook); // shoot_along_path_
HookCall(0x4237FC, &ToHitHook); // compute_attack_
HookCall(0x424379, &ToHitHook); // determine_to_hit_
HookCall(0x42438D, &ToHitHook); // determine_to_hit_no_range_
HookCall(0x42439C, &ToHitHook); // determine_to_hit_from_tile_
HookCall(0x42679A, &ToHitHook); // combat_to_hit_
LoadHookScript("hs_afterhitroll", HOOK_AFTERHITROLL);
MakeJump(0x423893, AfterHitRollHook);
LoadHookScript("hs_calcapcost", HOOK_CALCAPCOST);
HookCall(0x42307A, &CalcApCostHook);
HookCall(0x42669F, &CalcApCostHook);
HookCall(0x42687B, &CalcApCostHook);
HookCall(0x42A625, &CalcApCostHook);
HookCall(0x42A655, &CalcApCostHook);
HookCall(0x42A686, &CalcApCostHook);
HookCall(0x42AE32, &CalcApCostHook);
HookCall(0x42AE71, &CalcApCostHook);
HookCall(0x460048, &CalcApCostHook);
HookCall(0x47807B, &CalcApCostHook);
MakeCall(0x478083, CalcApCostHook2);
LoadHookScript("hs_deathanim1", HOOK_DEATHANIM1);
LoadHookScript("hs_deathanim2", HOOK_DEATHANIM2);
HookCall(0x4109DE, &CalcDeathAnimHook);
HookCall(0x410981, &CalcDeathAnimHook2);
HookCall(0x4109A1, &CalcDeathAnimHook2);
HookCall(0x4109BF, &CalcDeathAnimHook2);
LoadHookScript("hs_combatdamage", HOOK_COMBATDAMAGE);
HookCall(0x42326C, ComputeDamageHook); // check_ranged_miss()
HookCall(0x4233E3, ComputeDamageHook); // shoot_along_path() - for extra burst targets
HookCall(0x423AB7, ComputeDamageHook); // compute_attack()
HookCall(0x423BBF, ComputeDamageHook); // compute_attack()
HookCall(0x423DE7, ComputeDamageHook); // compute_explosion_on_extras()
HookCall(0x423E69, ComputeDamageHook); // compute_explosion_on_extras()
HookCall(0x424220, ComputeDamageHook); // attack_crit_failure()
HookCall(0x4242FB, ComputeDamageHook); // attack_crit_failure()
LoadHookScript("hs_ondeath", HOOK_ONDEATH);
HookCall(0x4130CC, &OnDeathHook);
HookCall(0x4130EF, &OnDeathHook);
HookCall(0x413603, &OnDeathHook);
HookCall(0x426EF0, &OnDeathHook);
HookCall(0x42D1EC, &OnDeathHook);
HookCall(0x42D6F9, &OnDeathHook);
HookCall(0x457BC5, &OnDeathHook);
HookCall(0x457E3A, &OnDeathHook);
HookCall(0x457E54, &OnDeathHook);
HookCall(0x4C14F9, &OnDeathHook);
HookCall(0x425161, &OnDeathHook2);
LoadHookScript("hs_findtarget", HOOK_FINDTARGET);
HookCall(0x429143, FindTargetHook);
LoadHookScript("hs_useobjon", HOOK_USEOBJON);
HookCall(0x49C606, &UseObjOnHook);
HookCall(0x473619, &UseObjOnHook);
// the following hooks allows to catch drug use of AI and from action cursor
HookCall(0x4285DF, &UseObjOnHook_item_d_take_drug); // ai_check_drugs
HookCall(0x4286F8, &UseObjOnHook_item_d_take_drug); // ai_check_drugs
HookCall(0x4287F8, &UseObjOnHook_item_d_take_drug); // ai_check_drugs
HookCall(0x473573, &UseObjOnHook_item_d_take_drug); // inven_action_cursor
LoadHookScript("hs_removeinvenobj", HOOK_REMOVEINVENOBJ);
MakeJump(0x477492, RemoveObjHook); // old 0x477490
LoadHookScript("hs_barterprice", HOOK_BARTERPRICE);
HookCall(0x474D4C, BarterPriceHook);
HookCall(0x475735, BarterPriceHook);
HookCall(0x475762, BarterPriceHook);
HookCall(0X47551A, PC_BarterPriceHook);
MakeJump(0x474D3F, OverrideCost_BarterPriceHack); // just overrides cost of offered goods
LoadHookScript("hs_movecost", HOOK_MOVECOST);
HookCall(0x417665, &MoveCostHook);
HookCall(0x44B88A, &MoveCostHook);
LoadHookScript("hs_hexmoveblocking", HOOK_HEXMOVEBLOCKING);
LoadHookScript("hs_hexaiblocking", HOOK_HEXAIBLOCKING);
LoadHookScript("hs_hexshootblocking", HOOK_HEXSHOOTBLOCKING);
LoadHookScript("hs_hexsightblocking", HOOK_HEXSIGHTBLOCKING);
SafeWrite32(0x413979, (DWORD)&HexSightBlockingHook);
SafeWrite32(0x4C1A88, (DWORD)&HexShootBlockingHook);
SafeWrite32(0x423178, (DWORD)&HexShootBlockingHook);
SafeWrite32(0x4232D4, (DWORD)&HexShootBlockingHook);
SafeWrite32(0x423B4D, (DWORD)&HexShootBlockingHook);
SafeWrite32(0x426CF8, (DWORD)&HexShootBlockingHook);
SafeWrite32(0x42A570, (DWORD)&HexShootBlockingHook);
SafeWrite32(0x42A0A4, (DWORD)&HexABlockingHook);
MakeJump(0x48B848, HexMBlockingHook);
LoadHookScript("hs_itemdamage", HOOK_ITEMDAMAGE);
HookCall(0x478560, &ItemDamageHook);
LoadHookScript("hs_ammocost", HOOK_AMMOCOST);
HookCall(0x423A7C, AmmoCostHook);
LoadHookScript("hs_useobj", HOOK_USEOBJ);
HookCall(0x42AEBF, &UseObjHook);
HookCall(0x473607, &UseObjHook);
HookCall(0x49C12E, &UseObjHook);
LoadHookScript("hs_keypress", HOOK_KEYPRESS);
LoadHookScript("hs_mouseclick", HOOK_MOUSECLICK);
LoadHookScript("hs_useskill", HOOK_USESKILL);
HookCall(0x49C48F, &UseSkillHook);
HookCall(0x49D12E, &UseSkillHook);
LoadHookScript("hs_steal", HOOK_STEAL);
HookCall(0x4749A2, &StealCheckHook);
HookCall(0x474A69, &StealCheckHook);
LoadHookScript("hs_withinperception", HOOK_WITHINPERCEPTION);
HookCall(0x429157, PerceptionRangeHook);
HookCall(0x42B4ED, PerceptionRangeHook);
HookCall(0x42BC87, PerceptionRangeHook);
HookCall(0x42BC9F, PerceptionRangeHook);
HookCall(0x42BD04, PerceptionRangeHook);
HookCall(0x456BA2, PerceptionRangeSeeHook);
HookCall(0x458403, PerceptionRangeHearHook);
LoadHookScript("hs_inventorymove", HOOK_INVENTORYMOVE);
HookCall(0x4712E3, SwitchHandHook); // left slot
HookCall(0x47136D, SwitchHandHook); // right slot
MakeJump(0x4713A9, UseArmorHack); // old 0x4713A3
MakeJump(0x476491, DropIntoContainerHack);
MakeJump(0x471338, DropIntoContainerHandSlotHack);
MakeJump(0x4712AB, DropIntoContainerHandSlotHack);
HookCall(0x471200, MoveInventoryHook);
HookCall(0x476549, DropAmmoIntoWeaponHook); // old 0x476588
HookCall(0x473851, InvenActionCursorObjDropHook);
HookCall(0x47386F, InvenActionCursorObjDropHook);
HookCall(0x47379A, InvenActionCursorObjDropHook); // caps multi drop
MakeCall(0x473807, InvenActionExplosiveDropHack, 1); // drop active explosives
MakeCall(0x49B660, PickupObjectHack);
SafeWrite32(0x49B665, 0x850FD285); // test edx, edx
SafeWrite32(0x49B669, 0xC2); // jnz 0x49B72F
SafeWrite8(0x49B66E, 0xFE); // cmp edi > cmp esi
HookCall(0x471457, InvenPickupHook);
LoadHookScript("hs_invenwield", HOOK_INVENWIELD);
HookCall(0x47275E, InvenWieldFuncHook); // inven_wield_
HookCall(0x495FDF, InvenWieldFuncHook); // partyMemberCopyLevelInfo_
HookCall(0x45967D, InvenUnwieldFuncHook); // op_metarule_
HookCall(0x472A5A, InvenUnwieldFuncHook); // inven_unwield_
HookCall(0x495F0B, InvenUnwieldFuncHook); // partyMemberCopyLevelInfo_
HookCall(0x45680C, CorrectFidForRemovedItemHook); // op_rm_obj_from_inven_
HookCall(0x45C4EA, CorrectFidForRemovedItemHook); // op_move_obj_inven_to_obj_
__asm {
xor edx, edx
lea eax, filenames
call db_free_file_list_
}
dlogr("Finished loading hook scripts.", DL_HOOK|DL_INIT);
}
void HookScriptClear() {
for (int i = 0; i < numHooks; i++) {
hooks[i].clear();
}
}
void HookScriptInit() {
isGlobalScriptLoading = 1; // this should allow to register global exported variables
HookScriptInit2();
InitingHookScripts = 1;
for (int i = 0; i < numHooks; i++) {
if (hooks[i].size()) {
InitScriptProgram(hooks[i][0].prog);// zero hook is always hs_*.int script because Hook scripts are loaded BEFORE global scripts
}
}
isGlobalScriptLoading = 0;
InitingHookScripts = 0;
}
// run specific event procedure for all hook scripts
void _stdcall RunHookScriptsAtProc(DWORD procId) {
for (int i = 0; i < numHooks; i++) {
if (hooks[i].size() > 0 && !hooks[i][0].isGlobalScript) {
RunScriptProc(&hooks[i][0].prog, procId);
}
}
}