#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ヂ�ーチャー剛史 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ヂ�ーチャー剛史 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_addrprocessStopAddress)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; } } bool Elf::attach_function() { auto _1= InsertElfHook()||__()||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 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, }; 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(); } } bool Elf2::attach_function(){ return elf2()||Elf2attach_function()||all(); } 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; }