LunaHook-mirror/LunaHook/engine32/Unicorn.cpp

858 lines
34 KiB
C++
Raw Normal View History

2024-02-07 20:59:24 +08:00
#include"Unicorn.h"
#include"embed_util.h"
/**
* jichi 9/16/2013: a-unicorn / gesen18
* See (CaoNiMaGeBi): http://tieba.baidu.com/p/2586681823
* Pattern: 2bce8bf8
* 2bce sub ecx,esi ; hook here
* 8bf8 mov eds,eax
* 8bd1 mov edx,ecx
*
* /HBN-20*0@xxoo
* - length_offset: 1
* - off: 4294967260 (0xffffffdc)
* - type: 1032 (0x408)
*/
bool InsertUnicornHook()
{
// pattern: 2bce8bf8
const BYTE bytes[] = {
0x2b,0xce, // sub ecx,esi ; hook here
0x8b,0xf8 // mov edi,eax
};
//enum { addr_offset = 0 };
ULONG range = min(processStopAddress - processStartAddress, MAX_REL_ADDR);
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStartAddress + range);
if (!addr) {
ConsoleOutput("Unicorn: pattern not exist");
return false;
}
HookParam hp;
hp.type = NO_CONTEXT | DATA_INDIRECT;
hp.offset=get_reg(regs::edi);
hp.address = addr;
//index = SearchPattern(processStartAddress, size,ins, sizeof(ins));
//GROWL_DWORD2(base, index);
ConsoleOutput("INSERT Unicorn");
return NewHook(hp, "Unicorn");
}
namespace { // unnamed
// A simple but very inefficient implementation for LRU cache.
class TextHashCache
{
int capacity_;
std::list<uint64_t> hashes_;
public:
explicit TextHashCache(int capacity) : capacity_(capacity) {}
bool contains(uint64_t h) const
{ return std::find(hashes_.begin(), hashes_.end(), h) != hashes_.end(); }
void add(uint64_t h)
{
if (hashes_.size() == capacity_)
hashes_.pop_back();
hashes_.push_front(h);
}
};
enum : UINT64 { djb2_hash0 = 5381 };
inline UINT64 djb2(const UINT8 *str, UINT64 hash = djb2_hash0)
{
UINT8 c;
while ((c = *str++))
hash = ((hash << 5) + hash) + c; // hash * 33 + c
return hash;
}inline UINT64 djb2_n2(const char* str, size_t len, UINT64 hash = djb2_hash0)
{
while (len--)
hash = ((hash << 5) + hash) + (*str++); // hash * 33 + c
return hash;
}
inline UINT64 hashByteArraySTD(const std::string& b, UINT64 h = djb2_hash0)
{
return djb2_n2(b.c_str(), b.size(), h);
}
inline UINT64 hashCharArray(const void *lp)
{ return djb2(reinterpret_cast<const UINT8 *>(lp)); }
namespace ScenarioHook {
TextHashCache textCache_(30); // capacity = 30
namespace Private {
class TextStorage
{
LPSTR text_;
std::string oldData_,
newData_;
int lineCount_;
bool saved_;
public:
TextStorage()
: text_(nullptr), lineCount_(0), saved_(false) {}
bool isEmpty() const
{ return lineCount_ == 0; }
void clear()
{
text_ = nullptr;
lineCount_ = 0;
saved_ = false;
oldData_.clear();
newData_.clear();
}
std::string load(char *textAddress);
void save();
bool restore(); // recover old text
} textStorage_;
// Hook
ULONG textOffset_; // = 0x114;
std::string sourceData_;
LPSTR targetText_;
bool hookBefore(hook_stack*s,void* data, size_t* len1,uintptr_t*role)
{
// Sample game: 三極姫4 ~天華繚乱 天命の恋絵巻~
// 004B76BB 51 PUSH ECX
// 004B76BC 8BCB MOV ECX,EBX
// 004B76BE 894424 14 MOV DWORD PTR SS:[ESP+0x14],EAX
// 004B76C2 E8 89A5FFFF CALL Sangokuh.004B1C50 ; jichi: name caller
// 004B76C7 E8 44A5FFFF CALL Sangokuh.004B1C10
// 004B76CC 85C0 TEST EAX,EAX
// 004B76CE 0F8E F6000000 JLE Sangokuh.004B77CA
// 004B76D4 8BF8 MOV EDI,EAX
// 004B76D6 EB 08 JMP SHORT Sangokuh.004B76E0
// 004B76D8 8DA424 00000000 LEA ESP,DWORD PTR SS:[ESP]
// 004B76DF 90 NOP
// 004B76E0 33C0 XOR EAX,EAX
// 004B76E2 B9 0F000000 MOV ECX,0xF
// 004B76E7 898C24 FC000000 MOV DWORD PTR SS:[ESP+0xFC],ECX
// 004B76EE 898424 F8000000 MOV DWORD PTR SS:[ESP+0xF8],EAX
// 004B76F5 888424 E8000000 MOV BYTE PTR SS:[ESP+0xE8],AL
// 004B76FC 898C24 18010000 MOV DWORD PTR SS:[ESP+0x118],ECX
// 004B7703 898424 14010000 MOV DWORD PTR SS:[ESP+0x114],EAX
// 004B770A 888424 04010000 MOV BYTE PTR SS:[ESP+0x104],AL
// 004B7711 8D9424 84040000 LEA EDX,DWORD PTR SS:[ESP+0x484]
// 004B7718 52 PUSH EDX
// 004B7719 8BCB MOV ECX,EBX
// 004B771B C68424 AC060000 01 MOV BYTE PTR SS:[ESP+0x6AC],0x1
// 004B7723 E8 28A5FFFF CALL Sangokuh.004B1C50 ; jichi: scenario caller
// 004B7728 8D8424 84040000 LEA EAX,DWORD PTR SS:[ESP+0x484]
// 004B772F 50 PUSH EAX
// 004B7730 8D8C24 E8000000 LEA ECX,DWORD PTR SS:[ESP+0xE8]
//
// Sample game: 天極姫 ~新世大乱・双界の覇者達~
// Name caller:
// 0049A83B E8 D0AFFFFF CALL .00495810
// 0049A840 894424 14 MOV DWORD PTR SS:[ESP+0x14],EAX
// 0049A844 8D8424 EC010000 LEA EAX,DWORD PTR SS:[ESP+0x1EC]
// 0049A84B 50 PUSH EAX
// 0049A84C E8 DFAFFFFF CALL .00495830 ; jichi: name caller
// 0049A851 E8 9AAFFFFF CALL .004957F0
// 0049A856 BD 0F000000 MOV EBP,0xF
// 0049A85B 85C0 TEST EAX,EAX
// 0049A85D 0F8E E3000000 JLE .0049A946
auto retaddr = s->stack[0];
* role = 0;
//if (retaddr == 0x4b7728)
if ((*(DWORD *)(retaddr - 5 - 8) & 0x00ffffff) == 0x2484c6) // 004B771B C68424 AC060000 01 MOV BYTE PTR SS:[ESP+0x6AC],0x1
*role = Engine::ScenarioRole;
//else if (retaddr == 0x4b76c7)
else if ((*(DWORD *)(retaddr - 5 - 8) & 0x00ffffff) == 0x0024848d // 0049A844 8D8424 EC010000 LEA EAX,DWORD PTR SS:[ESP+0x1EC]
|| (*(DWORD *)(retaddr - 5 - 4) & 0x00ffffff) == 0x00244489) // 004B76BE 894424 14 MOV DWORD PTR SS:[ESP+0x14],EAX
*role = Engine::NameRole;
//else
// return true;
if (*role != Engine::ScenarioRole && !textStorage_.isEmpty()) {
textStorage_.restore();
textStorage_.clear();
}
if (!*role)
return false;
auto text = (LPSTR)*(DWORD *)(s->ecx + textOffset_); // [ecx+0x114]
if (!*text || all_ascii(text)) // allspaces is only needed when textstorage is enabled though
return false;
if (!textStorage_.isEmpty()) {
textStorage_.restore();
textStorage_.clear();
}
bool textStorageEnabled = *role == Engine::ScenarioRole && Engine::isAddressWritable(text);
std::string oldData;
if (textStorageEnabled)
oldData = textStorage_.load(text);
else
oldData = text;
if (*role == Engine::NameRole)
strReplace(oldData, "\x81\x40", "");
//oldData.replace("\x81\x40", ""); // remove spaces in the middle of names
strcpy((CHAR*)data,oldData.c_str());
*len1=oldData.size();
return true;
}
void hookafter2(hook_stack*s,void* data, size_t len){
auto newData =std::string((char*)data,len);
auto retaddr = s->stack[0];
int role = 0;
//if (retaddr == 0x4b7728)
if ((*(DWORD *)(retaddr - 5 - 8) & 0x00ffffff) == 0x2484c6) // 004B771B C68424 AC060000 01 MOV BYTE PTR SS:[ESP+0x6AC],0x1
role = Engine::ScenarioRole;
//else if (retaddr == 0x4b76c7)
else if ((*(DWORD *)(retaddr - 5 - 8) & 0x00ffffff) == 0x0024848d // 0049A844 8D8424 EC010000 LEA EAX,DWORD PTR SS:[ESP+0x1EC]
|| (*(DWORD *)(retaddr - 5 - 4) & 0x00ffffff) == 0x00244489) // 004B76BE 894424 14 MOV DWORD PTR SS:[ESP+0x14],EAX
role = Engine::NameRole;
//else
// return true;
if (role != Engine::ScenarioRole && !textStorage_.isEmpty()) {
textStorage_.restore();
textStorage_.clear();
}
if (!role)
return ;
auto text = (LPSTR)*(DWORD *)(s->ecx + textOffset_); // [ecx+0x114]
if (!*text || all_ascii(text)) // allspaces is only needed when textstorage is enabled though
return ;
if (!textStorage_.isEmpty()) {
textStorage_.restore();
textStorage_.clear();
}
bool textStorageEnabled = role == Engine::ScenarioRole && Engine::isAddressWritable(text);
std::string oldData;
if (textStorageEnabled)
oldData = textStorage_.load(text);
else
oldData = text;
if (role == Engine::NameRole)
strReplace(oldData, "\x81\x40", "");
//oldData.replace("\x81\x40", ""); // remove spaces in the middle of names
if (oldData == newData) {
if (textStorageEnabled)
textStorage_.clear();
return ;
}
if (textStorageEnabled)
textStorage_.save();
sourceData_ = newData;
targetText_ = (LPSTR)s->stack[1]; // arg1
textCache_.add(hashByteArraySTD(newData));
}
bool hookAfter(hook_stack*s,void* data, size_t* len1,uintptr_t*role)
{
if (targetText_) {
::strcpy(targetText_, sourceData_.c_str());
targetText_ = nullptr;
}
return false;
}
} // namespace Private
/**
* Sample text
*
* Sample game: 4
*
* 01FE881C 81 40 92 6A 81 40 00 01 81 75 82 BB 81 41 82 BB   .
* 01FE882C 82 F1 82 C8 81 63 81 63 82 BB 82 EA 82 AA 8D C5
* 01FE883C 8C E3 82 CC 90 48 97 BF 82 C8 82 CC 82 C9 81 63
* 01FE884C 81 63 81 49 81 76 00 00 00 00 FF FF FF FF FF FF ....
* 01FE885C FF FF 11 19 00 1B 00 0F 19 00 1D 00 03 00 00 00 .......
* 01FE886C 03 00 00 00 00 01 97 AA 92 44 81 5C 81 5C 00 00 ......
*
* 01FE8758 01 00 00 00 01 00 00 00 93 90 81 40 91 AF 00 02 ...... .
* 01FE8768 81 75 82 C7 82 A4 82 B9 82 B1 82 EA 82 C1 82 DB
* 01FE8778 82 C1 82 BF 82 CC 90 48 97 BF 82 AA 82 A0 82 C1
* 01FE8788 82 BD 82 C6 82 B1 82 EB 82 C5 81 41 8B 51 82 A6
* 01FE8798 82 C4 8E 80 00 00 00 00 FF FF FF FF FF FF FF FF ....
* 01FE87A8 0A 82 CA 82 CC 82 CD 93 AF 82 B6 82 BE 82 EB 81 .
* 01FE87B8 49 81 40 82 D9 82 E7 91 53 95 94 82 E6 82 B1 82 I 
* 01FE87C8 B9 82 C1 81 49 81 76 00 00 00 00 FF FF FF FF FF ....
* 01FE87D8 FF FF FF 11 19 00 16 00 19 19 00 18 00 32 00 00 ....2..
* 01FE87E8 00 44 61 74 61 5C 76 6F 69 63 65 5C 65 74 63 5C .Data\voice\etc\
* 01FE87F8 65 74 63 4A 5F 70 63 41 5F 30 30 30 31 2E 76 6F etcJ_pcA_0001.vo
* 01FE8808 69 00 00 00 00 00 00 0F 19 00 19 00 02 00 00 00 i...........
*
* Sample game: 6
*
* 023AF0E8 82 BB 82 CC 90 BA 82 F0 95 B7 82 AB 81 41 90 B0
* 023AF0F8 90 4D 82 CD 82 B7 82 C1 82 C6 95 5C 8F EE 82 F0
* 023AF108 88 F8 82 AB 92 F7 82 DF 82 BD 81 42 00 00 00 00 ....
* 023AF118 BE BE BE FF FF FF FF FF 11 0E 00 1E 00 0F 0E 00 ...
* 023AF128 20 00 03 00 00 00 03 00 00 00 95 90 93 63 90 4D .......
* 023AF138 94 C9 00 01 81 75 90 4D 8C D5 97 6C 82 CD 81 41 .
* 023AF148 97 5C 92 E8 82 C7 82 A8 82 E8 82 BE 82 BB 82 A4
* 023AF158 82 BE 81 76 00 00 00 00 BE BE BE FF FF FF FF FF ....
* 023AF168 11 0E 00 22 00 0F 0E 00 24 00 04 00 00 00 04 00 ."..$.....
* 023AF178 00 00 00 02 95 94 89 AE 82 C9 82 CD 82 A2 82 C1 ...
* 023AF188 82 C4 82 AB 82 BD 90 4D 94 C9 82 CD 81 41 90 B0
* 023AF198 90 4D 82 CC 91 4F 82 D6 82 C6 8D 98 82 F0 82 A8
* 023AF1A8 82 EB 82 B5 8C FC 82 A9 00 00 00 00 BE BE BE FF ....
* 023AF1B8 FF FF FF FF 0A 82 A2 82 A0 82 A4 81 42 00 00 00 ....
* 023AF1C8 00 BE BE BE FF FF FF FF FF 11 0E 00 27 00 01 0E ..'.
* 023AF1D8 00 2A 00 84 D9 07 00 02 00 00 00 E8 18 00 00 01 .*.......
* 023AF1E8 60 00 00 00 E9 18 00 00 01 5B 00 00 00 19 0E 00 `.....[....
* 023AF1F8 2C 00 06 00 00 00 44 61 74 61 5C 76 6F 69 63 65 ,....Data\voice
* 023AF208 5C 73 69 6E 67 65 6E 5C 73 69 6E 67 65 6E 5F 30 \singen\singen_0
* 023AF218 30 34 33 2E 76 6F 69 00 00 00 00 00 00 0F 0E 00 043.voi.......
*
* Sample game:
* 0211F8AA 82 91 80 82 BD 82 BF 82 CD 82 B1 82 CC 90 A2 8A €
* 0211F8BA 45 82 C9 93 CB 91 52 8C BB 82 EA 82 BD 81 42 82 Eに突然現れた
* 0211F8CA BB 82 B5 82 C4 82 B1 82 B1 82 CC 96 AF 82 BD 82
* 0211F8DA BF 82 CD 00 00 00 00 BE BE BE FF FF FF FF FF 0A ソ.....
* 0211F8EA 91 82 91 80 82 BD 82 BF 82 F0 81 41 92 B7 82 AD
* 0211F8FA 91 B1 82 A2 82 BD 90 ED 97 90 82 F0 8F 49 82 ED
* 0211F90A 82 E7 82 B9 82 E9 89 70 97 59 82 C6 81 41 96 7B
* 0211F91A 8B 43 82 C5 00 00 00 00 BE BE BE FF FF FF FF FF ....
* 0211F92A 0A 90 4D 82 B6 82 C4 82 A2 82 E9 82 C6 82 A2 82 .
* 0211F93A A4 82 B1 82 C6 82 BE 82 C1 82 BD 81 42 00 00 00 ...
*/
// 三極姫4: 00 00 00 00 ff ff ff ff ff ff ff ff 0a
// 戦極姫6: 00 00 00 00 be be be ff ff ff ff ff 0a
//enum { TextSeparatorSize = 12 };
static inline bool isTextSeparator(LPCSTR text)
{
//return 0 == ::memcmp(p, "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0a", 13);
return 0 == ::memcmp(text, "\x00\x00\x00\x00", 4)
&& 0 == ::memcmp(text + 8, "\xff\xff\xff\xff\x0a", 5);
}
std::string Private::TextStorage::load(char *text)
{
text_ = text;
std::string data = text;
lineCount_ = 1;
LPCSTR p = text + ::strlen(text);
for (; isTextSeparator(p); p += ::strlen(p)) {
lineCount_++;
p += 12;
data.append(p);
}
oldData_ = std::string(text, p - text);
return data;
}
void Private::TextStorage::save()
{
if (lineCount_ <= 1)
return;
LPSTR p = text_ + ::strlen(text_);
while (isTextSeparator(p)) {
p += 12 + 1; // +1 for the extra 0xa
if (size_t size = ::strlen(p)) {
::memset(p, ' ', size);
p += size;
}
}
newData_ = std::string(text_, p - text_);
}
bool Private::TextStorage::restore()
{
if (!saved_
|| !Engine::isAddressWritable(text_, oldData_.size())
|| ::memcmp(text_, newData_.c_str(), newData_.size()))
return false;
if (::memcmp(text_, oldData_.c_str(), oldData_.size()))
::memcpy(text_, oldData_.c_str(), oldData_.size());
saved_ = false;
return true;
}
/**
* Sample game: 4
*
* Function found by hardware breakpoint scenario text.
*
* The memory copy function:
* 004B1C4D CC INT3
* 004B1C4E CC INT3
* 004B1C4F CC INT3
* 004B1C50 8B81 14010000 MOV EAX,DWORD PTR DS:[ECX+0x114] ; jichi: source text in eax, beforeAddress
* 004B1C56 8B5424 04 MOV EDX,DWORD PTR SS:[ESP+0x4] ; jichi: target address in edx
* 004B1C5A 56 PUSH ESI
* 004B1C5B 33F6 XOR ESI,ESI
* 004B1C5D 8038 00 CMP BYTE PTR DS:[EAX],0x0
* 004B1C60 74 1D JE SHORT Sangokuh.004B1C7F
* 004B1C62 8B81 14010000 MOV EAX,DWORD PTR DS:[ECX+0x114]
* 004B1C68 8A00 MOV AL,BYTE PTR DS:[EAX]
* 004B1C6A 8802 MOV BYTE PTR DS:[EDX],AL
* 004B1C6C FF81 14010000 INC DWORD PTR DS:[ECX+0x114]
* 004B1C72 8B81 14010000 MOV EAX,DWORD PTR DS:[ECX+0x114]
* 004B1C78 42 INC EDX
* 004B1C79 46 INC ESI
* 004B1C7A 8038 00 CMP BYTE PTR DS:[EAX],0x0
* 004B1C7D ^75 E3 JNZ SHORT Sangokuh.004B1C62
* 004B1C7F 8B81 14010000 MOV EAX,DWORD PTR DS:[ECX+0x114]
* 004B1C85 8A00 MOV AL,BYTE PTR DS:[EAX]
* 004B1C87 8802 MOV BYTE PTR DS:[EDX],AL
* 004B1C89 FF81 14010000 INC DWORD PTR DS:[ECX+0x114]
* 004B1C8F 8BC6 MOV EAX,ESI ; jichi: copied count
* 004B1C91 5E POP ESI
* 004B1C92 C2 0400 RETN 0x4 ; jichi: afterAddress
* 004B1C95 CC INT3
* 004B1C96 CC INT3
* 004B1C97 CC INT3
*
* The very large caller function:
*
* 004B76AB 894424 1C MOV DWORD PTR SS:[ESP+0x1C],EAX
* 004B76AF E8 7CA5FFFF CALL Sangokuh.004B1C30
* 004B76B4 8D8C24 7C030000 LEA ECX,DWORD PTR SS:[ESP+0x37C]
* 004B76BB 51 PUSH ECX
* 004B76BC 8BCB MOV ECX,EBX
* 004B76BE 894424 14 MOV DWORD PTR SS:[ESP+0x14],EAX
* 004B76C2 E8 89A5FFFF CALL Sangokuh.004B1C50 ; jichi: name caller
* 004B76C7 E8 44A5FFFF CALL Sangokuh.004B1C10
* 004B76CC 85C0 TEST EAX,EAX
* 004B76CE 0F8E F6000000 JLE Sangokuh.004B77CA
* 004B76D4 8BF8 MOV EDI,EAX
* 004B76D6 EB 08 JMP SHORT Sangokuh.004B76E0
* 004B76D8 8DA424 00000000 LEA ESP,DWORD PTR SS:[ESP]
* 004B76DF 90 NOP
* 004B76E0 33C0 XOR EAX,EAX
* 004B76E2 B9 0F000000 MOV ECX,0xF
* 004B76E7 898C24 FC000000 MOV DWORD PTR SS:[ESP+0xFC],ECX
* 004B76EE 898424 F8000000 MOV DWORD PTR SS:[ESP+0xF8],EAX
* 004B76F5 888424 E8000000 MOV BYTE PTR SS:[ESP+0xE8],AL
* 004B76FC 898C24 18010000 MOV DWORD PTR SS:[ESP+0x118],ECX
* 004B7703 898424 14010000 MOV DWORD PTR SS:[ESP+0x114],EAX
* 004B770A 888424 04010000 MOV BYTE PTR SS:[ESP+0x104],AL
* 004B7711 8D9424 84040000 LEA EDX,DWORD PTR SS:[ESP+0x484]
* 004B7718 52 PUSH EDX
* 004B7719 8BCB MOV ECX,EBX
* 004B771B C68424 AC060000 01 MOV BYTE PTR SS:[ESP+0x6AC],0x1
* 004B7723 E8 28A5FFFF CALL Sangokuh.004B1C50 ; jichi: scenario caller
* 004B7728 8D8424 84040000 LEA EAX,DWORD PTR SS:[ESP+0x484]
* 004B772F 50 PUSH EAX
* 004B7730 8D8C24 E8000000 LEA ECX,DWORD PTR SS:[ESP+0xE8]
*
* Sample game: 6
* 004A6C88 CC INT3
* 004A6C89 CC INT3
* 004A6C8A CC INT3
* 004A6C8B CC INT3
* 004A6C8C CC INT3
* 004A6C8D CC INT3
* 004A6C8E CC INT3
* 004A6C8F CC INT3
* 004A6C90 8B81 14010000 MOV EAX,DWORD PTR DS:[ECX+0x114]
* 004A6C96 8B5424 04 MOV EDX,DWORD PTR SS:[ESP+0x4]
* 004A6C9A 56 PUSH ESI
* 004A6C9B 33F6 XOR ESI,ESI
* 004A6C9D 8038 00 CMP BYTE PTR DS:[EAX],0x0
* 004A6CA0 74 1D JE SHORT .004A6CBF
* 004A6CA2 8B81 14010000 MOV EAX,DWORD PTR DS:[ECX+0x114]
* 004A6CA8 8A00 MOV AL,BYTE PTR DS:[EAX]
* 004A6CAA 8802 MOV BYTE PTR DS:[EDX],AL
* 004A6CAC FF81 14010000 INC DWORD PTR DS:[ECX+0x114]
* 004A6CB2 8B81 14010000 MOV EAX,DWORD PTR DS:[ECX+0x114]
* 004A6CB8 42 INC EDX
* 004A6CB9 46 INC ESI
* 004A6CBA 8038 00 CMP BYTE PTR DS:[EAX],0x0
* 004A6CBD ^75 E3 JNZ SHORT .004A6CA2
* 004A6CBF 8B81 14010000 MOV EAX,DWORD PTR DS:[ECX+0x114]
* 004A6CC5 8A00 MOV AL,BYTE PTR DS:[EAX]
* 004A6CC7 8802 MOV BYTE PTR DS:[EDX],AL
* 004A6CC9 FF81 14010000 INC DWORD PTR DS:[ECX+0x114]
* 004A6CCF 8BC6 MOV EAX,ESI
* 004A6CD1 5E POP ESI
* 004A6CD2 C2 0400 RETN 0x4
* 004A6CD5 CC INT3
* 004A6CD6 CC INT3
* 004A6CD7 CC INT3
* 004A6CD8 CC INT3
* 004A6CD9 CC INT3
*/
bool attach(ULONG startAddress, ULONG stopAddress)
{
ULONG beforeAddress;
{
const uint8_t bytes[] = {
0x8b,0x81, XX4, // 004b1c50 8b81 14010000 mov eax,dword ptr ds:[ecx+0x114] ; jichi: source text in eax
0x8b,0x54,0x24, 0x04, // 004b1c56 8b5424 04 mov edx,dword ptr ss:[esp+0x4] ; jichi: target address in edx
0x56, // 004b1c5a 56 push esi
0x33,0xf6, // 004b1c5b 33f6 xor esi,esi
0x80,0x38, 0x00 // 004b1c5d 8038 00 cmp byte ptr ds:[eax],0x0
};
beforeAddress = MemDbg::findBytes(bytes, sizeof(bytes), startAddress, stopAddress);
if (!beforeAddress)
return false;
}
ULONG afterAddress;
{
// 004B1C92 C2 0400 RETN 0x4 ; jichi: afterAddress
// 004B1C95 CC INT3
DWORD bytes = 0xcc0004c2;
afterAddress = MemDbg::findBytes(&bytes, sizeof(bytes), beforeAddress, stopAddress);
if (!afterAddress || afterAddress - beforeAddress > 0x200) // should within 0x42
return false;
}
// 004b1c50 8b81 14010000 mov eax,dword ptr ds:[ecx+0x114] ; jichi: source text in eax
Private::textOffset_ = *(DWORD *)(beforeAddress + 2); // 0x114
HookParam hp;
hp.address=beforeAddress;
hp.hook_before=Private::hookBefore;
hp.hook_after=Private::hookafter2;
hp.offset=get_stack(1);
hp.newlineseperator=L"\\n";
hp.type=EMBED_ABLE|EMBED_DYNA_SJIS;
hp.hook_font=F_GetGlyphOutlineA;
auto suc=NewHook(hp,"EMbedUnicorn");
hp.address=afterAddress;
hp.type=HOOK_EMPTY|EMBED_ABLE;
hp.hook_before=Private::hookAfter;
suc|=NewHook(hp,"EMbedUnicorn");
return suc;
}
} // namespace ScenarioHook
namespace OtherHook {
namespace Private {
//bool isSkippedText(LPCSTR text)
//{
// return 0 == ::strcmp(text, "\x82\x6c\x82\x72\x20\x83\x53\x83\x56\x83\x62\x83\x4e"); // " ゴシック"
//}
/**
* Sample game: 6
*
*/
bool hookBefore(hook_stack*s,void* data, size_t* len,uintptr_t*role)
{
static std::string data_;
auto retaddr = s->stack[0];
// 0052FDCE 83C4 0C ADD ESP,0xC
// 0052FDD1 ^EB C1 JMP SHORT .0052FD94
//if (*(DWORD *)retaddr != 0xeb0cc483)
// return true;
//retaddr = s->stack[7]; // parent caller
// Scenario/name/other threads to skip:
// - 0x404062 // there are so many other texts in this thread
//
// Other thread to keep:
// - 0x4769f8: message
// - 0x4135ba: in-game text that split into lines
//
// 004769E9 2BC7 SUB EAX,EDI
// 004769EB 50 PUSH EAX
// 004769EC 51 PUSH ECX
// 004769ED 8D8E C4080000 LEA ECX,DWORD PTR DS:[ESI+0x8C4]
// 004769F3 E8 B8D1F8FF CALL .00403BB0 ; jichi; message
// 004769F8 D9EE FLDZ
// 004769FA 8B6C24 18 MOV EBP,DWORD PTR SS:[ESP+0x18]
// 004769FE D996 04090000 FST DWORD PTR DS:[ESI+0x904]
//
// 004135B1 52 PUSH EDX
// 004135B2 8D4E 3C LEA ECX,DWORD PTR DS:[ESI+0x3C]
// 004135B5 E8 F605FFFF CALL .00403BB0 ; jichi: in-game caller
// 004135BA EB 08 JMP SHORT .004135C4
// 004135BC 8D4E 3C LEA ECX,DWORD PTR DS:[ESI+0x3C]
//if (retaddr != 0x4769f8 && retaddr != 0x4135ba)
// return true;
switch (*(WORD *)retaddr) {
case 0xeed9: // 004769F8 D9EE FLDZ
case 0x08eb: // 004135BA EB 08 JMP SHORT .004135C4
break;
default: return false;
}
auto text = (LPCSTR)s->stack[1]; // arg1
int size = s->stack[2]; // arg2
if (!text
|| size <= 2 // avoid painting individual character
|| ::strlen(text) != size
|| all_ascii(text)
|| ScenarioHook::textCache_.contains(hashCharArray(text)))
//|| !q->isTextDecodable(text)) // avoid re-translation
//|| isascii(text[::strlen(text) - 2])
//|| isSkippedText(text))
return false;
enum { role = Engine::OtherRole };
std::string oldData = text;
strcpy((char*)data,oldData.c_str());
*len=oldData.size();
return true;
/* //oldData.replace("\\n", "\n"); // Remove new line. FIXME: automatically adjust line width
std::string newData = EngineController::instance()->dispatchTextASTD(oldData, role, retaddr);
if (newData == oldData)
return true;
data_ = newData;
s->stack[1] = (ULONG)data_.c_str();
s->stack[2] = data_.size();
return true;*/
}
void hookafter(hook_stack*s,void* data, size_t len){
auto newData =std::string((char*)data,len);
static std::string data_;
data_ = newData;
s->stack[1] = (ULONG)data_.c_str();
s->stack[2] = data_.size();
}
} // namespace Private
/**
* Sample game: 6
* Function found by debugging caller of GetGlyphOutlineA.
* 0052F2DC CC INT3
* 0052F2DD CC INT3
* 0052F2DE CC INT3
* 0052F2DF CC INT3
* 0052F2E0 55 PUSH EBP
* 0052F2E1 8BEC MOV EBP,ESP
* 0052F2E3 57 PUSH EDI
* 0052F2E4 56 PUSH ESI
* 0052F2E5 8B75 0C MOV ESI,DWORD PTR SS:[EBP+0xC] ; jichi: arg2, source text
* 0052F2E8 8B4D 10 MOV ECX,DWORD PTR SS:[EBP+0x10] ; jichi: arg3, count?
* 0052F2EB 8B7D 08 MOV EDI,DWORD PTR SS:[EBP+0x8] ; jichi: arg1, target location
* 0052F2EE 8BC1 MOV EAX,ECX
* 0052F2F0 8BD1 MOV EDX,ECX
* 0052F2F2 03C6 ADD EAX,ESI
* 0052F2F4 3BFE CMP EDI,ESI
* 0052F2F6 76 08 JBE SHORT .0052F300
* 0052F2F8 3BF8 CMP EDI,EAX
* 0052F2FA 0F82 A4010000 JB .0052F4A4
* 0052F300 81F9 00010000 CMP ECX,0x100 ; jichi: 0x100 is the threshold
* 0052F306 72 1F JB SHORT .0052F327
* 0052F308 833D 6472D800 00 CMP DWORD PTR DS:[0xD87264],0x0
* 0052F30F 74 16 JE SHORT .0052F327
* 0052F311 57 PUSH EDI
* 0052F312 56 PUSH ESI
* 0052F313 83E7 0F AND EDI,0xF
* 0052F316 83E6 0F AND ESI,0xF
* 0052F319 3BFE CMP EDI,ESI
* 0052F31B 5E POP ESI
* 0052F31C 5F POP EDI
* 0052F31D 75 08 JNZ SHORT .0052F327
* 0052F31F 5E POP ESI
* 0052F320 5F POP EDI
* 0052F321 5D POP EBP
* 0052F322 E9 7C5F0000 JMP .005352A3
* 0052F327 F7C7 03000000 TEST EDI,0x3
* 0052F32D 75 15 JNZ SHORT .0052F344
* 0052F32F C1E9 02 SHR ECX,0x2
* 0052F332 83E2 03 AND EDX,0x3
* 0052F335 83F9 08 CMP ECX,0x8
* 0052F338 72 2A JB SHORT .0052F364
* 0052F33A F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS>
* 0052F33C FF2495 54F45200 JMP DWORD PTR DS:[EDX*4+0x52F454]
* 0052F343 90 NOP
*
* Here's its parent parent caller:
* - arg1: jichi: source text
* - arg2: jichi: source size
*
* 00403BAB CC INT3
* 00403BAC CC INT3
* 00403BAD CC INT3
* 00403BAE CC INT3
* 00403BAF CC INT3
* 00403BB0 55 PUSH EBP
* 00403BB1 8B6C24 08 MOV EBP,DWORD PTR SS:[ESP+0x8]
* 00403BB5 56 PUSH ESI
* 00403BB6 57 PUSH EDI
* 00403BB7 8BF1 MOV ESI,ECX
* 00403BB9 85ED TEST EBP,EBP
* 00403BBB 74 46 JE SHORT .00403C03
* 00403BBD 8B56 18 MOV EDX,DWORD PTR DS:[ESI+0x18]
* 00403BC0 8D46 04 LEA EAX,DWORD PTR DS:[ESI+0x4]
* 00403BC3 83FA 10 CMP EDX,0x10
* 00403BC6 72 04 JB SHORT .00403BCC
* 00403BC8 8B08 MOV ECX,DWORD PTR DS:[EAX]
* 00403BCA EB 02 JMP SHORT .00403BCE
* 00403BCC 8BC8 MOV ECX,EAX
* 00403BCE 3BE9 CMP EBP,ECX
* 00403BD0 72 31 JB SHORT .00403C03
* 00403BD2 83FA 10 CMP EDX,0x10
* 00403BD5 72 04 JB SHORT .00403BDB
* 00403BD7 8B08 MOV ECX,DWORD PTR DS:[EAX]
* 00403BD9 EB 02 JMP SHORT .00403BDD
* 00403BDB 8BC8 MOV ECX,EAX
* 00403BDD 8B7E 14 MOV EDI,DWORD PTR DS:[ESI+0x14]
* 00403BE0 03F9 ADD EDI,ECX
* 00403BE2 3BFD CMP EDI,EBP
* 00403BE4 76 1D JBE SHORT .00403C03
* 00403BE6 83FA 10 CMP EDX,0x10
* 00403BE9 72 02 JB SHORT .00403BED
* 00403BEB 8B00 MOV EAX,DWORD PTR DS:[EAX]
* 00403BED 8B4C24 14 MOV ECX,DWORD PTR SS:[ESP+0x14]
* 00403BF1 51 PUSH ECX
* 00403BF2 2BE8 SUB EBP,EAX
* 00403BF4 55 PUSH EBP
* 00403BF5 56 PUSH ESI
* 00403BF6 8BCE MOV ECX,ESI
* 00403BF8 E8 D3FEFFFF CALL .00403AD0
* 00403BFD 5F POP EDI
* 00403BFE 5E POP ESI
* 00403BFF 5D POP EBP
* 00403C00 C2 0800 RETN 0x8
* 00403C03 8B7C24 14 MOV EDI,DWORD PTR SS:[ESP+0x14]
* 00403C07 83FF FE CMP EDI,-0x2
* 00403C0A 76 05 JBE SHORT .00403C11
* 00403C0C E8 B94F1500 CALL .00558BCA
* 00403C11 8B46 18 MOV EAX,DWORD PTR DS:[ESI+0x18]
* 00403C14 3BC7 CMP EAX,EDI
* 00403C16 73 20 JNB SHORT .00403C38
* 00403C18 8B56 14 MOV EDX,DWORD PTR DS:[ESI+0x14]
* 00403C1B 52 PUSH EDX
* 00403C1C 57 PUSH EDI
* 00403C1D 8BCE MOV ECX,ESI
* 00403C1F E8 5CFDFFFF CALL .00403980
* 00403C24 85FF TEST EDI,EDI
* 00403C26 76 56 JBE SHORT .00403C7E
* 00403C28 8B4E 18 MOV ECX,DWORD PTR DS:[ESI+0x18]
* 00403C2B 53 PUSH EBX
* 00403C2C 8D5E 04 LEA EBX,DWORD PTR DS:[ESI+0x4]
* 00403C2F 83F9 10 CMP ECX,0x10
* 00403C32 72 2C JB SHORT .00403C60
* 00403C34 8B03 MOV EAX,DWORD PTR DS:[EBX]
* 00403C36 EB 2A JMP SHORT .00403C62
* 00403C38 85FF TEST EDI,EDI
* 00403C3A ^75 EA JNZ SHORT .00403C26
* 00403C3C 897E 14 MOV DWORD PTR DS:[ESI+0x14],EDI
* 00403C3F 83F8 10 CMP EAX,0x10
* 00403C42 72 0E JB SHORT .00403C52
* 00403C44 8B46 04 MOV EAX,DWORD PTR DS:[ESI+0x4]
* 00403C47 5F POP EDI
* 00403C48 C600 00 MOV BYTE PTR DS:[EAX],0x0
* 00403C4B 8BC6 MOV EAX,ESI
* 00403C4D 5E POP ESI
* 00403C4E 5D POP EBP
* 00403C4F C2 0800 RETN 0x8
* 00403C52 8D46 04 LEA EAX,DWORD PTR DS:[ESI+0x4]
* 00403C55 5F POP EDI
* 00403C56 C600 00 MOV BYTE PTR DS:[EAX],0x0
* 00403C59 8BC6 MOV EAX,ESI
* 00403C5B 5E POP ESI
* 00403C5C 5D POP EBP
* 00403C5D C2 0800 RETN 0x8
* 00403C60 8BC3 MOV EAX,EBX
* 00403C62 57 PUSH EDI
* 00403C63 55 PUSH EBP
* 00403C64 51 PUSH ECX
* 00403C65 50 PUSH EAX
* 00403C66 E8 19C11200 CALL .0052FD84 ; jichi: actual paint function
* 00403C6B 83C4 10 ADD ESP,0x10
* 00403C6E 837E 18 10 CMP DWORD PTR DS:[ESI+0x18],0x10
* 00403C72 897E 14 MOV DWORD PTR DS:[ESI+0x14],EDI
* 00403C75 72 02 JB SHORT .00403C79
* 00403C77 8B1B MOV EBX,DWORD PTR DS:[EBX]
* 00403C79 C6043B 00 MOV BYTE PTR DS:[EBX+EDI],0x0
* 00403C7D 5B POP EBX
* 00403C7E 5F POP EDI
* 00403C7F 8BC6 MOV EAX,ESI
* 00403C81 5E POP ESI
* 00403C82 5D POP EBP
* 00403C83 C2 0800 RETN 0x8
* 00403C86 CC INT3
* 00403C87 CC INT3
* 00403C88 CC INT3
* 00403C89 CC INT3
* 00403C8A CC INT3
* 00403C8B CC INT3
*
* 08BCF938 00403C6B RETURN to .00403C6B from .0052FD84
* 08BCF93C 088DC7F0 ; jichi: target location
* 08BCF940 0000001F ; jichi: target capacity
* 08BCF944 08BCFC68 ; jichi: source size
* 08BCF948 00000010 ; jichi: source size
* 08BCF94C 00000001
* 08BCF950 08BCFC69
* 08BCF954 08BCFC68
* 08BCF958 0000000F
* 08BCF95C 00404870 RETURN to .00404870 from .00403BB0
* 08BCF960 08BCFC68 ; jichi: source text
* 08BCF964 00000010 ; jichi: source size
* 08BCF968 0000000F ; jichi: extra capacity
* 08BCF96C 008B68F8 .008B68F8
* 08BCF970 004AC441 RETURN to .004AC441 from .00404850
* 08BCF974 08BCFC68
* 08BCF978 2AE30C3B
* 08BCF97C 004A5710 .004A5710
* 08BCF980 088D5448
*/
bool attach(ULONG startAddress, ULONG stopAddress)
{
const uint8_t bytes[] = {
0x72, 0x0E, // 00403C42 72 0E JB SHORT .00403C52
0x8B,0x46, 0x04, // 00403C44 8B46 04 MOV EAX,DWORD PTR DS:[ESI+0x4]
0x5F, // 00403C47 5F POP EDI
0xC6,0x00, 0x00, // 00403C48 C600 00 MOV BYTE PTR DS:[EAX],0x0
0x8B,0xC6, // 00403C4B 8BC6 MOV EAX,ESI
0x5E, // 00403C4D 5E POP ESI
0x5D, // 00403C4E 5D POP EBP
0xC2, 0x08,0x00 // 00403C4F C2 0800 RETN 0x8
};
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), startAddress, stopAddress);
if (!addr)
return false;
addr = MemDbg::findEnclosingAlignedFunction(addr);
if (!addr)
return false;
//addr = 0x00403BB0;
HookParam hp;
hp.address=addr;
hp.hook_before=Private::hookBefore;
hp.hook_after=Private::hookafter;
hp.type=EMBED_ABLE|EMBED_DYNA_SJIS;
hp.newlineseperator=L"\\n";
hp.hook_font=F_GetGlyphOutlineA;
return NewHook(hp,"EMbedUnicornOther");
}
} // namespace OtherHook
} // unnamed namespace
bool Unicorn::attach_function() {
auto embed=ScenarioHook::attach(processStartAddress,processStopAddress);
if(embed){
OtherHook::attach(processStartAddress,processStopAddress);
}
return InsertUnicornHook()||embed;
}
bool Unicorn_Anesen::attach_function(){
//[060908][あねせん] あまからツインズ~双姉といっしょ~
//[071012][あねせん] おしえて巫女先生弐
//[071214][あねせん] おしえて巫女先生弐 外伝~ハーレム編~
const BYTE bytes[] = {
0x83 ,0xFF ,0x20,
XX2,
0x0F ,0x84,XX4,
0x81 ,0xFF ,0x40 ,0x81 ,0x00 ,0x00,
0x0F ,0x84
};
auto addr=MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress);
if(addr==0)return false;
addr=MemDbg::findEnclosingAlignedFunction(addr);
if(addr==0)return false;
HookParam hp;
hp.type = USING_STRING;
hp.offset =get_stack(4);
hp.address = addr;
return NewHook(hp, "Unicorn_Anesen");
}