This commit is contained in:
test123456654321 2024-10-27 22:09:44 +08:00
parent 8592939737
commit 623b208549
11 changed files with 1379 additions and 1343 deletions

View File

@ -175,15 +175,6 @@ bool isPauseKeyPressed()
{
return WinKey::isKeyControlPressed() || WinKey::isKeyShiftPressed() && !WinKey::isKeyReturnPressed();
}
inline UINT64 djb2_n2(const unsigned char *str, size_t len, UINT64 hash = 5381)
{
int i = 0;
while (len--)
{
hash = ((hash << 5) + hash) + (*str++); // hash * 33 + c
}
return hash;
}
std::unordered_map<UINT64, std::wstring> translatecache;
bool check_is_thread_selected(const ThreadParam &tp)
{
@ -200,7 +191,7 @@ bool check_embed_able(const ThreadParam &tp)
bool waitforevent(UINT32 timems, const ThreadParam &tp, const std::wstring &origin)
{
char eventname[1000];
sprintf(eventname, LUNA_EMBED_notify_event, GetCurrentProcessId(), djb2_n2((const unsigned char *)(origin.c_str()), origin.size() * 2));
sprintf(eventname, LUNA_EMBED_notify_event, GetCurrentProcessId(), simplehash::djb2_n2((const unsigned char *)(origin.c_str()), origin.size() * 2));
auto event = win_event(eventname);
while (timems)
{

View File

@ -38,59 +38,10 @@ bool InsertEMEHook()
namespace
{
// LRU template class, recv two type params: key & value
template <typename Key>
class LRUCache
{
private:
// cache capacity
size_t _capacity = 0;
// list _keys中key的指向位置
std::unordered_map<Key, typename std::list<Key>::iterator> _cache;
std::list<Key> _keys;
public:
// construct function
LRUCache(size_t size) : _capacity(size){};
bool contains(Key key)
{
auto it = _cache.find(key);
if (it == _cache.end())
{
return false;
}; // 返回默认值
_keys.splice(_keys.begin(), _keys, it->second);
return true;
}
void put(Key key)
{
auto it = _cache.find(key);
if (it != _cache.end())
{
_keys.splice(_keys.begin(), _keys, it->second);
return;
}
if (_keys.size() == _capacity)
{
Key oldKey = _keys.back();
_keys.pop_back();
_cache.erase(oldKey);
}
_keys.push_front(key);
_cache[key] = _keys.begin();
}
};
bool takeout()
{
//https://vndb.org/v6187
//みちくさLoitering on the way
// https://vndb.org/v6187
// みちくさLoitering on the way
trigger_fun = [](LPVOID addr, hook_stack *stack)
{
@ -108,11 +59,8 @@ public:
hp.filter_fun = [](LPVOID data, size_t *size, HookParam *)
{
auto xx = std::string((char *)data, *size);
static LRUCache<std::string> last(10);
if (last.contains(xx))
return false;
last.put(xx);
return true;
static lru_cache<std::string> last(10);
return !last.touch(xx);
};
return NewHook(hp, "takeout");
};

View File

@ -1,4 +1,4 @@
#include"LunaSoft.h"
#include "LunaSoft.h"
/** jichi 12/27/2014 LunaSoft
* Sample game: [141226] [LunaSoft] -- /hsn8@46C5EF
*
@ -55,7 +55,7 @@
*/
// Remove: \n\s*
// This is dangerous since \n could appear within SJIS
//static bool LunaSoftFilter(LPVOID data, size_t *size, HookParam *)
// static bool LunaSoftFilter(LPVOID data, size_t *size, HookParam *)
//{
// size_t len = *size;
// char *str = reinterpret_cast<char *>(data),
@ -83,82 +83,63 @@ bool InsertLunaSoftHook()
0xcc, // 0046c57e cc int3
0xcc, // 0046c57f cc int3
0x55, // 0046c580 55 push ebp ; jichi: text in arg1
0x8b,0xec, // 0046c581 8bec mov ebp,esp
0x83,0xec, 0x08, // 0046c583 83ec 08 sub esp,0x8
0x89,0x4d, 0xf8, // 0046c586 894d f8 mov dword ptr ss:[ebp-0x8],ecx
0x8b,0x4d, 0xf8, // 0046c589 8b4d f8 mov ecx,dword ptr ss:[ebp-0x8]
0x83,0xc1, 0x1c, // 0046c58c 83c1 1c add ecx,0x1c
0x8b, 0xec, // 0046c581 8bec mov ebp,esp
0x83, 0xec, 0x08, // 0046c583 83ec 08 sub esp,0x8
0x89, 0x4d, 0xf8, // 0046c586 894d f8 mov dword ptr ss:[ebp-0x8],ecx
0x8b, 0x4d, 0xf8, // 0046c589 8b4d f8 mov ecx,dword ptr ss:[ebp-0x8]
0x83, 0xc1, 0x1c, // 0046c58c 83c1 1c add ecx,0x1c
0xe8 // 0046c58f e8 2cebf9ff call .0040b0c0
};
enum { addr_offset = 2 };
enum
{
addr_offset = 2
};
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress);
//GROWL(addr);
if (!addr) {
// GROWL(addr);
if (!addr)
{
ConsoleOutput("LunaSoft: pattern not found");
return false;
}
HookParam hp;
hp.address = addr + addr_offset;
hp.offset =get_stack(1);
hp.offset = get_stack(1);
hp.type = USING_STRING;
//hp.filter_fun = LunaSoftFilter; // remove \n
// hp.filter_fun = LunaSoftFilter; // remove \n
ConsoleOutput("INSERT LunaSoft");
return NewHook(hp, "LunaSoft");
// There are no GDI functions anyway
//ConsoleOutput("LunaSoft: disable GDI hooks");
// ConsoleOutput("LunaSoft: disable GDI hooks");
//
}
bool InsertXXkata(){
//アイリスフィールド
bool InsertXXkata()
{
// アイリスフィールド
//素晴らしき国家の築き方
//浮遊都市の作り方
//正しい性奴隷の使い方
// 素晴らしき国家の築き方
// 浮遊都市の作り方
// 正しい性奴隷の使い方
//HSNc@0:user32.dll:wsprintfA
auto addr = GetProcAddress(GetModuleHandleW(L"user32.dll"),"wsprintfA");
if (addr == 0)return false;
// HSNc@0:user32.dll:wsprintfA
auto addr = GetProcAddress(GetModuleHandleW(L"user32.dll"), "wsprintfA");
if (addr == 0)
return false;
HookParam hp;
hp.address=(uint64_t)addr ;
hp.type=USING_STRING|NO_CONTEXT;
hp.offset=get_stack(3);
hp.address = (uint64_t)addr;
hp.type = USING_STRING | NO_CONTEXT;
hp.offset = get_stack(3);
hp.filter_fun = all_ascii_Filter;
return NewHook(hp, "XXkata");
}
namespace { // unnamed
namespace ScenarioHook {
namespace Private {
class DataCache // LRU policy, hashtable not used for simplicity
namespace
{ // unnamed
namespace ScenarioHook
{
int capacity_;
std::list<std::string> stack_; // priority stack
public:
explicit DataCache(int capacity = 100)
: capacity_(capacity) {} //{ stack_.reserve(capacity); }
bool contains(const std::string &data) const
{ return stack_.end() != std::find(stack_.begin(), stack_.end(), data); }
std::string retain(const std::string &data)
namespace Private
{
auto p = std::find(stack_.begin(), stack_.end(), data);
if (p == stack_.end()) {
if (stack_.size() == capacity_)
stack_.pop_back();
stack_.push_front(data);
return data;
} else {
if (p != stack_.begin())
stack_.splice(stack_.begin(), stack_, p);
return *p;
}
}
};
DataCache cache_; // this is used to make sure that same translation will have the same address
lru_cache<std::string> cache_(100);
/**
* Sample game: , scenario return address: 0x42f6dc
@ -177,34 +158,34 @@ namespace Private {
* 0042F6EB E9 5E010000 JMP lus004.0042F84E
* 0042F6F0 8B4D 10 MOV ECX,DWORD PTR SS:[EBP+0x10]
*/
bool hookBefore(hook_stack*s,void* data1, size_t* len,uintptr_t*role)
bool hookBefore(hook_stack *s, void *data1, size_t *len, uintptr_t *role)
{
auto text = (LPCSTR)s->stack[1]; // arg1
if (!text || !*text) // || Util::allAscii(text))
return 0;
std::string oldData = text;
if (cache_.contains(oldData))
if (cache_.exists(oldData))
return 0;
// 0042F6DC 8B45 10 MOV EAX,DWORD PTR SS:[EBP+0x10] ; jichi: retaddr
// 0042F6DF 50 PUSH EAX
ULONG retaddr = s->stack[0];
* role = Engine::OtherRole;
*role = Engine::OtherRole;
if (*(DWORD *)retaddr == 0x5010458b)
*role = Engine::ScenarioRole;
write_string_overwrite(data1,len,oldData);
write_string_overwrite(data1, len, oldData);
return 1;
}
void hookafter1(hook_stack*s,void* data1, size_t len){
void hookafter1(hook_stack *s, void *data1, size_t len)
{
static std::string newData;
newData=std::string((char*)data1,len);
newData = cache_.retain(newData);
newData = std::string((char *)data1, len);
newData = cache_.put(newData).first;
s->stack[1] = (ULONG)newData.c_str(); // arg1
}
} // namespace Private
} // namespace Private
/**
/**
* Sample game:
*
* Debugging method: Hook to all function that accessing the text
@ -492,14 +473,14 @@ namespace Private {
* 0012FEC4 |00BF0752
* 0012FEC8 |00000113
*/
bool attach(ULONG startAddress, ULONG stopAddress) // attach scenario
{
bool attach(ULONG startAddress, ULONG stopAddress) // attach scenario
{
ULONG addr1, addr2;
{
const uint8_t bytes1[] = {
0x89,0x88, 0xac,0x02,0x00,0x00, // 00487c1d 8988 ac020000 mov dword ptr ds:[eax+0x2ac],ecx
0x8b,0x55, 0xfc, // 00487c23 8b55 fc mov edx,dword ptr ss:[ebp-0x4]
0xd9,0xee // 00487c26 d9ee fldz
0x89, 0x88, 0xac, 0x02, 0x00, 0x00, // 00487c1d 8988 ac020000 mov dword ptr ds:[eax+0x2ac],ecx
0x8b, 0x55, 0xfc, // 00487c23 8b55 fc mov edx,dword ptr ss:[ebp-0x4]
0xd9, 0xee // 00487c26 d9ee fldz
};
addr1 = MemDbg::findBytes(bytes1, sizeof(bytes1), startAddress, stopAddress);
if (!addr1)
@ -507,14 +488,14 @@ bool attach(ULONG startAddress, ULONG stopAddress) // attach scenario
addr1 = MemDbg::findEnclosingAlignedFunction(addr1);
if (!addr1)
return false;
//addr1 = 0x00487c10;
// addr1 = 0x00487c10;
}
{
const uint8_t bytes2[] = {
0x83,0xe0, 0x0c, // 0042b266 83e0 0c and eax,0xc
0x83,0xf8, 0x04, // 0042b269 83f8 04 cmp eax,0x4
0x83, 0xe0, 0x0c, // 0042b266 83e0 0c and eax,0xc
0x83, 0xf8, 0x04, // 0042b269 83f8 04 cmp eax,0x4
0x75, 0x07, // 0042b26c 75 07 jnz short lus004.0042b275
0xd9,0xee // 0042b26e d9ee fldz
0xd9, 0xee // 0042b26e d9ee fldz
};
addr2 = MemDbg::findBytes(bytes2, sizeof(bytes2), startAddress, stopAddress);
if (!addr2)
@ -522,29 +503,27 @@ bool attach(ULONG startAddress, ULONG stopAddress) // attach scenario
addr2 = MemDbg::findEnclosingAlignedFunction(addr2);
if (!addr2)
return false;
//addr2 = 0x0042b1f0;
// addr2 = 0x0042b1f0;
}
HookParam hp;
hp.address=addr1;
hp.hook_before=Private::hookBefore;
hp.hook_after=Private::hookafter1;
hp.type=EMBED_ABLE|EMBED_DYNA_SJIS;
auto succ=NewHook(hp,"EMBEDLUNA");
hp.address=addr2;
succ|=NewHook(hp,"EMBEDLUNA");
hp.address = addr1;
hp.hook_before = Private::hookBefore;
hp.hook_after = Private::hookafter1;
hp.type = EMBED_ABLE | EMBED_DYNA_SJIS;
auto succ = NewHook(hp, "EMBEDLUNA");
hp.address = addr2;
succ |= NewHook(hp, "EMBEDLUNA");
return succ;
}
} // namespace ScenarioHook
}
} // namespace ScenarioHook
} // unnamed namespace
bool LunaSoft::attach_function()
{
bool LunaSoft::attach_function() {
bool b1= InsertLunaSoftHook();
bool b2=InsertXXkata();
bool embed=ScenarioHook::attach(processStartAddress, processStopAddress);
return b1||b2||embed;
bool b1 = InsertLunaSoftHook();
bool b2 = InsertXXkata();
bool embed = ScenarioHook::attach(processStartAddress, processStopAddress);
return b1 || b2 || embed;
}

View File

@ -954,25 +954,7 @@ struct TextArgument // first argument of the scenario hook
&& !::strstr(text, "\x81\x5e"); // ""
}
};
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 {
namespace Private {
@ -1049,7 +1031,7 @@ namespace Private {
arg->size = data_.size() + 1;
arg->capacity = arg->size;
hashes_.insert(hashCharArray(arg->text));
hashes_.insert(simplehash::hashCharArray(arg->text));
}
bool hookBefore(hook_stack*s,void* data, size_t* len1,uintptr_t*role)
{
@ -1072,7 +1054,7 @@ namespace Private {
return false;
auto arg = (TextArgument *)s->stack[0]; // arg1
if (!arg || !arg->isValid()
|| hashes_.find(hashCharArray(arg->text)) != hashes_.end())
|| hashes_.find(simplehash::hashCharArray(arg->text)) != hashes_.end())
return false;
if (arg->size < 0xf && split > 0 && !isOtherText(arg->text))
return false;

View File

@ -1,4 +1,4 @@
#include"Unicorn.h"
#include "Unicorn.h"
/**
* jichi 9/16/2013: a-unicorn / gesen18
* See (CaoNiMaGeBi): http://tieba.baidu.com/p/2586681823
@ -16,72 +16,40 @@ bool InsertUnicornHook()
{
// pattern: 2bce8bf8
const BYTE bytes[] = {
0x2b,0xce, // sub ecx,esi ; hook here
0x8b,0xf8 // mov edi,eax
0x2b, 0xce, // sub ecx,esi ; hook here
0x8b, 0xf8 // mov edi,eax
};
//enum { addr_offset = 0 };
// enum { addr_offset = 0 };
ULONG range = min(processStopAddress - processStartAddress, MAX_REL_ADDR);
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStartAddress + range);
if (!addr) {
if (!addr)
{
ConsoleOutput("Unicorn: pattern not exist");
return false;
}
HookParam hp;
hp.type = NO_CONTEXT | DATA_INDIRECT;
hp.offset=get_reg(regs::edi);
hp.offset = get_reg(regs::edi);
hp.address = addr;
//index = SearchPattern(processStartAddress, size,ins, sizeof(ins));
//GROWL_DWORD2(base, index);
// 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) {}
namespace
{ // unnamed
// A simple but very inefficient implementation for LRU cache.
bool contains(uint64_t h) const
{ return std::find(hashes_.begin(), hashes_.end(), h) != hashes_.end(); }
void add(uint64_t h)
namespace ScenarioHook
{
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 {
lru_cache<uint64_t> textCache_(30); // capacity = 30
TextHashCache textCache_(30); // capacity = 30
namespace Private {
namespace Private
{
class TextStorage
{
@ -90,12 +58,15 @@ namespace Private {
newData_;
int lineCount_;
bool saved_;
public:
TextStorage()
: text_(nullptr), lineCount_(0), saved_(false) {}
bool isEmpty() const
{ return lineCount_ == 0; }
{
return lineCount_ == 0;
}
void clear()
{
@ -117,7 +88,7 @@ namespace Private {
std::string sourceData_;
LPSTR targetText_;
bool hookBefore(hook_stack*s,void* data, size_t* len1,uintptr_t*role)
bool hookBefore(hook_stack *s, void *data, size_t *len1, uintptr_t *role)
{
// Sample game: 三極姫4 ~天華繚乱 天命の恋絵巻~
// 004B76BB 51 PUSH ECX
@ -161,28 +132,30 @@ namespace Private {
// 0049A85D 0F8E E3000000 JLE .0049A946
auto retaddr = s->stack[0];
* role = 0;
//if (retaddr == 0x4b7728)
*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 (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
// else
// return true;
if (*role != Engine::ScenarioRole && !textStorage_.isEmpty()) {
if (*role != Engine::ScenarioRole && !textStorage_.isEmpty())
{
textStorage_.restore();
textStorage_.clear();
}
if (!*role)
return false;
auto text = (LPSTR)*(DWORD *)(s->ecx + textOffset_); // [ecx+0x114]
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()) {
if (!textStorage_.isEmpty())
{
textStorage_.restore();
textStorage_.clear();
}
@ -196,35 +169,37 @@ namespace Private {
if (*role == Engine::NameRole)
strReplace(oldData, "\x81\x40", "");
//oldData.replace("\x81\x40", ""); // remove spaces in the middle of names
return write_string_overwrite(data,len1,oldData);
// oldData.replace("\x81\x40", ""); // remove spaces in the middle of names
return write_string_overwrite(data, len1, oldData);
}
void hookafter2(hook_stack*s,void* data, size_t len){
void hookafter2(hook_stack *s, void *data, size_t len)
{
auto newData =std::string((char*)data,len);
auto newData = std::string((char *)data, len);
auto retaddr = s->stack[0];
int role = 0;
//if (retaddr == 0x4b7728)
// 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 (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
// else
// return true;
if (role != Engine::ScenarioRole && !textStorage_.isEmpty()) {
if (role != Engine::ScenarioRole && !textStorage_.isEmpty())
{
textStorage_.restore();
textStorage_.clear();
}
if (!role)
return ;
auto text = (LPSTR)*(DWORD *)(s->ecx + textOffset_); // [ecx+0x114]
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()) {
return;
if (!textStorage_.isEmpty())
{
textStorage_.restore();
textStorage_.clear();
}
@ -236,31 +211,32 @@ namespace Private {
oldData = text;
if (role == Engine::NameRole)
strReplace(oldData, "\x81\x40", "");
//oldData.replace("\x81\x40", ""); // remove spaces in the middle of names
if (oldData == newData) {
// oldData.replace("\x81\x40", ""); // remove spaces in the middle of names
if (oldData == newData)
{
if (textStorageEnabled)
textStorage_.clear();
return ;
return;
}
if (textStorageEnabled)
textStorage_.save();
sourceData_ = newData;
targetText_ = (LPSTR)s->stack[1]; // arg1
textCache_.add(hashByteArraySTD(newData));
textCache_.put(simplehash::hashByteArraySTD(newData));
}
bool hookAfter(hook_stack*s,void* data, size_t* len1,uintptr_t*role)
bool hookAfter(hook_stack *s, void *data, size_t *len1, uintptr_t *role)
{
if (targetText_)
{
if (targetText_) {
::strcpy(targetText_, sourceData_.c_str());
targetText_ = nullptr;
}
return false;
}
} // namespace Private
} // namespace Private
/**
/**
* Sample text
*
* Sample game: 4
@ -320,58 +296,58 @@ namespace Private {
* 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)
{
// 三極姫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)) {
for (; isTextSeparator(p); p += ::strlen(p))
{
lineCount_++;
p += 12;
data.append(p);
}
oldData_ = std::string(text, p - text);
return data;
}
}
void Private::TextStorage::save()
{
void Private::TextStorage::save()
{
if (lineCount_ <= 1)
return;
LPSTR p = text_ + ::strlen(text_);
while (isTextSeparator(p)) {
while (isTextSeparator(p))
{
p += 12 + 1; // +1 for the extra 0xa
if (size_t size = ::strlen(p)) {
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()))
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.
@ -476,16 +452,16 @@ bool Private::TextStorage::restore()
* 004A6CD8 CC INT3
* 004A6CD9 CC INT3
*/
bool attach(ULONG startAddress, ULONG stopAddress)
{
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
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
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)
@ -505,44 +481,46 @@ bool attach(ULONG startAddress, ULONG stopAddress)
// 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");
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 ScenarioHook
namespace OtherHook {
namespace Private {
namespace OtherHook
{
namespace Private
{
//bool isSkippedText(LPCSTR text)
// 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)
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)
// if (*(DWORD *)retaddr != 0xeb0cc483)
// return true;
//retaddr = s->stack[7]; // parent caller
// retaddr = s->stack[7]; // parent caller
// Scenario/name/other threads to skip:
// - 0x404062 // there are so many other texts in this thread
@ -565,29 +543,31 @@ namespace Private {
// 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)
// if (retaddr != 0x4769f8 && retaddr != 0x4135ba)
// return true;
switch (*(WORD *)retaddr) {
switch (*(WORD *)retaddr)
{
case 0xeed9: // 004769F8 D9EE FLDZ
case 0x08eb: // 004135BA EB 08 JMP SHORT .004135C4
break;
default: return false;
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)))
if (!text || size <= 2 // avoid painting individual character
|| ::strlen(text) != size || all_ascii(text) || ScenarioHook::textCache_.exists(simplehash::hashCharArray(text)))
//|| !q->isTextDecodable(text)) // avoid re-translation
//|| isascii(text[::strlen(text) - 2])
//|| isSkippedText(text))
return false;
enum { role = Engine::OtherRole };
enum
{
role = Engine::OtherRole
};
return write_string_overwrite(data,len,text);
/* //oldData.replace("\\n", "\n"); // Remove new line. FIXME: automatically adjust line width
return write_string_overwrite(data, len, text);
/* //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;
@ -597,18 +577,18 @@ namespace Private {
return true;*/
}
void hookafter(hook_stack*s,void* data, size_t len){
void hookafter(hook_stack *s, void *data, size_t len)
{
auto newData =std::string((char*)data,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
} // namespace Private
/**
/**
* Sample game: 6
* Function found by debugging caller of GetGlyphOutlineA.
* 0052F2DC CC INT3
@ -788,17 +768,17 @@ namespace Private {
* 08BCF97C 004A5710 .004A5710
* 08BCF980 088D5448
*/
bool attach(ULONG startAddress, ULONG stopAddress)
{
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]
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
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
0xC2, 0x08, 0x00 // 00403C4F C2 0800 RETN 0x8
};
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), startAddress, stopAddress);
if (!addr)
@ -806,47 +786,50 @@ bool attach(ULONG startAddress, ULONG stopAddress)
addr = MemDbg::findEnclosingAlignedFunction(addr);
if (!addr)
return false;
//addr = 0x00403BB0;
// 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);
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");
}
return InsertUnicornHook()||embed;
} // 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(){
bool Unicorn_Anesen::attach_function()
{
//[060908][あねせん] あまからツインズ~双姉といっしょ~
//[071012][あねせん] おしえて巫女先生弐
//[071214][あねせん] おしえて巫女先生弐 外伝~ハーレム編~
const BYTE bytes[] = {
0x83 ,0xFF ,0x20,
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;
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.offset = get_stack(4);
hp.address = addr;
return NewHook(hp, "Unicorn_Anesen");

View File

@ -1772,6 +1772,48 @@ namespace
return write_string_overwrite(data, len, s);
}
bool F010005F00E036000_1(void *data, size_t *len, HookParam *hp)
{
static lru_cache<std::string> cache(5);
static std::string last;
auto s = std::string((char *)data, *len);
if (endWith(last, s))
{
last = s;
return false;
}
if (cache.touch(s))
{
last = s;
return false;
}
last = s;
return write_string_overwrite(data, len, s);
}
bool F010005F00E036000(void *data, size_t *len, HookParam *hp)
{
if (!F010005F00E036000_1(data, len, hp))
return false;
static std::string last;
auto s = std::string((char *)data, *len);
auto parse = [](std::string &s)
{
strReplace(s, u8"", u8"");
strReplace(s, u8"", u8"");
strReplace(s, u8"", u8"");
return s;
};
if (startWith(s, last))
{
write_string_overwrite(data, len, parse(s.substr(last.size(), s.size() - last.size())));
last = s;
return true;
}
last = s;
return write_string_overwrite(data, len, parse(s));
}
bool F0100FC2019346000(void *data, size_t *len, HookParam *hp)
{
StringFilter((char *)data, len, "#n", 2);
@ -3143,6 +3185,8 @@ namespace
{0x801f67c0, {CODEC_UTF32, 1, 0, 0, F01007FD00DB20000, "01007FD00DB20000", "1.0.0"}},
{0x802a76c0, {CODEC_UTF32, 0, 0, 0, F01007FD00DB20000, "01007FD00DB20000", "1.0.0"}},
{0x8031fc80, {CODEC_UTF32, 1, 0, 0, F01007FD00DB20000, "01007FD00DB20000", "1.0.0"}},
// 真 流行り神1・2パック
{0x80072720, {CODEC_UTF8, 1, 0, 0, F010005F00E036000, "010005F00E036000", "1.0.0"}},
// 真流行り神3
{0x80082F70, {0, 0xb, 0, TF0100AA1013B96000, 0, "0100AA1013B96000", nullptr}}, //"1.0.0", "1.0.1"

View File

@ -164,15 +164,6 @@ C_LUNA_API void Luna_useembed(DWORD pid, uint64_t address, uint64_t ctx1, uint64
}
}
inline UINT64 djb2_n2(const unsigned char *str, size_t len, UINT64 hash = 5381)
{
int i = 0;
while (len--)
{
hash = ((hash << 5) + hash) + (*str++); // hash * 33 + c
}
return hash;
}
C_LUNA_API void Luna_embedcallback(DWORD pid, LPCWSTR text, LPCWSTR trans)
{
auto sm = Host::GetEmbedSharedMem(pid);
@ -180,7 +171,7 @@ C_LUNA_API void Luna_embedcallback(DWORD pid, LPCWSTR text, LPCWSTR trans)
return;
wcsncpy(sm->text, trans, ARRAYSIZE(sm->text));
char eventname[1000];
sprintf(eventname, LUNA_EMBED_notify_event, pid, djb2_n2((const unsigned char *)(text), wcslen(text) * 2));
sprintf(eventname, LUNA_EMBED_notify_event, pid, simplehash::djb2_n2((const unsigned char *)(text), wcslen(text) * 2));
win_event event1(eventname);
event1.signal(true);
}

89
include/lrucache.hpp Normal file
View File

@ -0,0 +1,89 @@
/*
* File: lrucache.hpp
* Author: Alexander Ponomarev
*
* Created on June 20, 2013, 5:09 PM
*/
#ifndef _LRUCACHE_HPP_INCLUDED_
#define _LRUCACHE_HPP_INCLUDED_
#include <unordered_map>
#include <list>
#include <cstddef>
#include <stdexcept>
template <typename key_t, typename value_t = int>
class lru_cache
{
public:
typedef typename std::pair<key_t, value_t> key_value_pair_t;
typedef typename std::list<key_value_pair_t>::iterator list_iterator_t;
lru_cache(size_t max_size) : _max_size(max_size)
{
}
const key_value_pair_t &put(const key_t &key, const value_t &&value = {})
{
auto it = _cache_items_map.find(key);
_cache_items_list.emplace_front(key_value_pair_t(key, value));
if (it != _cache_items_map.end())
{
_cache_items_list.erase(it->second);
_cache_items_map.erase(it);
}
_cache_items_map[key] = _cache_items_list.begin();
if (_cache_items_map.size() > _max_size)
{
auto last = _cache_items_list.end();
last--;
_cache_items_map.erase(last->first);
_cache_items_list.pop_back();
}
return *_cache_items_list.begin();
}
const value_t &get(const key_t &key)
{
auto it = _cache_items_map.find(key);
if (it == _cache_items_map.end())
{
throw std::range_error("There is no such key in cache");
}
else
{
_cache_items_list.splice(_cache_items_list.begin(), _cache_items_list, it->second);
return it->second->second;
}
}
bool exists(const key_t &key) const
{
return _cache_items_map.find(key) != _cache_items_map.end();
}
bool touch(const key_t &key)
{
try
{
auto _ = get(key);
return true;
}
catch (...)
{
put(key);
return false;
}
}
size_t size() const
{
return _cache_items_map.size();
}
private:
std::list<key_value_pair_t> _cache_items_list;
std::unordered_map<key_t, list_iterator_t> _cache_items_map;
size_t _max_size;
};
#endif /* _LRUCACHE_HPP_INCLUDED_ */

View File

@ -42,3 +42,4 @@
#include "hookcode.h"
#include "texthook.h"
#include "winevent.hpp"
#include "lrucache.hpp"

View File

@ -51,7 +51,7 @@ inline std::vector<StringT> strSplit_impl(const StringT &s, const StringT &delim
template <class StringT>
inline bool endWith_impl(const StringT &s, const StringT &s2)
{
if ((s.size() >= s2.size()) && (s.substr(s.size() - s2.size(), s2.size()) == s2))
if (s2.size() && (s.size() >= s2.size()) && (s.substr(s.size() - s2.size(), s2.size()) == s2))
{
return true;
}
@ -61,7 +61,7 @@ inline bool endWith_impl(const StringT &s, const StringT &s2)
template <class StringT>
inline bool startWith_impl(const StringT &s, const StringT &s2)
{
if ((s.size() >= s2.size()) && (s.substr(0, s2.size()) == s2))
if (s2.size() && (s.size() >= s2.size()) && (s.substr(0, s2.size()) == s2))
{
return true;
}
@ -215,7 +215,7 @@ size_t u32strlen(uint32_t *data)
return s;
}
std::string wcasta(const std::wstring& x)
std::string wcasta(const std::wstring &x)
{
std::string xx;
for (auto c : x)
@ -223,7 +223,7 @@ std::string wcasta(const std::wstring& x)
return xx;
}
std::wstring acastw(const std::string& x)
std::wstring acastw(const std::string &x)
{
std::wstring xx;
for (auto c : x)

View File

@ -142,3 +142,31 @@ struct SafeFptr
return ptr(std::forward<Args>(args)...);
}
};
namespace simplehash
{
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 unsigned 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((const unsigned char *)b.c_str(), b.size(), h);
}
inline UINT64 hashCharArray(const void *lp)
{
return djb2(reinterpret_cast<const UINT8 *>(lp));
}
}