This commit is contained in:
恍兮惚兮 2024-08-15 04:50:33 +08:00
parent 3cc416c29f
commit 05c65a095e
2 changed files with 813 additions and 747 deletions

View File

@ -1,4 +1,4 @@
#include"Wolf.h" #include "Wolf.h"
/** /**
* jichi 10/12/2014 * jichi 10/12/2014
* P.S.: Another approach * P.S.: Another approach
@ -39,56 +39,64 @@
* *
* Again, drop the N to split dialogue and menu text into separate threads. * Again, drop the N to split dialogue and menu text into separate threads.
*/ */
namespace { // WolfRPG namespace
// jichi 10/13/2013: restored { // WolfRPG
bool InsertOldWolfHook() // jichi 10/13/2013: restored
{ bool InsertOldWolfHook()
{
// jichi 10/12/2013: // jichi 10/12/2013:
// Step 1: find the address of GetTextMetricsA // Step 1: find the address of GetTextMetricsA
// Step 2: find where this function is called // Step 2: find where this function is called
// Step 3: search "sub esp, XX" after where it is called // Step 3: search "sub esp, XX" after where it is called
enum { sub_esp = 0xec81 }; // jichi: caller pattern: sub esp = 0x81,0xec enum
{
sub_esp = 0xec81
}; // jichi: caller pattern: sub esp = 0x81,0xec
if (DWORD c1 = Util::FindCallAndEntryAbs((DWORD)GetTextMetricsA, processStopAddress - processStartAddress, processStartAddress, sub_esp)) if (DWORD c1 = Util::FindCallAndEntryAbs((DWORD)GetTextMetricsA, processStopAddress - processStartAddress, processStartAddress, sub_esp))
if (DWORD c2 = Util::FindCallOrJmpRel(c1, processStopAddress - processStartAddress, processStartAddress, 0)) { if (DWORD c2 = Util::FindCallOrJmpRel(c1, processStopAddress - processStartAddress, processStartAddress, 0))
union { {
union
{
DWORD i; DWORD i;
WORD *k; WORD *k;
}; };
DWORD j; DWORD j;
for (i = c2 - 0x100, j = c2 - 0x400; i > j; i--) for (i = c2 - 0x100, j = c2 - 0x400; i > j; i--)
if (*k == 0xec83) { // jichi 10/12/2013: 83 EC XX sub esp, XX See: http://lists.cs.uiuc.edu/pipermail/llvm-commits/Week-of-Mon-20120312.txt if (*k == 0xec83)
{ // jichi 10/12/2013: 83 EC XX sub esp, XX See: http://lists.cs.uiuc.edu/pipermail/llvm-commits/Week-of-Mon-20120312.txt
HookParam hp; HookParam hp;
hp.address = i; hp.address = i;
hp.offset=get_reg(regs::ecx); hp.offset = get_reg(regs::ecx);
hp.split = get_reg(regs::esp); hp.split = get_reg(regs::esp);
hp.type = DATA_INDIRECT|USING_SPLIT; hp.type = DATA_INDIRECT | USING_SPLIT;
//GROWL_DWORD(hp.address); // jichi 6/5/2014: 淫乱勀<E4B9B1><E58B80>フィのRPG = 0x50a400 // GROWL_DWORD(hp.address); // jichi 6/5/2014: 淫乱勀<E4B9B1><E58B80>フィのRPG = 0x50a400
ConsoleOutput("INSERT WolfRPG"); ConsoleOutput("INSERT WolfRPG");
return NewHook(hp, "WolfRPG"); return NewHook(hp, "WolfRPG");
} }
} }
//ConsoleOutput("Unknown WolfRPG engine."); // ConsoleOutput("Unknown WolfRPG engine.");
ConsoleOutput("WolfRPG: failed"); ConsoleOutput("WolfRPG: failed");
return false; return false;
} }
//example-game:妹!せいかつ~ファンタジー~ by:iov // example-game:妹!せいかつ~ファンタジー~ by:iov
bool InsertWolf3Hook() bool InsertWolf3Hook()
{ {
const BYTE bytes[] = { 0xC7,0x45,0xFC,0x00,0x00,0x00,0x00,0x8B,0x45,0x94,0x83,0xE0,0x01 }; const BYTE bytes[] = {0xC7, 0x45, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x8B, 0x45, 0x94, 0x83, 0xE0, 0x01};
ULONG range = min(processStopAddress - processStartAddress, MAX_REL_ADDR); ULONG range = min(processStopAddress - processStartAddress, MAX_REL_ADDR);
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStartAddress + range); ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStartAddress + range);
if (!addr) { if (!addr)
{
ConsoleOutput("WolfRPG: pattern3 not found"); ConsoleOutput("WolfRPG: pattern3 not found");
return false; return false;
} }
HookParam myhp; HookParam myhp;
myhp.address = addr+41; myhp.address = addr + 41;
myhp.type = USING_STRING | NO_CONTEXT; myhp.type = USING_STRING | NO_CONTEXT;
myhp.offset=get_reg(regs::eax); myhp.offset = get_reg(regs::eax);
myhp.type |= DATA_INDIRECT; myhp.type |= DATA_INDIRECT;
myhp.index = 4; myhp.index = 4;
@ -97,13 +105,15 @@ bool InsertWolf3Hook()
ConsoleOutput("Insert: WolfRPG_String_Copy Hook"); ConsoleOutput("Insert: WolfRPG_String_Copy Hook");
return NewHook(myhp, nameForUser); return NewHook(myhp, nameForUser);
} }
bool InsertWolf4Hook() { bool InsertWolf4Hook()
const BYTE bytes[] = {0xC6,0x45,0xFC,0x29,0x8B,0x8D,0xE0,0xEF,0xFF,0xFF,0xE8,XX4,0x50,0x8B,0x4D,0xE8,0x2B,0x4D,0xEC }; {
const BYTE bytes[] = {0xC6, 0x45, 0xFC, 0x29, 0x8B, 0x8D, 0xE0, 0xEF, 0xFF, 0xFF, 0xE8, XX4, 0x50, 0x8B, 0x4D, 0xE8, 0x2B, 0x4D, 0xEC};
ULONG range = min(processStopAddress - processStartAddress, MAX_REL_ADDR); ULONG range = min(processStopAddress - processStartAddress, MAX_REL_ADDR);
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStartAddress + range); ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStartAddress + range);
if (!addr) { if (!addr)
{
ConsoleOutput("WolfRPG: pattern4 not found"); ConsoleOutput("WolfRPG: pattern4 not found");
return false; return false;
} }
@ -112,7 +122,7 @@ bool InsertWolf4Hook() {
myhp.address = addr + 16; myhp.address = addr + 16;
myhp.type = USING_STRING | NO_CONTEXT; myhp.type = USING_STRING | NO_CONTEXT;
myhp.offset=get_reg(regs::eax); myhp.offset = get_reg(regs::eax);
// myhp.type |= DATA_INDIRECT; // myhp.type |= DATA_INDIRECT;
// myhp.index = 4; // myhp.index = 4;
@ -121,9 +131,7 @@ bool InsertWolf4Hook() {
ConsoleOutput("Insert: WolfRPG4 Hook"); ConsoleOutput("Insert: WolfRPG4 Hook");
return NewHook(myhp, nameForUser); return NewHook(myhp, nameForUser);
} }
} // WolfRPG namespace } // WolfRPG namespace
@ -132,115 +140,128 @@ bool InsertWolfHook()
// return InsertOldWolfHook(), InsertWolf2Hook(), InsertWolf3Hook(), InsertWolf4Hook(); // return InsertOldWolfHook(), InsertWolf2Hook(), InsertWolf3Hook(), InsertWolf4Hook();
return InsertOldWolfHook(), InsertWolf3Hook(), InsertWolf4Hook(); return InsertOldWolfHook(), InsertWolf3Hook(), InsertWolf4Hook();
} }
namespace{ namespace
{
bool commonfilter(void* data, size_t* len, HookParam* hp){ bool commonfilter(void *data, size_t *len, HookParam *hp)
auto str=std::string(reinterpret_cast<LPSTR>(data),*len); {
bool checkchaos=WideStringToString(StringToWideString(str))!=str; auto str = std::string(reinterpret_cast<LPSTR>(data), *len);
if(checkchaos)return false; bool checkchaos = WideStringToString(StringToWideString(str)) != str;
bool check1= str.find("/")!=str.npos||str.find("\\")!=str.npos; if (checkchaos)
auto hashsuffix=[str](){ return false;
bool check1 = str.find("/") != str.npos || str.find("\\") != str.npos;
auto filterpath={ auto hashsuffix = [str]()
".png",".jpg",".bmp", {
".mp3",".ogg", auto filterpath = {
".webm",".mp4", ".png", ".jpg", ".bmp",
".otf",".mps" ".mp3", ".ogg",
}; ".webm", ".mp4",
for(auto _ :filterpath) ".otf", ".mps"};
if(str.find(_)!=str.npos) for (auto _ : filterpath)
if (str.find(_) != str.npos)
return true; return true;
return false; return false;
}; };
bool check2=hashsuffix(); bool check2 = hashsuffix();
bool check3=all_ascii((const char *)data,*len); bool check3 = all_ascii((const char *)data, *len);
if(check1&&(check2||check3))return false; if (check1 && (check2 || check3))
return false;
return true; return true;
} }
bool hook5_1(DWORD addr_1){ bool hook5_1(DWORD addr_1)
//RJ338582 {
//妹せいかつ ファンタジー1.4.5 // RJ338582
// 妹せいかつ ファンタジー1.4.5
const BYTE bytes[] = { const BYTE bytes[] = {
0x6a,0x01, 0x6a, 0x01,
0x68,XX4, 0x68, XX4,
0x68,XX4, 0x68, XX4,
0x6a,0x01, 0x6a, 0x01,
0x6a,0x00, 0x6a, 0x00,
0xFF,0x77,0x10, 0xFF, 0x77, 0x10,
0xFF,0x77,0x18, 0xFF, 0x77, 0x18,
0xE8 0xE8};
};
auto addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress); auto addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress);
if(addr==0)return false; if (addr == 0)
auto off=(*((DWORD*)(sizeof(bytes)+addr))); return false;
auto _calladdr=addr+sizeof(bytes)+4+off; auto off = (*((DWORD *)(sizeof(bytes) + addr)));
if(addr_1!=_calladdr)return false; auto _calladdr = addr + sizeof(bytes) + 4 + off;
if (addr_1 != _calladdr)
return false;
HookParam hp; HookParam hp;
hp.address = addr+sizeof(bytes)-1; hp.address = addr + sizeof(bytes) - 1;
hp.offset =get_stack(7); hp.offset = get_stack(7);
hp.type =USING_STRING|CODEC_UTF8|EMBED_ABLE|EMBED_AFTER_OVERWRITE|EMBED_BEFORE_SIMPLE; hp.type = USING_STRING | CODEC_UTF8 | EMBED_ABLE | EMBED_AFTER_OVERWRITE | EMBED_BEFORE_SIMPLE;
hp.filter_fun=commonfilter; hp.filter_fun = commonfilter;
return NewHook(hp, "Wolf5_1"); return NewHook(hp, "Wolf5_1");
} }
bool hook5(){ bool hook5()
{
//[220901][あせろら] 寝取られ新妻モニカツンデレな奥さんのHなお仕事 //[220901][あせろら] 寝取られ新妻モニカツンデレな奥さんのHなお仕事
const BYTE bytes[] = { const BYTE bytes[] = {
0x80,0x38,0x40, 0x80, 0x38, 0x40,
0x0f,0x85,XX4, 0x0f, 0x85, XX4,
0x57, 0x57,
0x68,XX4, 0x68, XX4,
0x8d,XX2, 0x8d, XX2,
0xe8 0xe8};
};
auto addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress); auto addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress);
if(addr==0)return false; if (addr == 0)
addr=MemDbg::findEnclosingAlignedFunction(addr); return false;
addr = MemDbg::findEnclosingAlignedFunction(addr);
if(addr==0)return false; if (addr == 0)
if(hook5_1(addr))return true; return false;
if (hook5_1(addr))
return true;
HookParam hp; HookParam hp;
hp.address = addr; hp.address = addr;
hp.offset =get_stack(8); hp.offset = get_stack(8);
hp.type =USING_STRING|CODEC_UTF8|EMBED_ABLE|EMBED_AFTER_OVERWRITE|EMBED_BEFORE_SIMPLE; hp.type = USING_STRING | CODEC_UTF8 | EMBED_ABLE | EMBED_AFTER_OVERWRITE | EMBED_BEFORE_SIMPLE;
hp.filter_fun=commonfilter; hp.filter_fun = commonfilter;
return NewHook(hp, "Wolf5"); return NewHook(hp, "Wolf5");
} }
bool hook6(){ bool hook6()
{
//[220901][あせろら] 寝取られ新妻モニカツンデレな奥さんのHなお仕事 //[220901][あせろら] 寝取られ新妻モニカツンデレな奥さんのHなお仕事
const BYTE bytes[] = { const BYTE bytes[] = {
0xB8,0x00,0x00,0x00,0x80, 0xB8, 0x00, 0x00, 0x00, 0x80,
0x83,0xC0,0x23 0x83, 0xC0, 0x23};
}; bool ok = false;
bool ok=false; auto addrs = Util::SearchMemory(bytes, sizeof(bytes), PAGE_EXECUTE, processStartAddress, processStopAddress);
auto addrs =Util::SearchMemory(bytes, sizeof(bytes),PAGE_EXECUTE, processStartAddress, processStopAddress); for (auto addr : addrs)
for (auto addr : addrs) { {
addr=MemDbg::findEnclosingAlignedFunction(addr); addr = MemDbg::findEnclosingAlignedFunction(addr);
if(addr==0)continue; if (addr == 0)
continue;
HookParam hp; HookParam hp;
hp.address = (DWORD)addr; hp.address = (DWORD)addr;
hp.offset=get_stack(3); hp.offset = get_stack(3);
hp.type =USING_STRING|CODEC_UTF8; hp.type = USING_STRING | CODEC_UTF8;
hp.filter_fun=commonfilter; hp.filter_fun = commonfilter;
ok|=NewHook(hp, "Wolf6"); ok |= NewHook(hp, "Wolf6");
} }
return ok; return ok;
} }
bool hook56(){ bool hook56()
bool _1=hook5(); {
bool _2=hook6(); bool _1 = hook5();
return _1||_2; bool _2 = hook6();
return _1 || _2;
} }
} }
namespace
{ // unnamed
namespace { // unnamed namespace ScenarioHook
{
namespace ScenarioHook { namespace Private
{
namespace Private {
struct TextListElement // ecx, this structure saved a list of element struct TextListElement // ecx, this structure saved a list of element
{ {
@ -253,12 +274,13 @@ namespace Private {
capacity; // 0xe8, capacity of the data including \0 capacity; // 0xe8, capacity of the data including \0
bool isScenarioText() const bool isScenarioText() const
{ return flag1 == 0 && flag2 == 0 && flag3 == 0 && flag4 == 0; } {
return flag1 == 0 && flag2 == 0 && flag3 == 0 && flag4 == 0;
}
bool isValid() const bool isValid() const
{ {
return size > 0 && size <= capacity return size > 0 && size <= capacity && Engine::isAddressReadable(text, capacity) && size == ::strlen(text);
&& Engine::isAddressReadable(text, capacity) && size == ::strlen(text);
} }
}; };
@ -270,26 +292,31 @@ namespace Private {
return s; return s;
} }
std::unordered_set<std::string> dataSet_; std::unordered_set<std::string> dataSet_;
bool hookBefore(hook_stack*s,void* data1, size_t* len1,uintptr_t*role) bool hookBefore(hook_stack *s, void *data1, size_t *len1, uintptr_t *role)
{ {
//enum { DataQueueCapacity = 30 }; // enum { DataQueueCapacity = 30 };
auto self = (TextListElement *)s->ecx; // ecx is actually a list of element auto self = (TextListElement *)s->ecx; // ecx is actually a list of element
if (self->isValid()) { if (self->isValid())
{
char *text = ltrim(self->text); char *text = ltrim(self->text);
if (*text) { if (*text)
{
std::string data = text; std::string data = text;
if (dataSet_.find(data)==dataSet_.end()) { if (dataSet_.find(data) == dataSet_.end())
{
auto role = text == self->text && self->isScenarioText() ? Engine::ScenarioRole : Engine::OtherRole; auto role = text == self->text && self->isScenarioText() ? Engine::ScenarioRole : Engine::OtherRole;
auto split = s->stack[0]; // retaddr auto split = s->stack[0]; // retaddr
// auto sig = Engine::hashThreadSignature(role, split); // auto sig = Engine::hashThreadSignature(role, split);
enum { SendAllowed = true }; enum
{
SendAllowed = true
};
bool timeout; bool timeout;
int prefixSize = text - self->text, int prefixSize = text - self->text,
capacity = self->capacity - prefixSize; capacity = self->capacity - prefixSize;
return write_string_overwrite(data1,len1,data); return write_string_overwrite(data1, len1, data);
// data = EngineController::instance()->dispatchTextASTD(data, role, sig, capacity, SendAllowed, &timeout); // data = EngineController::instance()->dispatchTextASTD(data, role, sig, capacity, SendAllowed, &timeout);
// if (timeout) // if (timeout)
@ -304,26 +331,33 @@ namespace Private {
} }
return 0; return 0;
} }
void hookafter2(hook_stack*s,void* data1, size_t len){ void hookafter2(hook_stack *s, void *data1, size_t len)
{
auto newData =std::string((char*)data1,len); auto newData = std::string((char *)data1, len);
auto self = (TextListElement *)s->ecx; // ecx is actually a list of element auto self = (TextListElement *)s->ecx; // ecx is actually a list of element
if (self->isValid()) { if (self->isValid())
{
char *text = ltrim(self->text); char *text = ltrim(self->text);
if (*text) { if (*text)
{
std::string data = text; std::string data = text;
if (dataSet_.find(data)==dataSet_.end()) { if (dataSet_.find(data) == dataSet_.end())
{
auto role = text == self->text && self->isScenarioText() ? Engine::ScenarioRole : Engine::OtherRole; auto role = text == self->text && self->isScenarioText() ? Engine::ScenarioRole : Engine::OtherRole;
auto split = s->stack[0]; // retaddr auto split = s->stack[0]; // retaddr
// auto sig = Engine::hashThreadSignature(role, split); // auto sig = Engine::hashThreadSignature(role, split);
enum { SendAllowed = true }; enum
{
SendAllowed = true
};
bool timeout; bool timeout;
int prefixSize = text - self->text, int prefixSize = text - self->text,
capacity = self->capacity - prefixSize; capacity = self->capacity - prefixSize;
data=newData; data = newData;
dataSet_.insert(data); dataSet_.insert(data);
::memcpy(text, data.c_str(), min(data.size() + 1, capacity)); ::memcpy(text, data.c_str(), min(data.size() + 1, capacity));
@ -332,9 +366,9 @@ namespace Private {
} }
} }
} }
} // namespace Private } // namespace Private
/** /**
* Sample game: DRAGON SLAVE * Sample game: DRAGON SLAVE
* *
* This function is very long and contains many CharNextA. * This function is very long and contains many CharNextA.
@ -825,30 +859,60 @@ namespace Private {
* 00471898 CC INT3 * 00471898 CC INT3
* 00471899 CC INT3 * 00471899 CC INT3
*/ */
bool attach(ULONG startAddress, ULONG stopAddress) // attach other text bool attach(ULONG startAddress, ULONG stopAddress) // attach other text
{ {
ULONG addr = MemDbg::findCallerAddressAfterInt3((ULONG)::CharNextA, startAddress, stopAddress); ULONG addr = MemDbg::findCallerAddressAfterInt3((ULONG)::CharNextA, startAddress, stopAddress);
//addr = MemDbg::findNearCallAddress(addr, startAddress, stopAddress); // addr = MemDbg::findNearCallAddress(addr, startAddress, stopAddress);
//if (!addr) // if (!addr)
// return false; // return false;
if(addr==0)return 0; if (addr == 0)
return 0;
HookParam hp; HookParam hp;
hp.address=addr; hp.address = addr;
hp.offset=get_reg(regs::ecx); hp.offset = get_reg(regs::ecx);
hp.index = 4; hp.index = 4;
hp.hook_before=Private::hookBefore; hp.hook_before = Private::hookBefore;
hp.hook_after=Private::hookafter2; hp.hook_after = Private::hookafter2;
hp.type=USING_STRING|DATA_INDIRECT|EMBED_ABLE|EMBED_DYNA_SJIS; hp.type = USING_STRING | DATA_INDIRECT | EMBED_ABLE | EMBED_DYNA_SJIS;
hp.hook_font=F_GetGlyphOutlineA; hp.hook_font = F_GetGlyphOutlineA;
return NewHook(hp,"EmbedWolf"); return NewHook(hp, "EmbedWolf");
} }
} // namespace ScenarioHook
} // namespace ScenarioHook
} // unnamed namespace } // unnamed namespace
namespace
bool Wolf::attach_function() { {
auto _=ScenarioHook::attach(processStartAddress,processStopAddress); bool wolf7()
return InsertWolfHook()||hook56()||_; {
BYTE sig[] = {
0x52,
0x8b, 0x4d, 0xf4,
0xe8, XX4,
0x03, 0x45, 0x08,
0x03, 0x45, 0x0c,
0x50,
0x8b, 0x4d, 0xf4,
0xe8, XX4,
0x03, 0x45, 0x08,
0x03, 0x45, 0x14,
0x50,
0xe8, XX4,
0x83, 0xc4, 0x0c,
0x8b, 0x45, 0x14};
auto addr = MemDbg::findBytes(sig, sizeof(sig), processStartAddress, processStopAddress);
if (!addr)
return false;
addr += 31;
HookParam hp;
hp.address = addr;
hp.offset = get_stack(1);
hp.type = USING_STRING | NO_CONTEXT;
return NewHook(hp, "Wolf7");
}
}
bool Wolf::attach_function()
{
auto _ = ScenarioHook::attach(processStartAddress, processStopAddress);
return InsertWolfHook() || hook56() || _ || wolf7();
} }

View File

@ -1,11 +1,13 @@
class Wolf:public ENGINE{ class Wolf : public ENGINE
public: {
Wolf(){ public:
Wolf()
check_by=CHECK_BY::FILE_ANY; {
check_by_target=check_by_list{L"data.wolf",L"data\\*.wolf",L"data\\basicdata\\cdatabase.dat"}; is_engine_certain = false;
check_by = CHECK_BY::FILE_ANY;
check_by_target = check_by_list{L"data.wolf", L"data\\*.wolf", L"data\\basicdata\\cdatabase.dat"};
}; };
bool attach_function(); bool attach_function();
}; };