add function based offset. rewrite hookparam processing while we're at it

This commit is contained in:
Akash Mozumdar 2018-10-11 12:58:30 -04:00
parent 3b9ca65e39
commit f7e3bbeb02
9 changed files with 149 additions and 166 deletions

View File

@ -234,7 +234,7 @@ namespace Host
WaitForSingleObject(pr.sectionMutex, 0);
const TextHook* hooks = (const TextHook*)pr.sectionMap;
for (int i = 0; i < MAX_HOOK; ++i)
if (hooks[i].hp.address == addr)
if (hooks[i].hp.insertion_address == addr)
ret = hooks[i].hp;
ReleaseMutex(pr.sectionMutex);
return ret;
@ -252,7 +252,7 @@ namespace Host
WaitForSingleObject(pr.sectionMutex, 0);
const TextHook* hooks = (const TextHook*)pr.sectionMap;
for (int i = 0; i < MAX_HOOK; ++i)
if (hooks[i].hp.address == addr)
if (hooks[i].hp.insertion_address == addr)
{
buffer.resize(hooks[i].name_length);
ReadProcessMemory(pr.processHandle, hooks[i].hook_name, buffer.data(), hooks[i].name_length, nullptr);

View File

@ -157,7 +157,7 @@ std::unordered_map<std::string, int64_t> MainWindow::GetInfoForExtensions(TextTh
QVector<HookParam> MainWindow::GetAllHooks(DWORD processId)
{
QSet<DWORD> addresses;
QSet<uint64_t> addresses;
QVector<HookParam> hooks;
for (int i = 0; i < ttCombo->count(); ++i)
{
@ -208,13 +208,13 @@ void MainWindow::on_unhookButton_clicked()
QStringList hookList;
for (auto hook : hooks)
hookList.push_back(
QString::fromStdWString(Host::GetHookName(GetSelectedProcessId(), hook.address)) +
QString::fromStdWString(Host::GetHookName(GetSelectedProcessId(), hook.insertion_address)) +
": " +
GenerateCode(hook, GetSelectedProcessId())
);
bool ok;
QString hook = QInputDialog::getItem(this, "Unhook", "Which hook to remove?", hookList, 0, false, &ok);
if (ok) Host::RemoveHook(GetSelectedProcessId(), hooks.at(hookList.indexOf(hook)).address);
if (ok) Host::RemoveHook(GetSelectedProcessId(), hooks.at(hookList.indexOf(hook)).insertion_address);
}
void MainWindow::on_saveButton_clicked()

View File

@ -1,7 +1,7 @@
#include "misc.h"
#include "const.h"
#include <QRegExp>
#include <Psapi.h>
#include <QTextStream>
QString GetFullModuleName(DWORD processId, HMODULE module)
{
@ -32,18 +32,12 @@ QMultiHash<QString, DWORD> GetAllProcesses()
namespace
{
DWORD Hash(QString module)
{
module = module.toLower();
DWORD hash = 0;
for (auto i : module) hash = _rotr(hash, 7) + i.unicode();
return hash;
}
std::optional<HookParam> ParseRCode(QString RCode)
{
HookParam hp = {};
hp.type |= DIRECT_READ;
// {S|Q|V}
switch (RCode.at(0).unicode())
{
case L'S':
@ -58,25 +52,32 @@ namespace
return {};
}
RCode.remove(0, 1);
if (RCode.at(0).unicode() == L'0') RCode.remove(0, 1);
QRegExp stringGap("^\\*(\\-?[\\dA-F]+)");
if (stringGap.indexIn(RCode) != -1)
// [*deref_offset|0]
if (RCode.at(0).unicode() == L'0') RCode.remove(0, 1); // Legacy
QRegularExpressionMatch deref = QRegularExpression("^\\*(\\-?[[:xdigit:]]+)").match(RCode);
if (deref.hasMatch())
{
hp.index = stringGap.cap(1).toInt(nullptr, 16);
RCode.remove(0, stringGap.cap(0).length());
hp.type |= DATA_INDIRECT;
hp.index = deref.captured(1).toInt(nullptr, 16);
RCode.remove(0, deref.captured(0).length());
}
if (RCode.at(0).unicode() != L'@') return {};
RCode.remove(0, 1);
QRegExp address("[\\dA-F]+$");
if (address.indexIn(RCode) == -1) return {};
hp.address = address.cap(0).toULongLong(nullptr, 16);
// @addr
QRegularExpressionMatch address = QRegularExpression("^@([[:xdigit:]]+)$").match(RCode);
if (!address.hasMatch()) return {};
hp.address = address.captured(1).toULongLong(nullptr, 16);
return hp;
}
std::optional<HookParam> ParseHCode(QString HCode)
{
HookParam hp = {};
// {A|B|W|S|Q|V}
switch (HCode.at(0).unicode())
{
case L'S':
@ -103,139 +104,141 @@ namespace
return {};
}
HCode.remove(0, 1);
// [N]
if (HCode.at(0).unicode() == L'N')
{
hp.type |= NO_CONTEXT;
HCode.remove(0, 1);
}
QRegExp dataOffset("^\\-?[\\dA-F]+");
if (dataOffset.indexIn(HCode) == -1) return {};
hp.offset = dataOffset.cap(0).toInt(nullptr, 16);
HCode.remove(0, dataOffset.cap(0).length());
QRegExp dataIndirect("^\\*(\\-?[\\dA-F]+)");
if (dataIndirect.indexIn(HCode) != -1)
// data_offset
QRegularExpressionMatch dataOffset = QRegularExpression("^\\-?[[:xdigit:]]+").match(HCode);
if (!dataOffset.hasMatch()) return {};
hp.offset = dataOffset.captured(0).toInt(nullptr, 16);
HCode.remove(0, dataOffset.captured(0).length());
// [*deref_offset1]
QRegularExpressionMatch deref1 = QRegularExpression("^\\*(\\-?[[:xdigit:]]+)").match(HCode);
if (deref1.hasMatch())
{
hp.type |= DATA_INDIRECT;
hp.index = dataIndirect.cap(1).toInt(nullptr, 16);
HCode.remove(0, dataIndirect.cap(0).length());
hp.index = deref1.captured(1).toInt(nullptr, 16);
HCode.remove(0, deref1.captured(0).length());
}
QRegExp split("^\\:(\\-?[\\dA-F]+)");
if (split.indexIn(HCode) != -1)
// [:split_offset[*deref_offset2]]
QRegularExpressionMatch splitOffset = QRegularExpression("^\\:(\\-?[[:xdigit:]]+)").match(HCode);
if (splitOffset.hasMatch())
{
hp.type |= USING_SPLIT;
hp.split = split.cap(1).toInt(nullptr, 16);
HCode.remove(0, split.cap(0).length());
QRegExp splitIndirect("^\\*(\\-?[\\dA-F]+)");
if (splitIndirect.indexIn(HCode) != -1)
hp.split = splitOffset.captured(1).toInt(nullptr, 16);
HCode.remove(0, splitOffset.captured(0).length());
QRegularExpressionMatch deref2 = QRegularExpression("^\\*(\\-?[[:xdigit:]]+)").match(HCode);
if (deref2.hasMatch())
{
hp.type |= SPLIT_INDIRECT;
hp.split_index = splitIndirect.cap(1).toInt(nullptr, 16);
HCode.remove(0, splitIndirect.cap(0).length());
hp.split_index = deref2.captured(1).toInt(nullptr, 16);
HCode.remove(0, deref2.captured(0).length());
}
}
if (HCode.at(0).unicode() != L'@') return {};
HCode.remove(0, 1);
QRegExp address("^([\\dA-F]+):?");
if (address.indexIn(HCode) == -1) return {};
hp.address = address.cap(1).toULongLong(nullptr, 16);
HCode.remove(address.cap(0));
if (HCode.length())
// @addr[:module[:func]]
QStringList addressPieces = HCode.split(":");
QRegularExpressionMatch address = QRegularExpression("^@([[:xdigit:]]+)$").match(addressPieces.at(0));
if (!address.hasMatch()) return {};
hp.address = address.captured(1).toULongLong(nullptr, 16);
if (addressPieces.size() > 1)
{
hp.type |= MODULE_OFFSET;
hp.module = Hash(HCode);
wcscpy_s<MAX_MODULE_SIZE>(hp.module, addressPieces.at(1).toStdWString().c_str());
}
if (hp.offset < 0)
hp.offset -= 4;
if (hp.split < 0)
hp.split -= 4;
if (addressPieces.size() > 2)
{
hp.type |= FUNCTION_OFFSET;
strcpy_s<MAX_MODULE_SIZE>(hp.function, addressPieces.at(2).toStdString().c_str());
}
// ITH has registers offset by 4 vs AGTH: need this to correct
if (hp.offset < 0) hp.offset -= 4;
if (hp.split < 0) hp.split -= 4;
return hp;
}
QString GenerateHCode(HookParam hp, DWORD processId)
{
QString code = "/H";
QString HCode = "/H";
QTextStream codeBuilder(&HCode);
codeBuilder.setIntegerBase(16);
codeBuilder.setNumberFlags(QTextStream::UppercaseDigits);
if (hp.type & USING_UNICODE)
{
if (hp.type & USING_STRING)
code += "Q";
else
code += "W";
if (hp.type & USING_STRING) codeBuilder << "Q";
else codeBuilder << "W";
}
else
{
if (hp.type & USING_UTF8)
code += "V";
else if (hp.type & USING_STRING)
code += "S";
else if (hp.type & BIG_ENDIAN)
code += "A";
else
code += "B";
if (hp.type & USING_UTF8) codeBuilder << "V";
else if (hp.type & USING_STRING) codeBuilder << "S";
else if (hp.type & BIG_ENDIAN) codeBuilder << "A";
else codeBuilder << "B";
}
if (hp.type & NO_CONTEXT)
code += "N";
if (hp.type & NO_CONTEXT) codeBuilder << "N";
if (hp.offset < 0) hp.offset += 4;
if (hp.split < 0) hp.split += 4;
if (hp.offset < 0)
code += "-" + QString::number(-hp.offset, 16);
else
code += QString::number(hp.offset, 16);
if (hp.type & DATA_INDIRECT)
codeBuilder << hp.offset;
if (hp.type & DATA_INDIRECT) codeBuilder << "*" << hp.index;
if (hp.type & USING_SPLIT) codeBuilder << ":" << hp.split;
if (hp.type & SPLIT_INDIRECT) codeBuilder << "*" << hp.split_index;
// Attempt to make the address relative
if (!(hp.type & MODULE_OFFSET))
{
if (hp.index < 0)
code += "*-" + QString::number(-hp.index, 16);
else
code += "*" + QString::number(hp.index, 16);
HANDLE processHandle;
if (!(processHandle = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, processId))) goto fin;
MEMORY_BASIC_INFORMATION info;
if (!VirtualQueryEx(processHandle, (LPCVOID)hp.address, &info, sizeof(info))) goto fin;
QString moduleName = GetModuleName(processId, (HMODULE)info.AllocationBase);
if (moduleName.size() == 0) goto fin;
hp.type |= MODULE_OFFSET;
hp.address -= (uint64_t)info.AllocationBase;
wcscpy_s<MAX_MODULE_SIZE>(hp.module, moduleName.toStdWString().c_str());
}
if (hp.type & USING_SPLIT)
{
if (hp.split < 0)
code += ":-" + QString::number(-hp.split, 16);
else
code += ":" + QString::number(hp.split, 16);
}
if (hp.type & SPLIT_INDIRECT)
{
if (hp.split_index < 0)
code += "*-" + QString::number(-hp.split_index, 16);
else
code += "*" + QString::number(hp.split_index, 16);
}
code += "@";
QString badCode = (code + QString::number(hp.address, 16)).toUpper();
HANDLE processHandle;
if (!(processHandle = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, processId))) return badCode;
MEMORY_BASIC_INFORMATION info;
if (!VirtualQueryEx(processHandle, (LPCVOID)hp.address, &info, sizeof(info))) return badCode;
QString moduleName = GetModuleName(processId, (HMODULE)info.AllocationBase);
if (moduleName.size() == 0) return badCode;
code += QString::number(hp.address - (DWORD)info.AllocationBase, 16) + ":";
code = code.toUpper();
code += moduleName;
return code;
fin:
codeBuilder << "@" << hp.address;
if (hp.type & MODULE_OFFSET) codeBuilder << ":" << QString::fromWCharArray(hp.module);
if (hp.type & FUNCTION_OFFSET) codeBuilder << ":" << hp.function;
return HCode;
}
QString GenerateRCode(HookParam hp)
{
QString code = "/R";
if (hp.type & USING_UNICODE)
code += "Q";
else if (hp.type & USING_UTF8)
code += "V";
else
code += "S";
if (hp.type & DATA_INDIRECT)
code += "*" + QString::number(hp.index, 16);
//code += QString::number(hp.offset, 16);
code += "@";
code += QString::number(hp.address, 16);
return code.toUpper();
QString RCode = "/R";
QTextStream codeBuilder(&RCode);
codeBuilder.setIntegerBase(16);
codeBuilder.setNumberFlags(QTextStream::UppercaseDigits);
if (hp.type & USING_UNICODE) codeBuilder << "Q";
else if (hp.type & USING_UTF8) codeBuilder << "V";
else codeBuilder << "S";
if (hp.type & DATA_INDIRECT) codeBuilder << "*" << hp.index;
codeBuilder << "@" << hp.address;
return RCode;
}
}
std::optional<HookParam> ParseCode(QString code)
{
code = code.toUpper();
if (code.startsWith("/H")) return ParseHCode(code.remove(0, 2));
else if (code.startsWith("/R")) return ParseRCode(code.remove(0, 2));
else return {};

View File

@ -13,7 +13,7 @@ QString GenerateCode(HookParam hp, DWORD processId);
static QString CodeInfoDump =
"Enter hook code\r\n\
/H{A|B|W|S|Q|V}[N]data_offset[*deref_offset1][:split_offset[*deref_offset2]]@addr[:module]\r\n\
/H{A|B|W|S|Q|V}[N]data_offset[*deref_offset1][:split_offset[*deref_offset2]]@addr[:module[:func]]\r\n\
OR\r\n\
Enter read code\r\n\
/R{S|Q|V}[*deref_offset|0]@addr\r\n\

View File

@ -4,7 +4,7 @@
// 8/23/2013 jichi
// Branch: ITH/common.h, rev 128
enum { MESSAGE_SIZE = 500, PIPE_BUFFER_SIZE = 0x1000, SHIFT_JIS = 932, MAX_THREAD_COUNT };
enum { MESSAGE_SIZE = 500, PIPE_BUFFER_SIZE = 0x1000, SHIFT_JIS = 932, MAX_THREAD_COUNT = 250, MAX_MODULE_SIZE = 120 };
// jichi 375/2014: Add offset of pusha/pushad
// http://faydoc.tripod.com/cpu/pushad.htm
@ -50,8 +50,8 @@ enum HookParamType : unsigned long
DATA_INDIRECT = 0x8,
USING_SPLIT = 0x10, // aware of split time?
SPLIT_INDIRECT = 0x20,
MODULE_OFFSET = 0x40, // do hash module, and the address is relative to module
//FUNCTION_OFFSET = 0x80, // do hash function, and the address is relative to funccion
MODULE_OFFSET = 0x40, // address is relative to module
FUNCTION_OFFSET = 0x80, // address is relative to function
USING_UTF8 = 0x100,
NO_CONTEXT = 0x400,
HOOK_EMPTY = 0x800,

View File

@ -11,12 +11,14 @@ struct HookParam
typedef bool(*filter_fun_t)(LPVOID str, DWORD *len, HookParam *hp, BYTE index); // jichi 10/24/2014: Add filter function. Return true if skip the text
typedef bool(*hook_fun_t)(DWORD esp, HookParam *hp); // jichi 10/24/2014: Add generic hook function, return false if stop execution.
uint64_t address; // absolute or relative address
uint64_t insertion_address; // absolute address
uint64_t address; // absolute or relative address (not changed by TextHook)
int offset, // offset of the data in the memory
index, // deref_offset1
split, // offset of the split character
split_index; // deref_offset2
DWORD module; // hash of the module
wchar_t module[MAX_MODULE_SIZE];
char function[MAX_MODULE_SIZE];
DWORD type; // flags
WORD length_offset; // index of the string length
DWORD user_value; // 7/20/2014: jichi additional parameters for PSP games
@ -24,8 +26,6 @@ struct HookParam
text_fun_t text_fun;
filter_fun_t filter_fun;
hook_fun_t hook_fun;
HANDLE readerHandle; // Artikash 8/4/2018: handle for reader thread
};
struct ThreadParam // From hook, used internally by host as well

View File

@ -40,29 +40,6 @@ namespace { // unnamed
0xe9 // jmp @original
};
DWORD Hash(std::wstring module)
{
DWORD hash = 0;
for (auto i : module) hash = _rotr(hash, 7) + i;
return hash;
}
//copy original instruction
//jmp back
DWORD GetModuleBase(DWORD hash)
{
HMODULE allModules[1000];
DWORD size;
EnumProcessModules(GetCurrentProcess(), allModules, sizeof(allModules), &size);
wchar_t name[MAX_PATH];
for (int i = 0; i < size / sizeof(HMODULE); ++i)
{
GetModuleFileNameW(allModules[i], name, MAX_PATH);
_wcslwr(name);
if (Hash(wcsrchr(name, L'\\') + 1) == hash) return (DWORD)allModules[i];
}
return 0;
}
__declspec(naked) // jichi 10/2/2013: No prolog and epilog
int ProcessHook(DWORD dwDataBase, DWORD dwRetn, TextHook *hook) // Use SEH to ensure normal execution even bad hook inserted.
@ -76,6 +53,7 @@ namespace { // unnamed
retn // jichi 12/13/2013: return near, see: http://stackoverflow.com/questions/1396909/ret-retn-retf-how-to-use-them
}
}
#else
const BYTE common_hook[] = {
0x9c, // push rflags
@ -160,7 +138,7 @@ DWORD TextHook::UnsafeSend(DWORD dwDataBase, DWORD dwRetn)
BYTE pbData[PIPE_BUFFER_SIZE];
DWORD dwType = hp.type;
dwAddr = hp.address;
dwAddr = hp.insertion_address;
/** jichi 12/24/2014
* @param addr function address
@ -254,19 +232,19 @@ bool TextHook::InsertHookCode()
bool TextHook::UnsafeInsertHookCode()
{
if (hp.module && (hp.type & MODULE_OFFSET)) // Map hook offset to real address.
{
if (DWORD base = GetModuleBase(hp.module)) hp.address += base;
if (hp.type & MODULE_OFFSET) // Map hook offset to real address.
if (hp.type & FUNCTION_OFFSET)
if (FARPROC function = GetProcAddress(GetModuleHandleW(hp.module), hp.function)) hp.insertion_address += (uint64_t)function;
else return ConsoleOutput("Textractor: UnsafeInsertHookCode: FAILED: function not present"), false;
else if (HMODULE moduleBase = GetModuleHandleW(hp.module)) hp.insertion_address += (uint64_t)moduleBase;
else return ConsoleOutput("Textractor: UnsafeInsertHookCode: FAILED: module not present"), false;
hp.type &= ~MODULE_OFFSET;
}
BYTE* original;
insert:
if (MH_STATUS err = MH_CreateHook((void*)hp.address, (void*)trampoline, (void**)&original))
if (MH_STATUS err = MH_CreateHook((void*)hp.insertion_address, (void*)trampoline, (void**)&original))
if (err == MH_ERROR_ALREADY_CREATED)
{
RemoveHook(hp.address);
RemoveHook(hp.insertion_address);
goto insert; // FIXME: i'm too lazy to do this properly right now...
}
else
@ -293,7 +271,7 @@ bool TextHook::UnsafeInsertHookCode()
//memcpy(trampoline + 46, &sendPtr, sizeof(sendPtr));
//memcpy(trampoline + sizeof(common_hook) - 8, &original, sizeof(void*));
if (MH_EnableHook((void*)hp.address) != MH_OK) return false;
if (MH_EnableHook((void*)hp.insertion_address) != MH_OK) return false;
return true;
}
@ -305,15 +283,15 @@ DWORD WINAPI ReaderThread(LPVOID hookPtr)
BYTE buffer[PIPE_BUFFER_SIZE] = {};
unsigned int changeCount = 0;
int dataLen = 0;
const void* currentAddress = (void*)hook->hp.address;
const void* currentAddress = (void*)hook->hp.insertion_address;
while (true)
{
if (!IthGetMemoryRange((void*)hook->hp.address, nullptr, nullptr))
if (!IthGetMemoryRange((void*)hook->hp.insertion_address, nullptr, nullptr))
{
ConsoleOutput("Textractor: can't read desired address");
break;
}
if (hook->hp.type & DATA_INDIRECT) currentAddress = *((char**)hook->hp.address + hook->hp.index);
if (hook->hp.type & DATA_INDIRECT) currentAddress = *((char**)hook->hp.insertion_address + hook->hp.index);
if (!IthGetMemoryRange(currentAddress, nullptr, nullptr))
{
ConsoleOutput("Textractor: can't read desired address");
@ -336,7 +314,7 @@ DWORD WINAPI ReaderThread(LPVOID hookPtr)
else
dataLen = strlen((const char*)currentAddress);
*(ThreadParam*)buffer = { GetCurrentProcessId(), hook->hp.address, 0, 0 };
*(ThreadParam*)buffer = { GetCurrentProcessId(), hook->hp.insertion_address, 0, 0 };
memcpy(buffer + sizeof(ThreadParam), currentAddress, dataLen + 1);
DWORD unused;
WriteFile(::hookPipe, buffer, dataLen + sizeof(ThreadParam), &unused, nullptr);
@ -349,7 +327,7 @@ DWORD WINAPI ReaderThread(LPVOID hookPtr)
bool TextHook::InsertReadCode()
{
//RemoveHook(hp.address); // Artikash 8/25/2018: clear existing
hp.readerHandle = CreateThread(nullptr, 0, ReaderThread, this, 0, nullptr);
readerHandle = CreateThread(nullptr, 0, ReaderThread, this, 0, nullptr);
return true;
}
@ -357,6 +335,7 @@ void TextHook::InitHook(const HookParam &h, LPCSTR name, WORD set_flag)
{
WaitForSingleObject(hmMutex, 0);
hp = h;
hp.insertion_address = hp.address;
hp.type |= set_flag;
if (name && name != hook_name) SetHookName(name);
ReleaseMutex(hmMutex);
@ -364,14 +343,14 @@ void TextHook::InitHook(const HookParam &h, LPCSTR name, WORD set_flag)
void TextHook::RemoveHookCode()
{
MH_DisableHook((void*)hp.address);
MH_RemoveHook((void*)hp.address);
MH_DisableHook((void*)hp.insertion_address);
MH_RemoveHook((void*)hp.insertion_address);
}
void TextHook::RemoveReadCode()
{
TerminateThread(hp.readerHandle, 0);
CloseHandle(hp.readerHandle);
TerminateThread(readerHandle, 0);
CloseHandle(readerHandle);
}
void TextHook::ClearHook()
@ -380,7 +359,7 @@ void TextHook::ClearHook()
if (hook_name) ConsoleOutput(("Textractor: removing hook: " + std::string(hook_name)).c_str());
if (hp.type & DIRECT_READ) RemoveReadCode();
else RemoveHookCode();
NotifyHookRemove(hp.address);
NotifyHookRemove(hp.insertion_address);
if (hook_name) delete[] hook_name;
memset(this, 0, sizeof(TextHook)); // jichi 11/30/2013: This is the original code of ITH
ReleaseMutex(hmMutex);

View File

@ -30,6 +30,7 @@ public:
LPSTR hook_name;
int name_length;
BYTE trampoline[120];
HANDLE readerHandle;
bool InsertHook();
void InitHook(const HookParam &hp, LPCSTR name = 0, WORD set_flag = 0);

View File

@ -49,7 +49,7 @@ BOOL WINAPI DllMain(HINSTANCE hModule, DWORD fdwReason, LPVOID)
{
::running = false;
MH_Uninitialize();
for (TextHook *man = ::hookman; man < ::hookman + MAX_HOOK; man++) if (man->hp.address) man->ClearHook();
for (TextHook *man = ::hookman; man < ::hookman + MAX_HOOK; man++) if (man->hp.insertion_address) man->ClearHook();
//if (ith_has_section)
UnmapViewOfFile(::hookman);
@ -83,7 +83,7 @@ void NewHook(const HookParam &hp, LPCSTR lpname, DWORD flag)
void RemoveHook(uint64_t addr)
{
for (int i = 0; i < MAX_HOOK; i++)
if (abs((long long)(::hookman[i].hp.address - addr)) < 9)
if (abs((long long)(::hookman[i].hp.insertion_address - addr)) < 9)
{
::hookman[i].ClearHook();
return;