2024-09-08 15:31:11 +08:00

673 lines
21 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "Elf.h"
/**
* jichi 6/1/2014:
* Observations from 愛姉妹4
* - Scenario: arg1 + 4*5 is 0, arg1+0xc is address of the text
* - Character: arg1 + 4*10 is 0, arg1+0xc is text
*/
static inline size_t _elf_strlen(LPCSTR p) // limit search address which might be bad
{
// CC_ASSERT(p);
for (size_t i = 0; i < VNR_TEXT_CAPACITY; i++)
if (!*p++)
return i;
return 0; // when len >= VNR_TEXT_CAPACITY
}
static void SpecialHookElf(hook_stack *stack, HookParam *, uintptr_t *data, uintptr_t *split, size_t *len)
{
// DWORD arg1 = *(DWORD *)(esp_base + 0x4);
DWORD arg1 = stack->stack[1];
DWORD arg2_scene = arg1 + 4 * 5,
arg2_chara = arg1 + 4 * 10;
DWORD text; //= 0; // This variable will be killed
if (*(DWORD *)arg2_scene == 0)
{
text = *(DWORD *)(arg2_scene + 4 * 3);
if (!text || ::IsBadReadPtr((LPCVOID)text, 1)) // Text from scenario could be bad when open backlog while the character is speaking
return;
*split = 1;
}
else if (*(DWORD *)arg2_chara == 0)
{
text = arg2_chara + 4 * 3;
*split = 2;
}
else
return;
// if (text && text < MemDbg::UserMemoryStopAddress) {
*len = _elf_strlen((LPCSTR)text); // in case the text is bad but still readable
//*len = ::strlen((LPCSTR)text);
*data = text;
}
/**
* jichi 5/31/2014: elf's
* Type1: SEXヂ<58>ーチャー剛史 trial, reladdr = 0x2f0f0, 2 parameters
* Type2: 愛姉妹4, reladdr = 0x2f9b0, 3 parameters
*
* IDA: sub_42F9B0 proc near ; bp-based frame
* var_8 = dword ptr -8
* var_4 = byte ptr -4
* var_3 = word ptr -3
* arg_0 = dword ptr 8
* arg_4 = dword ptr 0Ch
* arg_8 = dword ptr 10h
*
* Call graph (Type2):
* 0x2f9b0 ; hook here
* > 0x666a0 ; called multiple time
* > TextOutA ; there are two TextOutA, the second is the right one
*
* Function starts (Type1), pattern offset: 0xc
* - 012ef0f0 /$ 55 push ebp ; jichi: hook
* - 012ef0f1 |. 8bec mov ebp,esp
* - 012ef0f3 |. 83ec 10 sub esp,0x10
* - 012ef0f6 |. 837d 0c 00 cmp dword ptr ss:[ebp+0xc],0x0
* - 012ef0fa |. 53 push ebx
* - 012ef0fb |. 56 push esi
* - 012ef0fc |. 75 0f jnz short stt_tria.012ef10d ; jicchi: pattern starts
* - 012ef0fe |. 8b45 08 mov eax,dword ptr ss:[ebp+0x8]
* - 012ef101 |. 8b48 04 mov ecx,dword ptr ds:[eax+0x4]
* - 012ef104 |. 8b91 90000000 mov edx,dword ptr ds:[ecx+0x90] ; jichi: pattern stops
* - 012ef10a |. 8955 0c mov dword ptr ss:[ebp+0xc],edx
* - 012ef10d |> 8b4d 08 mov ecx,dword ptr ss:[ebp+0x8]
* - 012ef110 |. 8b51 04 mov edx,dword ptr ds:[ecx+0x4]
* - 012ef113 |. 33c0 xor eax,eax
* - 012ef115 |. c645 f8 00 mov byte ptr ss:[ebp-0x8],0x0
* - 012ef119 |. 66:8945 f9 mov word ptr ss:[ebp-0x7],ax
* - 012ef11d |. 8b82 b0000000 mov eax,dword ptr ds:[edx+0xb0]
* - 012ef123 |. 8945 f4 mov dword ptr ss:[ebp-0xc],eax
* - 012ef126 |. 33db xor ebx,ebx
* - 012ef128 |> 8b4f 20 /mov ecx,dword ptr ds:[edi+0x20]
* - 012ef12b |. 83f9 10 |cmp ecx,0x10
*
* Function starts (Type2), pattern offset: 0x10
* - 0093f9b0 /$ 55 push ebp ; jichi: hook here
* - 0093f9b1 |. 8bec mov ebp,esp
* - 0093f9b3 |. 83ec 08 sub esp,0x8
* - 0093f9b6 |. 837d 10 00 cmp dword ptr ss:[ebp+0x10],0x0
* - 0093f9ba |. 53 push ebx
* - 0093f9bb |. 8b5d 0c mov ebx,dword ptr ss:[ebp+0xc]
* - 0093f9be |. 56 push esi
* - 0093f9bf |. 57 push edi
* - 0093f9c0 |. 75 0f jnz short silkys.0093f9d1 ; jichi: pattern starts
* - 0093f9c2 |. 8b45 08 mov eax,dword ptr ss:[ebp+0x8]
* - 0093f9c5 |. 8b48 04 mov ecx,dword ptr ds:[eax+0x4]
* - 0093f9c8 |. 8b91 90000000 mov edx,dword ptr ds:[ecx+0x90] ; jichi: pattern stops
* - 0093f9ce |. 8955 10 mov dword ptr ss:[ebp+0x10],edx
* - 0093f9d1 |> 33c0 xor eax,eax
* - 0093f9d3 |. c645 fc 00 mov byte ptr ss:[ebp-0x4],0x0
* - 0093f9d7 |. 66:8945 fd mov word ptr ss:[ebp-0x3],ax
* - 0093f9db |. 33ff xor edi,edi
* - 0093f9dd |> 8b53 20 /mov edx,dword ptr ds:[ebx+0x20]
* - 0093f9e0 |. 8d4b 0c |lea ecx,dword ptr ds:[ebx+0xc]
* - 0093f9e3 |. 83fa 10 |cmp edx,0x10
*/
bool InsertElfHook()
{
const BYTE bytes[] = {
// 0x55, // 0093f9b0 /$ 55 push ebp ; jichi: hook here
// 0x8b,0xec, // 0093f9b1 |. 8bec mov ebp,esp
// 0x83,0xec, 0x08, // 0093f9b3 |. 83ec 08 sub esp,0x8
// 0x83,0x7d, 0x10, 0x00, // 0093f9b6 |. 837d 10 00 cmp dword ptr ss:[ebp+0x10],0x0
// 0x53, // 0093f9ba |. 53 push ebx
// 0x8b,0x5d, 0x0c, // 0093f9bb |. 8b5d 0c mov ebx,dword ptr ss:[ebp+0xc]
// 0x56, // 0093f9be |. 56 push esi
// 0x57, // 0093f9bf |. 57 push edi
0x75, 0x0f, // 0093f9c0 |. 75 0f jnz short silkys.0093f9d1
0x8b, 0x45, 0x08, // 0093f9c2 |. 8b45 08 mov eax,dword ptr ss:[ebp+0x8]
0x8b, 0x48, 0x04, // 0093f9c5 |. 8b48 04 mov ecx,dword ptr ds:[eax+0x4]
0x8b, 0x91, 0x90, 0x00, 0x00, 0x00 // 0093f9c8 |. 8b91 90000000 mov edx,dword ptr ds:[ecx+0x90]
};
// enum { addr_offset = 0xc };
ULONG range = min(processStopAddress - processStartAddress, MAX_REL_ADDR);
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStartAddress + range);
// GROWL_DWORD(addr);
// addr = 0x42f170; // 愛姉妹4 Trial
// reladdr = 0x2f9b0; // 愛姉妹4
// reladdr = 0x2f0f0; // SEXヂ<58>ーチャー剛史 trial
if (!addr)
{
ConsoleOutput("Elf: pattern not found");
return false;
}
enum : BYTE
{
push_ebp = 0x55
};
for (int i = 0; i < 0x20; i++, addr--) // value of i is supposed to be 0xc or 0x10
if (*(BYTE *)addr == push_ebp)
{ // beginning of the function
HookParam hp;
hp.address = addr;
hp.text_fun = SpecialHookElf;
hp.type = USING_STRING | NO_CONTEXT; // = 9
ConsoleOutput("INSERT Elf");
return NewHook(hp, "Elf");
}
ConsoleOutput("Elf: function not found");
return false;
}
namespace
{
bool __()
{
const BYTE bytes[] = {
// 姫騎士オリヴィア ~へ、変態、この変態男!少しは恥を知りなさい!
// 女系家族III秘密HIMITSU卑蜜
// ベロちゅー!~コスプレメイドをエロメロしちゃう魔法の舌戯~
0x0F, 0xB7, XX, XX4, // v11 == 30081 // movzx edx, ds:word_4C285C //word_4C285C dw 7581h
};
for (auto addr : Util::SearchMemory(bytes, sizeof(bytes), PAGE_EXECUTE, processStartAddress, processStopAddress))
{
BYTE reg = *(BYTE *)(addr + 2);
if ((reg != 0x05) && (reg != 0x0d) && (reg != 0x1d) && (reg != 0x15))
continue;
int word_4C285C_addr = *(int *)(addr + 3);
if (word_4C285C_addr < processStartAddress || word_4C285C_addr > processStopAddress)
continue;
int word_4C285C = *(int *)word_4C285C_addr;
if ((word_4C285C) != 0x7581)
continue;
addr = findfuncstart(addr, 0x200);
if (addr == 0)
continue;
HookParam hp;
hp.address = addr;
hp.offset = get_stack(1);
hp.type = USING_STRING;
return NewHook(hp, "aiwin6");
}
return false;
}
}
namespace
{ // unnamed
namespace ScenarioHook
{
namespace Private
{
struct TextArgument
{
DWORD _unknown1[5];
DWORD scenarioFlag; // +4*5, 0 if it is scenario
DWORD _unknown2[2];
LPCSTR scenarioText; // +4*5+4*3, could be bad address though
DWORD _unknown3;
DWORD nameFlag; // +4*10, 0 if it is name
DWORD _unknown4[2];
char nameText[1]; // +4*10+4*3, could be bad address though
};
std::string data_;
TextArgument *scenarioArg_,
*nameArg_;
LPCSTR scenarioText_;
enum
{
MaxNameSize = 100
};
char nameText_[MaxNameSize + 1];
bool hookBefore(hook_stack *s, void *data, size_t *len, uintptr_t *role)
{
auto arg = (TextArgument *)s->stack[0]; // arg1 on the top of the stack
// Scenario
if (arg->scenarioFlag == 0)
{
*role = Engine::ScenarioRole;
// Text from scenario could be bad when open backlog while the character is speaking
auto text = arg->scenarioText;
if (!Engine::isAddressReadable(text))
return 0;
return write_string_overwrite(data, len, text);
// data_ = q->dispatchTextASTD(text, role, sig);
// scenarioArg_ = arg;
// scenarioText_ = arg->scenarioText;
// arg->scenarioText = (LPCSTR)data_.c_str();
}
else if (arg->nameFlag == 0)
{
*role = Engine::NameRole;
auto text = arg->nameText;
return write_string_overwrite(data, len, text);
// ::memcpy(text, newData.constData(), qMin(oldData.size(), newData.size()));
// int left = oldData.size() - newData.size();
// if (left > 0)
// ::memset(text + oldData.size() - left, 0, left);
}
return 0;
}
void hookafter1(hook_stack *s, void *data1, size_t len)
{
auto newData = std::string((char *)data1, len);
auto arg = (TextArgument *)s->stack[0]; // arg1 on the top of the stack
// Scenario
if (arg->scenarioFlag == 0)
{
auto text = arg->scenarioText;
if (!Engine::isAddressReadable(text))
return;
data_ = newData;
scenarioArg_ = arg;
scenarioText_ = arg->scenarioText;
arg->scenarioText = (LPCSTR)data_.c_str();
}
else if (arg->nameFlag == 0)
{
auto text = arg->nameText;
std::string oldData = text;
::memcpy(text, newData.c_str(), min(oldData.size(), newData.size()));
int left = oldData.size() - newData.size();
if (left > 0)
::memset(text + oldData.size() - left, 0, left);
}
}
bool hookAfter(hook_stack *s, void *data, size_t *len, uintptr_t *role)
{
if (scenarioArg_)
{
scenarioArg_->scenarioText = scenarioText_;
scenarioArg_ = nullptr;
}
if (nameArg_)
{
::strcpy(nameArg_->nameText, nameText_);
nameArg_ = nullptr;
}
return 0;
}
} // namespace Private
/**
* jichi 5/31/2014: elf's
* Type1: SEXティーチャー剛史 trial, reladdr = 0x2f0f0, 2 parameters
* Type2: 愛姉妹4, reladdr = 0x2f9b0, 3 parameters
*
* The hooked function is the caller of the caller of TextOutA.
*/
bool attach(ULONG startAddress, ULONG stopAddress)
{
const uint8_t bytes[] = {
// 0x55, // 0093f9b0 /$ 55 push ebp ; jichi: hook here
// 0x8b,0xec, // 0093f9b1 |. 8bec mov ebp,esp
// 0x83,0xec, 0x08, // 0093f9b3 |. 83ec 08 sub esp,0x8
// 0x83,0x7d, 0x10, 0x00, // 0093f9b6 |. 837d 10 00 cmp dword ptr ss:[ebp+0x10],0x0
// 0x53, // 0093f9ba |. 53 push ebx
// 0x8b,0x5d, 0x0c, // 0093f9bb |. 8b5d 0c mov ebx,dword ptr ss:[ebp+0xc]
// 0x56, // 0093f9be |. 56 push esi
// 0x57, // 0093f9bf |. 57 push edi
0x75, 0x0f, // 0093f9c0 |. 75 0f jnz short silkys.0093f9d1
0x8b, 0x45, 0x08, // 0093f9c2 |. 8b45 08 mov eax,dword ptr ss:[ebp+0x8]
0x8b, 0x48, 0x04, // 0093f9c5 |. 8b48 04 mov ecx,dword ptr ds:[eax+0x4]
0x8b, 0x91, 0x90, 0x00, 0x00, 0x00 // 0093f9c8 |. 8b91 90000000 mov edx,dword ptr ds:[ecx+0x90]
};
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), startAddress, stopAddress);
if (!addr)
return false;
addr = MemDbg::findEnclosingAlignedFunction(addr);
if (!addr)
return false;
int count = 0;
auto fun = [&count](ULONG addr) -> bool
{
bool succ = false;
HookParam hp;
hp.address = addr;
hp.hook_before = Private::hookBefore;
hp.hook_after = Private::hookafter1;
hp.type = USING_STRING | EMBED_ABLE | EMBED_DYNA_SJIS;
hp.hook_font = F_TextOutA;
succ |= NewHook(hp, "EmbedElf");
hp.address = addr + 5;
hp.hook_before = Private::hookAfter;
succ |= NewHook(hp, "EmbedElf");
count += 1;
return succ; // replace all functions
};
MemDbg::iterNearCallAddress(fun, addr, startAddress, stopAddress);
return count;
// lastCaller = MemDbg::findEnclosingAlignedFunction(lastCaller);
// Private::attached_ = false;
// return winhook::hook_before(lastCaller, [=](winhook::hook_stack *s) -> bool {
// if (Private::attached_)
// return true;
// Private::attached_ = true;
// if (ULONG addr = MemDbg::findEnclosingAlignedFunction(s->stack[0])) {
// DOUT("dynamic pattern found");
// Private::oldHookFun = (Private::hook_fun_t)winhook::replace_fun(addr, (ULONG)Private::newHookFun);
// }
// return true;
// });
}
} // namespace ScenarioHook
} // unnamed namespace
namespace
{
// flutter of birds鳥達の羽ばたき
// https://vndb.org/v2379
// 需要注意的是不能把文本跳到最快不然2~4行无法显示。
// 这个有一大堆候选
bool elf3()
{
bool succ = false;
BYTE sig[] = {
0x83, XX, 0x14, 0x10,
0x72, XX};
for (auto addr : Util::SearchMemory(sig, sizeof(sig), PAGE_EXECUTE, processStartAddress, processStopAddress))
{
auto check1 = *(BYTE *)(addr + 5);
if (check1 != 0x02 && check1 != 0x04)
continue;
auto check = *(BYTE *)(addr + 1);
HookParam hp;
hp.address = addr;
hp.user_value = check;
hp.type = USING_STRING | NO_CONTEXT;
hp.text_fun = [](hook_stack *stack, HookParam *hp, uintptr_t *data, uintptr_t *split, size_t *len)
{
DWORD ptr;
switch (hp->user_value)
{
case 0x7a:
ptr = stack->edx;
break;
case 0x7b:
ptr = stack->ebx;
break;
case 0x79:
ptr = stack->ecx;
break;
case 0x78:
ptr = stack->eax;
break;
case 0x7e:
ptr = stack->esi;
break;
case 0x7f:
ptr = stack->edi;
break;
case 0x7d:
ptr = stack->ebp;
break;
// esp:
// 83 7c 24 14 10
default:
hp->type = HOOK_EMPTY;
break;
}
auto text = (TextUnionA *)ptr;
*data = (DWORD)text->getText();
*len = text->size;
};
hp.filter_fun = all_ascii_Filter;
succ |= NewHook(hp, "elf3");
}
return succ;
}
}
namespace
{
bool elf4()
{
// WORDS WORTH【Windows10対応】
// elf3只能拿到人名跳过
uint8_t bytes[] = {
//clang-format off
0x72, 0x02,
0x8b, 0x36,
0x8a, 0x0e,
0x84, 0xc9,
0x0f, 0x84, XX4,
0x8d, 0x57, XX,
0x8d, 0x5f, XX,
0x8b, 0xff,
0x80, 0xf9, 0x81,
0x72, 0x05,
0x80, 0xf9, 0x9f,
0x76, 0x07,
0x8d, 0x41, 0x20,
0x3c, 0x0f,
0x77, XX,
//clang-format on
};
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress);
if (!addr)
return false;
HookParam hp;
hp.address = addr + 4;
hp.type = USING_STRING;
hp.offset = get_reg(regs::esi);
return NewHook(hp, "Elf4");
}
}
bool Elf::attach_function()
{
auto _1 = InsertElfHook() || __() || elf4() || elf3();
return ScenarioHook::attach(processStartAddress, processStopAddress) || _1;
}
bool isshiftjisX(WORD w)
{
return (((BYTE)(w)) <= 0xfc) && (((BYTE)(w)) >= 0x80);
}
void SpecialHookElf2(hook_stack *stack, HookParam *, uintptr_t *data, uintptr_t *split, size_t *len)
{
static DWORD lasttext;
DWORD eax = stack->eax;
DWORD edx = stack->edx;
*data = *(WORD *)(eax + edx);
if (isshiftjisX(*data) == false)
{
*len = 0;
return;
}
*len = 2;
*split = stack->stack[1];
}
bool Elf2attach_function()
{
// 这个有好多乱码
//[エルフ]あしたの雪之丞 DVD Special Edition
const uint8_t bytes[] = {
0x53,
0x8a, 0x1c, 0x02,
0x8b, 0x54, 0x24, 0x08,
0x03, 0xc2};
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress);
if (!addr)
return false;
HookParam hp;
hp.address = addr + 1;
hp.text_fun = SpecialHookElf2;
hp.type = NO_CONTEXT;
return NewHook(hp, "Elf");
}
bool elf2()
{
//[エルフ]あしたの雪之丞 DVD Special Edition
// 勝 あしたの雪之丞2
const uint8_t bytes[] = {
0x66, 0x8b, 0x8e, XX4,
0x66, 0x8b, 0x96, XX4,
0x66, 0x01, 0x8e, XX4,
0x66, 0x89, 0x96, XX4,
0x8b, 0x06,
0x6a, 0x00,
0x8b, 0xce,
0xff, 0x50, 0x08,
0x84, 0xc0};
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress);
if (!addr)
return false;
HookParam hp;
hp.address = addr + sizeof(bytes);
hp.type = NO_CONTEXT | USING_STRING;
hp.offset = get_reg(regs::ebx);
return NewHook(hp, "Elf");
}
namespace
{
// リフレインブルー【Windows10対応】
bool _h1()
{
// HAN-18*-4@42E12:AI5WIN.exe
BYTE sig[] = {
0x33, 0xff,
0x8b, 0x06,
0x8b, 0xce,
0x6a, 0x01,
0x8b, 0x40, 0x08,
0xff, 0xd0,
0x0f, 0x0b6, 0xc0,
0x8b, 0xce,
0x66, 0xc1, 0xe0, 0x08,
0x0f, 0xb7, 0xc0,
0x89, 0x45, 0xfc,
0x8b, 0x06,
0x6a, 0x01,
0x8b, 0x40, 0x08,
0xff, 0xd0,
0x0f, 0xb6, 0xc0,
0x8b, 0xce,
0x66, 0x09, 0x45, 0xfc,
0xff, 0x75, 0xfc,
0xe8};
ULONG addr = MemDbg::findBytes(sig, sizeof(sig), processStartAddress, processStopAddress);
if (!addr)
return false;
HookParam hp;
hp.address = addr + sizeof(sig) - 1;
hp.type = NO_CONTEXT | USING_CHAR | DATA_INDIRECT | CODEC_ANSI_BE;
hp.offset = get_reg(regs::ebp);
hp.index = -4;
return NewHook(hp, "Elf");
}
bool _h2()
{
// HAN4@49570:AI5WIN.exe
// clang-format off
BYTE sig[]={
0x33,0xc5,
0x89,0x45,0xfc,
0x8a,0x81,XX4,
0x84,0xc0,
0x75,0x0e,
0x8b,0x81,XX4,
0x03,0x81,XX4,
0xeb,XX,
0x3c,0x01,
0x75,0x0e,
0x8b,0x81,XX4,
0x03,0x81,XX4,
0xeb,XX,
0x3c,0x02,
0x75,0x0e,
0x8b,0x81,XX4,
0x03,0x81,XX4,
0xeb,XX,
};
// clang-format on
ULONG addr = MemDbg::findBytes(sig, sizeof(sig), processStartAddress, processStopAddress);
if (!addr)
return false;
addr = MemDbg::findEnclosingAlignedFunction(addr);
if (!addr)
return false;
HookParam hp;
hp.address = addr;
hp.type = NO_CONTEXT | USING_CHAR | CODEC_ANSI_BE;
hp.offset = get_stack(1);
return NewHook(hp, "Elf");
}
bool all()
{
return _h1() | _h2();
}
}
namespace
{
bool el()
{
// https://vndb.org/v2293
// 【el】【Windows10対応】
BYTE sig[] = {
// 0x66,0x8b,0x4d,0x0c
// 0x66,0x8b,0xc1
0x66, 0xc1, 0xe8, 0x08,
XX, // 0x57
0x3c, 0x81,
0x72, 0x04,
0x3c, 0x9f,
0x76, 0x08,
0x3c, 0xe0,
0x72, 0x10,
0x3c, 0xef,
0x77, 0x0c};
// clang-format on
ULONG addr = MemDbg::findBytes(sig, sizeof(sig), processStartAddress, processStopAddress);
if (!addr)
return false;
HookParam hp;
hp.address = addr;
hp.type = NO_CONTEXT | USING_CHAR | CODEC_ANSI_BE;
hp.offset = get_reg(regs::eax);
return NewHook(hp, "Elf");
}
}
bool Elf2::attach_function()
{
return elf2() || Elf2attach_function() || all() || el();
}
bool ElfFunClubFinal::attach_function()
{
// mov reg,ds:TextOutA
bool succ = false;
for (auto addr : findiatcallormov_all((DWORD)TextOutA, processStartAddress, processStartAddress, processStopAddress, PAGE_EXECUTE, XX))
{
BYTE s[] = {XX, 0xCC, 0xCC, 0xCC};
addr = reverseFindBytes(s, 4, addr - 0x100, addr);
if (addr == 0)
continue;
HookParam hp;
hp.address = addr + 4;
hp.type = CODEC_ANSI_BE | USING_CHAR;
hp.text_fun = [](hook_stack *stack, HookParam *hp, uintptr_t *data, uintptr_t *split, size_t *len)
{
*data = (WORD)stack->stack[3];
*len = 2;
*split = stack->stack[2] > 8;
};
succ |= NewHook(hp, "ElfFunClubFinal");
}
return succ;
}