mirror of
https://github.com/sfall-team/sfall.git
synced 2026-07-27 16:52:34 -07:00
1913 lines
47 KiB
C++
1913 lines
47 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 <string>
|
|
#include <vector>
|
|
|
|
#include "main.h"
|
|
#include "FalloutEngine.h"
|
|
#include "Combat.h"
|
|
#include "LoadGameHook.h"
|
|
#include "Logging.h"
|
|
#include "PartyControl.h"
|
|
#include "ScriptExtender.h"
|
|
|
|
#include "HookScripts.h"
|
|
|
|
// Number of types of hooks
|
|
static const int numHooks = HOOK_COUNT;
|
|
|
|
static const int maxArgs = 16; // Maximum number of hook arguments
|
|
static const int maxRets = 8; // Maximum number of return values
|
|
static const int maxDepth = 8; // Maximum recursion depth for hook calls
|
|
|
|
// Struct for registered hook script
|
|
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;
|
|
|
|
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)
|
|
|
|
static struct HooksPositionInfo {
|
|
long hsPosition; // index of the hs_* script, or the beginning of the position for registering scripts using register_hook
|
|
// long positionShift; // offset to the last script registered by register_hook
|
|
bool hasHsScript;
|
|
} hooksInfo[numHooks];
|
|
|
|
DWORD initingHookScripts;
|
|
|
|
#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;
|
|
std::memcpy(&savedArgs[cDepth].oldArgs, args, maxArgs * sizeof(DWORD)); // values of the arguments
|
|
if (cRet) std::memcpy(&savedArgs[cDepth].oldRets, rets, maxRets * sizeof(DWORD)); // return values
|
|
|
|
//devlog_f("\nSaved cArgs/cRet: %d / %d(%d)\n", DL_HOOK, savedArgs[cDepth].argCount, savedArgs[cDepth].cRet, cRetTmp);
|
|
//for (unsigned int i = 0; i < maxArgs; i++) {
|
|
// devlog_f("Saved Args/Rets: %d / %d\n", DL_HOOK, savedArgs[cDepth].oldArgs[i], ((i < maxRets) ? savedArgs[cDepth].oldRets[i] : -1));
|
|
//}
|
|
}
|
|
callDepth++;
|
|
|
|
devlog_f("Begin running hook, current depth: %d, current executable hook: %d\n", DL_HOOK, callDepth, currentRunHook);
|
|
}
|
|
|
|
static void __stdcall RunSpecificHookScript(sHookScript *hook) {
|
|
cArg = 0;
|
|
cRetTmp = 0;
|
|
if (hook->callback != -1) {
|
|
ExecuteProcedure(hook->prog.ptr, hook->callback);
|
|
} else {
|
|
hook->callback = RunScriptStartProc(&hook->prog); // run start
|
|
}
|
|
}
|
|
|
|
static void __stdcall RunHookScript(DWORD hook) {
|
|
cRet = 0;
|
|
if (!hooks[hook].empty()) {
|
|
if (callDepth > 8) {
|
|
DebugPrintf("\n[SFALL] The hook ID: %d cannot be executed.", hook);
|
|
dlog_f("The hook %d cannot be executed due to exceeding depth limit\n", DL_MAIN, hook);
|
|
return;
|
|
}
|
|
currentRunHook = hook;
|
|
size_t hooksCount = hooks[hook].size();
|
|
dlog_f("Running hook %d, which has %0d entries attached, depth: %d\n", DL_HOOK, hook, hooksCount, callDepth);
|
|
for (int i = hooksCount - 1; i >= 0; i--) {
|
|
RunSpecificHookScript(&hooks[hook][i]);
|
|
|
|
//devlog_f("> Hook: %d, script entry: %d done\n", DL_HOOK, hook, i);
|
|
//devlog_f("> Check cArg/cRet: %d / %d(%d)\n", DL_HOOK, cArg, cRet, cRetTmp);
|
|
//for (unsigned int i = 0; i < maxArgs; i++) {
|
|
// devlog_f("> Check Args/Rets: %d / %d\n", DL_HOOK, args[i], ((i < maxRets) ? rets[i] : -1));
|
|
//}
|
|
}
|
|
} else {
|
|
cArg = 0;
|
|
|
|
devlog_f(">>> Try running hook ID: %d\n", DL_HOOK, hook);
|
|
}
|
|
}
|
|
|
|
static void __stdcall EndHook() {
|
|
devlog_f("End running hook %d, current depth: %d\n", DL_HOOK, currentRunHook, callDepth);
|
|
|
|
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
|
|
std::memcpy(args, &savedArgs[cDepth].oldArgs, maxArgs * sizeof(DWORD));
|
|
if (cRet) std::memcpy(rets, &savedArgs[cDepth].oldRets, maxRets * sizeof(DWORD));
|
|
|
|
//devlog_f("Restored cArgs/cRet: %d / %d(%d)\n", DL_HOOK, argCount, cRet, cRetTmp);
|
|
//for (unsigned int i = 0; i < maxArgs; i++) {
|
|
// devlog_f("Restored Args/Rets: %d / %d\n", DL_HOOK, args[i], ((i < maxRets) ? rets[i] : -1));
|
|
//}
|
|
}
|
|
} else {
|
|
currentRunHook = -1;
|
|
}
|
|
}
|
|
|
|
// BEGIN HOOKS
|
|
static void __declspec(naked) ToHitHook() {
|
|
__asm {
|
|
HookBegin;
|
|
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 + 8];
|
|
mov args[24], eax; // is ranged
|
|
push eax;
|
|
mov eax, [esp + 8];
|
|
mov args[20], eax; // attack type
|
|
push eax;
|
|
mov eax, args[4]; // restore
|
|
call determine_to_hit_func_;
|
|
mov args[0], eax;
|
|
mov ebx, eax;
|
|
}
|
|
argCount = 8;
|
|
|
|
args[7] = Combat_determineHitChance;
|
|
RunHookScript(HOOK_TOHIT);
|
|
|
|
__asm {
|
|
cmp cRet, 1;
|
|
cmovge ebx, rets[0];
|
|
call EndHook;
|
|
mov eax, ebx;
|
|
retn 8;
|
|
}
|
|
}
|
|
|
|
// 4.x backport
|
|
static DWORD __fastcall AfterHitRollHook_Script(TComputeAttack &ctd, DWORD hitChance, DWORD hit) {
|
|
BeginHook();
|
|
argCount = 5;
|
|
|
|
args[0] = hit;
|
|
args[1] = (DWORD)ctd.attacker; // Attacker
|
|
args[2] = (DWORD)ctd.target; // Target
|
|
args[3] = ctd.bodyPart; // bodypart
|
|
args[4] = hitChance;
|
|
|
|
RunHookScript(HOOK_AFTERHITROLL);
|
|
if (cRet > 0) {
|
|
hit = rets[0];
|
|
if (cRet > 1) {
|
|
ctd.bodyPart = rets[1];
|
|
if (cRet > 2) ctd.target = (TGameObj*)rets[2];
|
|
}
|
|
}
|
|
EndHook();
|
|
|
|
return hit;
|
|
}
|
|
|
|
static void __declspec(naked) AfterHitRollHook() {
|
|
__asm {
|
|
mov ecx, esi; // ctd
|
|
mov edx, [esp + 0x18 + 4]; // hit chance
|
|
push eax; // was it a hit?
|
|
call AfterHitRollHook_Script;
|
|
// engine code
|
|
mov ebx, eax;
|
|
cmp eax, ROLL_FAILURE;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
// Implementation of item_w_mp_cost_ engine function with the hook
|
|
long __fastcall sf_item_w_mp_cost(TGameObj* source, long hitMode, long isCalled) {
|
|
long cost = ItemWMpCost(source, hitMode, isCalled);
|
|
|
|
BeginHook();
|
|
argCount = 4;
|
|
|
|
args[0] = (DWORD)source;
|
|
args[1] = hitMode;
|
|
args[2] = isCalled;
|
|
args[3] = cost;
|
|
|
|
RunHookScript(HOOK_CALCAPCOST);
|
|
|
|
if (cRet > 0) cost = rets[0];
|
|
EndHook();
|
|
|
|
return cost;
|
|
}
|
|
|
|
static void __declspec(naked) CalcApCostHook() {
|
|
__asm {
|
|
HookBegin;
|
|
mov args[0], eax;
|
|
mov args[4], edx;
|
|
mov args[8], ebx;
|
|
call item_w_mp_cost_;
|
|
mov args[12], eax;
|
|
mov ebx, eax;
|
|
push ecx;
|
|
}
|
|
|
|
argCount = 4;
|
|
RunHookScript(HOOK_CALCAPCOST);
|
|
|
|
__asm {
|
|
cmp cRet, 1;
|
|
cmovge ebx, rets[0];
|
|
call EndHook;
|
|
mov eax, ebx;
|
|
pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
// this is for using non-weapon items, always 2 AP in vanilla
|
|
static void __declspec(naked) CalcApCostHook2() {
|
|
__asm {
|
|
HookBegin;
|
|
mov args[0], ecx; // critter
|
|
mov args[4], edx; // attack type (to determine hand)
|
|
mov args[8], ebx;
|
|
mov ebx, 2; // vanilla cost value
|
|
mov args[12], ebx;
|
|
//push ecx;
|
|
}
|
|
|
|
argCount = 4;
|
|
RunHookScript(HOOK_CALCAPCOST);
|
|
|
|
__asm {
|
|
cmp cRet, 1;
|
|
cmovge ebx, rets[0];
|
|
call EndHook;
|
|
mov eax, ebx;
|
|
//pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
// 4.x backport
|
|
static DWORD __fastcall CalcDeathAnimHook_Script(DWORD damage, TGameObj* target, TGameObj* attacker, TGameObj* weapon, int animation, int hitBack) {
|
|
BeginHook();
|
|
argCount = 5; // was 4
|
|
|
|
args[1] = (DWORD)attacker;
|
|
args[2] = (DWORD)target;
|
|
args[3] = damage;
|
|
|
|
// weapon_ptr
|
|
args[0] = (weapon) ? weapon->protoId : -1; // attack is unarmed
|
|
|
|
bool createNewObj = false;
|
|
args[4] = -1; // set 'no anim' for combined hooks use
|
|
RunHookScript(HOOK_DEATHANIM1);
|
|
if (cRet > 0) {
|
|
DWORD pid = rets[0];
|
|
args[0] = pid; // replace for HOOK_DEATHANIM2
|
|
TGameObj* object = nullptr;
|
|
if (ObjPidNew((TGameObj*)&object, pid) != -1) { // create new object
|
|
createNewObj = true;
|
|
weapon = object; // replace pointer with newly created weapon object
|
|
}
|
|
cRet = 0; // reset rets from HOOK_DEATHANIM1
|
|
}
|
|
|
|
DWORD animDeath = PickDeath(attacker, target, weapon, damage, animation, hitBack); // vanilla pick death
|
|
|
|
args[4] = animDeath;
|
|
RunHookScript(HOOK_DEATHANIM2);
|
|
|
|
if (cRet > 0) animDeath = rets[0];
|
|
EndHook();
|
|
|
|
if (createNewObj) ObjEraseObject(weapon, 0); // delete created object
|
|
|
|
return animDeath;
|
|
}
|
|
|
|
static void __declspec(naked) CalcDeathAnimHook() {
|
|
__asm {
|
|
push ecx;
|
|
push edx;
|
|
push [esp + 4 + 12]; // hit_from_back
|
|
push [esp + 4 + 12]; // animation
|
|
push ebx; // weapon_ptr
|
|
push eax; // attacker
|
|
call CalcDeathAnimHook_Script; // ecx - damage, edx - target
|
|
pop edx;
|
|
pop ecx;
|
|
retn 8;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) CalcDeathAnim2Hook() {
|
|
__asm {
|
|
call check_death_; // call original function
|
|
mov ebx, eax;
|
|
call BeginHook;
|
|
mov eax, [esp + 60];
|
|
mov args[4], eax; // attacker
|
|
mov args[8], esi; // target
|
|
mov eax, [esp + 12];
|
|
mov args[12], eax; // dmgAmount
|
|
mov args[16], ebx; // calculated animID
|
|
}
|
|
|
|
argCount = 5;
|
|
args[0] = -1; // weaponPid
|
|
RunHookScript(HOOK_DEATHANIM2);
|
|
|
|
__asm {
|
|
cmp cRet, 1;
|
|
cmovge ebx, rets[0];
|
|
call EndHook;
|
|
mov eax, ebx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
// 4.x backport
|
|
static void __fastcall ComputeDamageHook_Script(TComputeAttack &ctd, DWORD rounds, DWORD multiplier) {
|
|
BeginHook();
|
|
argCount = 13;
|
|
|
|
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
|
|
args[12] = (DWORD)&ctd; // main_ctd/shoot_ctd/explosion_ctd
|
|
|
|
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() {
|
|
using namespace Fields;
|
|
__asm {
|
|
push edx;
|
|
call BeginHook;
|
|
mov args[0], esi;
|
|
}
|
|
|
|
argCount = 1;
|
|
RunHookScript(HOOK_ONDEATH);
|
|
EndHook();
|
|
|
|
__asm {
|
|
pop edx;
|
|
// engine code
|
|
mov eax, [esi + protoId];
|
|
mov ebp, ebx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) OnDeathHook2() {
|
|
__asm {
|
|
call partyMemberRemove_;
|
|
HookBegin;
|
|
mov args[0], esi;
|
|
pushadc;
|
|
}
|
|
|
|
argCount = 1;
|
|
RunHookScript(HOOK_ONDEATH);
|
|
EndHook();
|
|
|
|
__asm popadc;
|
|
__asm 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;
|
|
mov args[0], edx; // target
|
|
mov args[4], eax; // user
|
|
mov args[8], ebx; // object
|
|
pushadc;
|
|
}
|
|
|
|
argCount = 3;
|
|
RunHookScript(HOOK_USEOBJON);
|
|
|
|
__asm {
|
|
popadc;
|
|
cmp cRet, 1;
|
|
jl defaultHandler;
|
|
cmp rets[0], -1;
|
|
je defaultHandler;
|
|
mov eax, rets[0];
|
|
HookEnd;
|
|
retn;
|
|
defaultHandler:
|
|
HookEnd;
|
|
jmp protinst_use_item_on_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) Drug_UseObjOnHook() {
|
|
__asm {
|
|
HookBegin;
|
|
mov args[0], eax; // target
|
|
mov args[4], eax; // user
|
|
mov args[8], edx; // object
|
|
pushadc;
|
|
}
|
|
|
|
argCount = 3;
|
|
RunHookScript(HOOK_USEOBJON);
|
|
|
|
__asm {
|
|
popadc;
|
|
cmp cRet, 1;
|
|
jl defaultHandler;
|
|
cmp rets[0], -1;
|
|
je defaultHandler;
|
|
mov eax, rets[0];
|
|
HookEnd;
|
|
retn;
|
|
defaultHandler:
|
|
HookEnd;
|
|
jmp item_d_take_drug_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) UseObjHook() {
|
|
__asm {
|
|
HookBegin;
|
|
mov args[0], eax; // user
|
|
mov args[4], edx; // object
|
|
pushadc;
|
|
}
|
|
|
|
argCount = 2;
|
|
RunHookScript(HOOK_USEOBJ);
|
|
|
|
__asm {
|
|
popadc;
|
|
cmp cRet, 1;
|
|
jl defaultHandler;
|
|
cmp rets[0], -1;
|
|
je defaultHandler;
|
|
mov eax, rets[0];
|
|
HookEnd;
|
|
retn;
|
|
defaultHandler:
|
|
HookEnd;
|
|
jmp protinst_use_item_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) RemoveObjHook() {
|
|
static const DWORD RemoveObjHookRet = 0x477497;
|
|
__asm {
|
|
mov ecx, [esp + 8]; // call addr
|
|
HookBegin;
|
|
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;
|
|
push eax;
|
|
push edx;
|
|
}
|
|
|
|
argCount = 5;
|
|
RunHookScript(HOOK_REMOVEINVENOBJ);
|
|
EndHook();
|
|
|
|
__asm {
|
|
pop edx;
|
|
pop eax;
|
|
sub esp, 0x0C;
|
|
jmp RemoveObjHookRet;
|
|
}
|
|
}
|
|
|
|
// 4.x backport
|
|
// The hook is executed twice when entering the barter screen and after transaction: the first time is for the player; the second time is for NPC
|
|
static DWORD __fastcall BarterPriceHook_Script(register TGameObj* source, register TGameObj* target, DWORD callAddr) {
|
|
bool barterIsParty = (*ptr_dialog_target_is_party != 0);
|
|
long computeCost = BarterComputeValue(source, target);
|
|
|
|
BeginHook();
|
|
argCount = 10;
|
|
|
|
args[0] = (DWORD)source;
|
|
args[1] = (DWORD)target;
|
|
args[2] = !barterIsParty ? computeCost : 0;
|
|
|
|
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;
|
|
|
|
long pcCost = 0;
|
|
if (barterIsParty) {
|
|
args[7] = pcCost;
|
|
pcCost = ItemTotalWeight(pTable);
|
|
} else {
|
|
args[7] = pcCost = ItemTotalCost(pTable);
|
|
}
|
|
|
|
args[8] = (DWORD)(callAddr == 0x474D51); // offers button pressed
|
|
args[9] = (DWORD)barterIsParty;
|
|
|
|
RunHookScript(HOOK_BARTERPRICE);
|
|
|
|
bool isPCHook = (callAddr == -1);
|
|
long cost = isPCHook ? pcCost : computeCost;
|
|
if (!barterIsParty && cRet > 0) {
|
|
if (isPCHook) {
|
|
if (cRet > 1) cost = rets[1]; // new cost for pc
|
|
} else if ((long)rets[0] > -1) {
|
|
cost = rets[0]; // new cost for npc
|
|
}
|
|
}
|
|
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 for pc
|
|
static void __declspec(naked) PC_BarterPriceHook() {
|
|
__asm {
|
|
push edx;
|
|
push ecx;
|
|
//-------
|
|
push -1; // 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 void __declspec(naked) OverrideCost_BarterPriceHook() {
|
|
__asm {
|
|
mov eax, offersGoodsCost;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) MoveCostHook() {
|
|
__asm {
|
|
HookBegin;
|
|
mov args[0], eax;
|
|
mov args[4], edx;
|
|
call critter_compute_ap_from_distance_;
|
|
mov args[8], eax;
|
|
push ebx;
|
|
push ecx;
|
|
mov ebx, eax;
|
|
}
|
|
|
|
argCount = 3;
|
|
RunHookScript(HOOK_MOVECOST);
|
|
|
|
__asm {
|
|
cmp cRet, 1;
|
|
cmovge ebx, rets[0];
|
|
call EndHook;
|
|
mov eax, ebx;
|
|
pop ecx;
|
|
pop ebx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) HexMoveBlockingHook() {
|
|
static const DWORD _obj_blocking_at = 0x48B84E;
|
|
__asm {
|
|
HookBegin;
|
|
mov args[0], eax;
|
|
mov args[4], edx;
|
|
mov args[8], ebx;
|
|
push return;
|
|
// engine code
|
|
push ecx;
|
|
push esi;
|
|
push edi;
|
|
push ebp;
|
|
mov ecx, eax;
|
|
// end engine code
|
|
jmp _obj_blocking_at;
|
|
return:
|
|
mov args[12], eax;
|
|
mov ebx, eax;
|
|
push ecx;
|
|
}
|
|
|
|
argCount = 4;
|
|
RunHookScript(HOOK_HEXMOVEBLOCKING);
|
|
|
|
__asm {
|
|
cmp cRet, 1;
|
|
cmovge ebx, rets[0];
|
|
call EndHook;
|
|
mov eax, ebx;
|
|
pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) HexAIBlockingHook() {
|
|
__asm {
|
|
HookBegin;
|
|
mov args[0], eax;
|
|
mov args[4], edx;
|
|
mov args[8], ebx;
|
|
call obj_ai_blocking_at_;
|
|
mov args[12], eax;
|
|
mov ebx, eax;
|
|
push ecx;
|
|
}
|
|
|
|
argCount = 4;
|
|
RunHookScript(HOOK_HEXAIBLOCKING);
|
|
|
|
__asm {
|
|
cmp cRet, 1;
|
|
cmovge ebx, rets[0];
|
|
call EndHook;
|
|
mov eax, ebx;
|
|
pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) HexShootBlockingHook() {
|
|
__asm {
|
|
HookBegin;
|
|
mov args[0], eax;
|
|
mov args[4], edx;
|
|
mov args[8], ebx;
|
|
call obj_shoot_blocking_at_;
|
|
mov args[12], eax;
|
|
mov ebx, eax;
|
|
push ecx;
|
|
}
|
|
|
|
argCount = 4;
|
|
RunHookScript(HOOK_HEXSHOOTBLOCKING);
|
|
|
|
__asm {
|
|
cmp cRet, 1;
|
|
cmovge ebx, rets[0];
|
|
call EndHook;
|
|
mov eax, ebx;
|
|
pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) HexSightBlockingHook() {
|
|
__asm {
|
|
HookBegin;
|
|
mov args[0], eax;
|
|
mov args[4], edx;
|
|
mov args[8], ebx;
|
|
call obj_sight_blocking_at_;
|
|
mov args[12], eax;
|
|
mov ebx, eax;
|
|
push ecx;
|
|
}
|
|
|
|
argCount = 4;
|
|
RunHookScript(HOOK_HEXSIGHTBLOCKING);
|
|
|
|
__asm {
|
|
cmp cRet, 1;
|
|
cmovge ebx, rets[0];
|
|
call EndHook;
|
|
mov eax, ebx;
|
|
pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) ItemDamageHook() {
|
|
__asm {
|
|
HookBegin;
|
|
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 (add to min/max melee damage)
|
|
pushadc;
|
|
}
|
|
argCount = 6;
|
|
|
|
if (args[2] == 0) { // weapon arg
|
|
args[4] += 8; // type arg
|
|
}
|
|
|
|
RunHookScript(HOOK_ITEMDAMAGE);
|
|
|
|
__asm popadc;
|
|
if (cRet > 0) {
|
|
__asm mov eax, rets[0]; // set min
|
|
if (cRet > 1) {
|
|
__asm mov edx, rets[4]; // set max
|
|
} else {
|
|
HookEnd;
|
|
__asm retn; // no calc random
|
|
}
|
|
}
|
|
HookEnd;
|
|
__asm jmp roll_random_;
|
|
}
|
|
|
|
// 4.x backport
|
|
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], ANIM_fire_burst;
|
|
jl skip;
|
|
cmp dword ptr [esp + 0x1C + 4], 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;
|
|
}
|
|
}
|
|
|
|
void __stdcall KeyPressHook(DWORD* dxKey, bool pressed, DWORD vKey) {
|
|
if (!IsGameLoaded()) {
|
|
return;
|
|
}
|
|
BeginHook();
|
|
argCount = 3;
|
|
args[0] = (DWORD)pressed;
|
|
args[1] = *dxKey;
|
|
args[2] = vKey;
|
|
RunHookScript(HOOK_KEYPRESS);
|
|
if (cRet != 0) *dxKey = rets[0];
|
|
EndHook();
|
|
}
|
|
|
|
void __stdcall MouseClickHook(DWORD button, bool pressed) {
|
|
if (!IsGameLoaded()) {
|
|
return;
|
|
}
|
|
BeginHook();
|
|
argCount = 2;
|
|
args[0] = (DWORD)pressed;
|
|
args[1] = button;
|
|
RunHookScript(HOOK_MOUSECLICK);
|
|
EndHook();
|
|
}
|
|
|
|
static void __declspec(naked) UseSkillHook() {
|
|
__asm {
|
|
HookBegin;
|
|
mov args[0], eax; // user
|
|
mov args[4], edx; // target
|
|
mov args[8], ebx; // skill id
|
|
mov args[12], ecx; // skill bonus
|
|
pushadc;
|
|
}
|
|
|
|
argCount = 4;
|
|
RunHookScript(HOOK_USESKILL);
|
|
|
|
__asm {
|
|
popadc;
|
|
cmp cRet, 1;
|
|
jl defaultHandler;
|
|
cmp rets[0], -1;
|
|
je defaultHandler;
|
|
mov eax, rets[0];
|
|
HookEnd;
|
|
retn;
|
|
defaultHandler:
|
|
HookEnd;
|
|
jmp skill_use_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) StealCheckHook() {
|
|
__asm {
|
|
HookBegin;
|
|
mov args[0], eax; // thief
|
|
mov args[4], edx; // target
|
|
mov args[8], ebx; // item
|
|
mov args[12], ecx; // is planting
|
|
pushadc;
|
|
}
|
|
|
|
argCount = 4;
|
|
RunHookScript(HOOK_STEAL);
|
|
|
|
__asm {
|
|
popadc;
|
|
cmp cRet, 1;
|
|
jl defaultHandler;
|
|
cmp rets[0], -1;
|
|
je defaultHandler;
|
|
mov eax, rets[0];
|
|
HookEnd;
|
|
retn;
|
|
defaultHandler:
|
|
HookEnd;
|
|
jmp skill_check_stealing_;
|
|
}
|
|
}
|
|
|
|
// 4.x backport
|
|
static long __fastcall PerceptionRangeHook_Script(TGameObj* watcher, TGameObj* target, int type) {
|
|
long result = IsWithinPerception(watcher, target);
|
|
|
|
BeginHook();
|
|
argCount = 4;
|
|
|
|
args[0] = (DWORD)watcher;
|
|
args[1] = (DWORD)target;
|
|
args[2] = result;
|
|
args[3] = type;
|
|
|
|
RunHookScript(HOOK_WITHINPERCEPTION);
|
|
|
|
if (cRet > 0) result = rets[0];
|
|
EndHook();
|
|
|
|
return result;
|
|
}
|
|
|
|
// Implementation of is_within_perception_ engine function with the hook
|
|
long __fastcall sf_is_within_perception(TGameObj* watcher, TGameObj* target) { // TODO: add type arg
|
|
return PerceptionRangeHook_Script(watcher, target, 0);
|
|
}
|
|
|
|
static void __declspec(naked) PerceptionRangeHook() {
|
|
__asm {
|
|
push ecx;
|
|
push 0;
|
|
mov ecx, eax;
|
|
call PerceptionRangeHook_Script;
|
|
pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) PerceptionRangeSeeHook() {
|
|
__asm {
|
|
push ecx;
|
|
push 1;
|
|
mov ecx, eax;
|
|
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;
|
|
mov ecx, eax;
|
|
call PerceptionRangeHook_Script;
|
|
pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) PerceptionSearchTargetHook() {
|
|
__asm {
|
|
push ecx;
|
|
push 3;
|
|
mov ecx, eax;
|
|
call PerceptionRangeHook_Script;
|
|
pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
// 4.x backport
|
|
static int __fastcall SwitchHandHook_Script(TGameObj* item, TGameObj* itemReplaced, DWORD addr) {
|
|
if (itemReplaced && GetItemType(itemReplaced) == item_type_weapon && GetItemType(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;
|
|
}
|
|
|
|
// This hack is called when an armor is dropped into the armor slot at inventory screen
|
|
static void __declspec(naked) UseArmorHack() {
|
|
static const DWORD UseArmorHack_back = 0x4713AF; // normal operation (old 0x4713A9)
|
|
static const DWORD UseArmorHack_skip = 0x471481; // skip code, prevent wearing armor
|
|
__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 void __declspec(naked) DropIntoContainerHack() {
|
|
static const DWORD DropIntoContainer_back = 0x47649D; // normal operation
|
|
static const DWORD DropIntoContainer_skip = 0x476503;
|
|
__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 void __declspec(naked) DropIntoContainerHandSlotHack() {
|
|
static const DWORD DropIntoContainerRet = 0x471481;
|
|
__asm {
|
|
call drop_into_container_;
|
|
jmp DropIntoContainerRet;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) DropAmmoIntoWeaponHook() {
|
|
//static const DWORD DropAmmoIntoWeaponHack_back = 0x47658D; // proceed with reloading
|
|
static const DWORD DropAmmoIntoWeaponHack_return = 0x476643;
|
|
__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;
|
|
}
|
|
}
|
|
|
|
// 4.x backport
|
|
/* Common InvenWield script hooks */
|
|
static long __fastcall InvenWieldHook_Script(TGameObj* critter, TGameObj* item, long slot, long isWield, long isRemove) {
|
|
if (!isWield) {
|
|
// for the critter, the right slot is always the active slot
|
|
if (slot == INVEN_TYPE_LEFT_HAND && critter != *ptr_obj_dude) return 1;
|
|
// check the current active slot for the player
|
|
if (slot != INVEN_TYPE_WORN && critter == *ptr_obj_dude) {
|
|
long _slot = (slot != INVEN_TYPE_LEFT_HAND);
|
|
if (_slot != *ptr_itemCurrentItem) return 1; // item in non-active slot
|
|
}
|
|
}
|
|
BeginHook();
|
|
argCount = 5;
|
|
|
|
args[0] = (DWORD)critter;
|
|
args[1] = (DWORD)item;
|
|
args[2] = slot;
|
|
args[3] = isWield; // unwield/wield event
|
|
args[4] = isRemove;
|
|
|
|
RunHookScript(HOOK_INVENWIELD);
|
|
|
|
long result = (cRet == 0 || rets[0] == -1);
|
|
EndHook();
|
|
|
|
return result; // 1 - use engine handler
|
|
}
|
|
|
|
static __declspec(noinline) bool InvenWieldHook_ScriptPart(long isWield, long isRemove = 0) {
|
|
argCount = 5;
|
|
|
|
args[3] = isWield; // unwield/wield event
|
|
args[4] = isRemove;
|
|
|
|
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 && GetItemType((TGameObj*)args[1]) != item_type_armor) {
|
|
args[2] = INVEN_TYPE_LEFT_HAND;
|
|
}
|
|
InvenWieldHook_ScriptPart(1); // wield event
|
|
|
|
__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
|
|
pushadc;
|
|
}
|
|
|
|
// set slot
|
|
if (args[2] == 0) { // left hand slot?
|
|
args[2] = INVEN_TYPE_LEFT_HAND;
|
|
}
|
|
|
|
// get item
|
|
args[1] = (DWORD)GetItemPtrSlot((TGameObj*)args[0], (InvenType)args[2]);
|
|
|
|
InvenWieldHook_ScriptPart(0); // unwield event
|
|
|
|
__asm {
|
|
test al, al;
|
|
popadc;
|
|
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
|
|
pushadc;
|
|
}
|
|
|
|
// set slot
|
|
if (args[2] & ObjectFlag::Right_Hand) { // right hand slot
|
|
args[2] = INVEN_TYPE_RIGHT_HAND;
|
|
} else if (args[2] & ObjectFlag::Left_Hand) { // left hand slot
|
|
args[2] = INVEN_TYPE_LEFT_HAND;
|
|
} else {
|
|
args[2] = INVEN_TYPE_WORN; // armor slot
|
|
}
|
|
|
|
InvenWieldHook_ScriptPart(0, 1); // unwield event (armor by default)
|
|
|
|
// engine handler is not overridden
|
|
__asm {
|
|
popadc;
|
|
jmp correctFidForRemovedItem_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) item_drop_all_hack() {
|
|
using namespace ObjectFlag;
|
|
__asm {
|
|
mov ecx, 1;
|
|
push eax;
|
|
mov [esp + 0x40 - 0x2C + 8], ecx; // itemIsEquipped
|
|
push ecx; // remove event
|
|
push 0; // unwield event
|
|
inc ecx; // INVEN_TYPE_LEFT_HAND (2)
|
|
test ah, Left_Hand >> 24;
|
|
jnz skip;
|
|
test ah, Worn >> 24;
|
|
setz cl; // set INVEN_TYPE_WORN or INVEN_TYPE_RIGHT_HAND
|
|
skip:
|
|
push ecx; // slot
|
|
mov edx, esi; // item
|
|
mov ecx, edi; // critter
|
|
call InvenWieldHook_Script;
|
|
//mov [esp + 0x40 - 0x2C + 8], eax; // itemIsEquipped (eax - hook return result)
|
|
pop eax;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
// called from bugfixes for obj_drop_
|
|
void __declspec(naked) InvenUnwield_HookDrop() { // ecx - critter, edx - item
|
|
using namespace Fields;
|
|
using namespace ObjectFlag;
|
|
__asm {
|
|
pushadc;
|
|
mov eax, INVEN_TYPE_LEFT_HAND;
|
|
test byte ptr [edx + flags + 3], Left_Hand >> 24;
|
|
jnz isLeft;
|
|
test byte ptr [edx + flags + 3], Worn >> 24;
|
|
setz al; // set INVEN_TYPE_WORN or INVEN_TYPE_RIGHT_HAND
|
|
isLeft:
|
|
push 1; // remove event
|
|
push 0; // unwield event
|
|
push eax; // slot
|
|
call InvenWieldHook_Script; // ecx - critter, edx - item
|
|
// engine handler is not overridden
|
|
popadc;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
// called from bugfixes for op_move_obj_inven_to_obj_
|
|
void __declspec(naked) InvenUnwield_HookMove() { // eax - item, edx - critter
|
|
__asm {
|
|
pushadc;
|
|
mov ecx, edx;
|
|
mov edx, eax;
|
|
push 1; // remove event
|
|
xor eax, eax;
|
|
push eax; // unwield event
|
|
push eax; // slot
|
|
call InvenWieldHook_Script; // ecx - critter, edx - item
|
|
// engine handler is not overridden
|
|
popadc;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
// called when unwelding dude weapon and armor
|
|
static void __declspec(naked) op_move_obj_inven_to_obj_hook() {
|
|
__asm {
|
|
cmp eax, ds:[_obj_dude];
|
|
je runHook;
|
|
jmp item_move_all_;
|
|
runHook:
|
|
push eax;
|
|
push edx;
|
|
mov ecx, eax; // keep source
|
|
mov edx, ds:[_itemCurrentItem]; // get player's active slot
|
|
test edx, edx;
|
|
jz left;
|
|
call inven_right_hand_;
|
|
jmp skip;
|
|
left:
|
|
call inven_left_hand_;
|
|
skip:
|
|
test eax, eax;
|
|
jz noWeapon;
|
|
push 1; // remove event
|
|
push 0; // unwield event
|
|
mov ebx, INVEN_TYPE_LEFT_HAND;
|
|
sub ebx, edx;
|
|
push ebx; // slot: INVEN_TYPE_LEFT_HAND or INVEN_TYPE_RIGHT_HAND
|
|
mov edx, eax; // weapon
|
|
mov ebx, ecx; // keep source
|
|
call InvenWieldHook_Script; // ecx - source
|
|
// engine handler is not overridden
|
|
noWeapon:
|
|
mov edx, [esp + 0x30 - 0x20 + 12]; // armor
|
|
test edx, edx;
|
|
jz noArmor;
|
|
xor eax, eax;
|
|
push 1; // remove event
|
|
push eax; // unwield event
|
|
push eax; // slot: INVEN_TYPE_WORN
|
|
mov ecx, ebx; // source
|
|
call InvenWieldHook_Script;
|
|
// engine handler is not overridden
|
|
noArmor:
|
|
pop edx;
|
|
pop eax;
|
|
jmp item_move_all_;
|
|
}
|
|
}
|
|
|
|
// internal function implementation with hook
|
|
long __stdcall CorrectFidForRemovedItem_wHook(TGameObj* critter, TGameObj* item, long flags) {
|
|
long result = 1;
|
|
if (!hooks[HOOK_INVENWIELD].empty()) {
|
|
long slot = INVEN_TYPE_WORN;
|
|
if (flags & ObjectFlag::Right_Hand) { // right hand slot
|
|
slot = INVEN_TYPE_RIGHT_HAND;
|
|
} else if (flags & ObjectFlag::Left_Hand) { // left hand slot
|
|
slot = INVEN_TYPE_LEFT_HAND;
|
|
}
|
|
result = InvenWieldHook_Script(critter, item, slot, 0, 0);
|
|
}
|
|
if (result) CorrectFidForRemovedItem(critter, item, flags);
|
|
return result;
|
|
}
|
|
|
|
// 4.x backport
|
|
static unsigned long previousGameMode = 0;
|
|
|
|
void __stdcall GameModeChangeHook(DWORD exit) {
|
|
BeginHook();
|
|
argCount = 2;
|
|
args[0] = exit;
|
|
args[1] = previousGameMode;
|
|
RunHookScript(HOOK_GAMEMODECHANGE);
|
|
EndHook();
|
|
|
|
previousGameMode = GetLoopFlags();
|
|
}
|
|
// END HOOKS
|
|
|
|
static void HookCommon_Reset() {
|
|
previousGameMode = 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
DWORD __stdcall GetHSArgAt(DWORD id) {
|
|
return args[id];
|
|
}
|
|
|
|
void __stdcall SetHSReturn(DWORD value) {
|
|
if (cRetTmp < maxRets) {
|
|
rets[cRetTmp++] = value;
|
|
}
|
|
if (cRetTmp > cRet) {
|
|
cRet = cRetTmp;
|
|
}
|
|
}
|
|
|
|
void __stdcall RegisterHook(TProgram* script, int id, int procNum, bool specReg) {
|
|
if (id >= numHooks || (id > HOOK_INVENWIELD && id < HOOK_GAMEMODECHANGE)) 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;
|
|
|
|
std::vector<sHookScript>::const_iterator c_it = hooks[id].cend();
|
|
if (specReg) {
|
|
c_it = hooks[id].cbegin();
|
|
hooksInfo[id].hsPosition++;
|
|
}
|
|
hooks[id].insert(c_it, 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);
|
|
|
|
if (DbAccess(filename)) {
|
|
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 HookScriptInit() {
|
|
dlogr("Loading hook scripts:", DL_HOOK|DL_INIT);
|
|
|
|
char* mask = "scripts\\hs_*.int";
|
|
char** filenames;
|
|
DbGetFileList(mask, &filenames);
|
|
|
|
LoadHookScript("hs_tohit", HOOK_TOHIT);
|
|
const DWORD toHitHkAddr[] = {
|
|
0x421686, // combat_safety_invalidate_weapon_func_
|
|
0x4231D9, // check_ranged_miss_
|
|
0x42331F, // shoot_along_path_
|
|
0x4237FC, // compute_attack_
|
|
0x424379, // determine_to_hit_
|
|
0x42438D, // determine_to_hit_no_range_
|
|
0x42439C, // determine_to_hit_from_tile_
|
|
0x42679A // combat_to_hit_
|
|
};
|
|
HookCalls(ToHitHook, toHitHkAddr);
|
|
|
|
LoadHookScript("hs_afterhitroll", HOOK_AFTERHITROLL);
|
|
MakeCall(0x423893, AfterHitRollHook);
|
|
|
|
LoadHookScript("hs_calcapcost", HOOK_CALCAPCOST);
|
|
const DWORD calcApCostHkAddr[] = {
|
|
0x42307A,
|
|
0x42669F,
|
|
0x42687B,
|
|
0x42A625,
|
|
0x42A655,
|
|
0x42A686,
|
|
0x42AE32,
|
|
0x42AE71,
|
|
0x460048,
|
|
0x47807B
|
|
};
|
|
HookCalls(CalcApCostHook, calcApCostHkAddr);
|
|
MakeCall(0x478083, CalcApCostHook2);
|
|
|
|
LoadHookScript("hs_deathanim1", HOOK_DEATHANIM1);
|
|
LoadHookScript("hs_deathanim2", HOOK_DEATHANIM2);
|
|
HookCall(0x4109DE, CalcDeathAnimHook); // show_damage_to_object_
|
|
const DWORD calcDeathAnim2HkAddr[] = {
|
|
0x410981, // show_damage_to_object_
|
|
0x4109A1, // show_damage_to_object_
|
|
0x4109BF // show_damage_to_object_
|
|
};
|
|
HookCalls(CalcDeathAnim2Hook, calcDeathAnim2HkAddr);
|
|
|
|
LoadHookScript("hs_combatdamage", HOOK_COMBATDAMAGE);
|
|
const DWORD computeDamageHkAddr[] = {
|
|
0x42326C, // check_ranged_miss()
|
|
0x4233E3, // shoot_along_path() - for extra burst targets
|
|
0x423AB7, // compute_attack()
|
|
0x423BBF, // compute_attack()
|
|
// 0x423DE7, // compute_explosion_on_extras()
|
|
0x423E69, // compute_explosion_on_extras()
|
|
0x424220, // attack_crit_failure()
|
|
0x4242FB // attack_crit_failure()
|
|
};
|
|
HookCalls(ComputeDamageHook, computeDamageHkAddr);
|
|
MakeCall(0x423DEB, ComputeDamageHook); // compute_explosion_on_extras() - for the attacker
|
|
|
|
LoadHookScript("hs_ondeath", HOOK_ONDEATH);
|
|
MakeCall(0x42DA6D, OnDeathHook); // critter_kill_
|
|
HookCall(0x425161, OnDeathHook2); // damage_object_
|
|
|
|
LoadHookScript("hs_findtarget", HOOK_FINDTARGET);
|
|
HookCall(0x429143, FindTargetHook);
|
|
|
|
LoadHookScript("hs_useobjon", HOOK_USEOBJON);
|
|
const DWORD useObjOnHkAddr[] = {0x49C606, 0x473619};
|
|
HookCalls(UseObjOnHook, useObjOnHkAddr);
|
|
// the following hooks allows to catch drug use of AI and from action cursor
|
|
const DWORD drugUseObjOnHkAddr[] = {
|
|
0x4285DF, // ai_check_drugs
|
|
0x4286F8, // ai_check_drugs
|
|
0x4287F8, // ai_check_drugs
|
|
0x473573 // inven_action_cursor
|
|
};
|
|
HookCalls(Drug_UseObjOnHook, drugUseObjOnHkAddr);
|
|
|
|
LoadHookScript("hs_removeinvenobj", HOOK_REMOVEINVENOBJ);
|
|
MakeJump(0x477492, RemoveObjHook); // old 0x477490
|
|
|
|
LoadHookScript("hs_barterprice", HOOK_BARTERPRICE);
|
|
const DWORD barterPriceHkAddr[] = {
|
|
0x474D4C, // barter_attempt_transaction_ (offers button)
|
|
0x475735, // display_table_inventories_ (for party members)
|
|
0x475762 // display_table_inventories_
|
|
};
|
|
HookCalls(BarterPriceHook, barterPriceHkAddr);
|
|
const DWORD pcBarterPriceHkAddr[] = {0x4754F4, 0X47551A}; // display_table_inventories_
|
|
HookCalls(PC_BarterPriceHook, pcBarterPriceHkAddr);
|
|
HookCall(0x474D3F, OverrideCost_BarterPriceHook); // barter_attempt_transaction_ (just overrides cost of offered goods)
|
|
|
|
LoadHookScript("hs_movecost", HOOK_MOVECOST);
|
|
const DWORD moveCostHkAddr[] = {0x417665, 0x44B88A};
|
|
HookCalls(MoveCostHook, moveCostHkAddr);
|
|
|
|
LoadHookScript("hs_hexmoveblocking", HOOK_HEXMOVEBLOCKING);
|
|
LoadHookScript("hs_hexaiblocking", HOOK_HEXAIBLOCKING);
|
|
LoadHookScript("hs_hexshootblocking", HOOK_HEXSHOOTBLOCKING);
|
|
LoadHookScript("hs_hexsightblocking", HOOK_HEXSIGHTBLOCKING);
|
|
SafeWrite32(0x413979, (DWORD)&HexSightBlockingHook);
|
|
const DWORD shootBlockingAddr[] = {0x4C1A88, 0x423178, 0x4232D4, 0x423B4D, 0x426CF8, 0x42A570};
|
|
SafeWriteBatch<DWORD>((DWORD)&HexShootBlockingHook, shootBlockingAddr);
|
|
SafeWrite32(0x42A0A4, (DWORD)&HexAIBlockingHook);
|
|
MakeJump(0x48B848, HexMoveBlockingHook);
|
|
|
|
LoadHookScript("hs_itemdamage", HOOK_ITEMDAMAGE);
|
|
HookCall(0x478560, ItemDamageHook);
|
|
|
|
LoadHookScript("hs_ammocost", HOOK_AMMOCOST);
|
|
HookCall(0x423A7C, AmmoCostHook);
|
|
|
|
LoadHookScript("hs_useobj", HOOK_USEOBJ);
|
|
const DWORD useObjHkAddr[] = {0x42AEBF, 0x473607, 0x49C12E};
|
|
HookCalls(UseObjHook, useObjHkAddr);
|
|
|
|
LoadHookScript("hs_keypress", HOOK_KEYPRESS);
|
|
LoadHookScript("hs_mouseclick", HOOK_MOUSECLICK);
|
|
|
|
LoadHookScript("hs_useskill", HOOK_USESKILL);
|
|
const DWORD useSkillHkAddr[] = {0x49C48F, 0x49D12E};
|
|
HookCalls(UseSkillHook, useSkillHkAddr);
|
|
|
|
LoadHookScript("hs_steal", HOOK_STEAL);
|
|
const DWORD stealCheckHkAddr[] = {0x4749A2, 0x474A69};
|
|
HookCalls(StealCheckHook, stealCheckHkAddr);
|
|
|
|
LoadHookScript("hs_withinperception", HOOK_WITHINPERCEPTION);
|
|
const DWORD perceptionRngHkAddr[] = {
|
|
0x42B4ED,
|
|
0x42BC87,
|
|
0x42BC9F,
|
|
0x42BD04
|
|
};
|
|
HookCalls(PerceptionRangeHook, perceptionRngHkAddr);
|
|
HookCall(0x429157, PerceptionSearchTargetHook);
|
|
HookCall(0x456BA2, PerceptionRangeSeeHook);
|
|
HookCall(0x458403, PerceptionRangeHearHook);
|
|
|
|
LoadHookScript("hs_inventorymove", HOOK_INVENTORYMOVE);
|
|
const DWORD switchHandHkAddr[] = {
|
|
0x4712E3, // left slot
|
|
0x47136D // right slot
|
|
};
|
|
HookCalls(SwitchHandHook, switchHandHkAddr);
|
|
MakeJump(0x4713A9, UseArmorHack); // old 0x4713A3
|
|
MakeJump(0x476491, DropIntoContainerHack);
|
|
MakeJump(0x471338, DropIntoContainerHandSlotHack);
|
|
MakeJump(0x4712AB, DropIntoContainerHandSlotHack);
|
|
HookCall(0x471200, MoveInventoryHook);
|
|
HookCall(0x476549, DropAmmoIntoWeaponHook); // old 0x476588
|
|
const DWORD actionCurObjDropHkAddr[] = {
|
|
0x473851, 0x47386F,
|
|
0x47379A // caps multi drop
|
|
};
|
|
HookCalls(InvenActionCursorObjDropHook, actionCurObjDropHkAddr);
|
|
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);
|
|
const DWORD invWieldFuncHkAddr[] = {
|
|
0x47275E, // inven_wield_
|
|
0x495FDF // partyMemberCopyLevelInfo_
|
|
};
|
|
HookCalls(InvenWieldFuncHook, invWieldFuncHkAddr);
|
|
const DWORD invUnwieldFuncHkAddr[] = {
|
|
0x45967D, // op_metarule_
|
|
0x472A5A, // inven_unwield_
|
|
0x495F0B // partyMemberCopyLevelInfo_
|
|
};
|
|
HookCalls(InvenUnwieldFuncHook, invUnwieldFuncHkAddr);
|
|
const DWORD fidRemovedItemHkAddr[] = {
|
|
0x45680C, // op_rm_obj_from_inven_
|
|
0x45C4EA // op_move_obj_inven_to_obj_
|
|
};
|
|
HookCalls(CorrectFidForRemovedItemHook, fidRemovedItemHkAddr);
|
|
HookCall(0x45C4F6, op_move_obj_inven_to_obj_hook);
|
|
MakeCall(0x4778AF, item_drop_all_hack, 3);
|
|
|
|
LoadHookScript("hs_gamemodechange", HOOK_GAMEMODECHANGE);
|
|
|
|
DbFreeFileList(&filenames, 0);
|
|
|
|
dlogr("Finished loading hook scripts.", DL_HOOK|DL_INIT);
|
|
}
|
|
|
|
void HookScriptClear() {
|
|
for (int i = 0; i < numHooks; i++) {
|
|
hooks[i].clear();
|
|
}
|
|
std::memset(hooksInfo, 0, numHooks * sizeof(HooksPositionInfo));
|
|
HookCommon_Reset();
|
|
}
|
|
|
|
void LoadHookScripts() {
|
|
isGlobalScriptLoading = 1; // this should allow to register global exported variables
|
|
HookScriptInit();
|
|
initingHookScripts = 1;
|
|
for (int i = 0; i < numHooks; i++) {
|
|
if (!hooks[i].empty()) {
|
|
hooksInfo[i].hasHsScript = true;
|
|
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 (hooksInfo[i].hasHsScript /*&& !hooks[i][hooksInfo[i].hsPosition].isGlobalScript*/) {
|
|
RunScriptProc(&hooks[i][hooksInfo[i].hsPosition].prog, procId); // run hs_*.int
|
|
}
|
|
}
|
|
}
|