#include "extension.h" #include #include #include #include extern const wchar_t* REPLACER_INSTRUCTIONS; constexpr auto REPLACE_SAVE_FILE = u8"SavedReplacements.txt"; std::atomic replaceFileLastWrite = {}; std::shared_mutex m; class Trie { public: Trie(std::unordered_map replacements) { for (const auto& [original, replacement] : replacements) { Node* current = &root; for (auto ch : original) if (!Ignore(ch)) current = Next(current, ch); if (current != &root) current->value = owningStorage.insert(owningStorage.end(), replacement.c_str(), replacement.c_str() + replacement.size() + 1) - owningStorage.begin(); } } std::wstring Replace(const std::wstring& sentence) const { std::wstring result; for (int i = 0; i < sentence.size();) { std::wstring_view replacement(sentence.c_str() + i, 1); int originalLength = 1; const Node* current = &root; for (int j = i; current && j <= sentence.size(); ++j) { if (current->value >= 0) { replacement = owningStorage.data() + current->value; originalLength = j - i; } if (!Ignore(sentence[j])) current = Next(current, sentence[j]); } result += replacement; i += originalLength; } return result; } bool Empty() { return root.charMap.empty(); } private: static bool Ignore(wchar_t ch) { return ch <= 0x20 || std::iswspace(ch); } template static Node* Next(Node* node, wchar_t ch) { auto it = std::lower_bound(node->charMap.begin(), node->charMap.end(), ch, [](const auto& one, auto two) { return one.first < two; }); if (it != node->charMap.end() && it->first == ch) return it->second.get(); if constexpr (!std::is_const_v) return node->charMap.insert(it, { ch, std::make_unique() })->second.get(); return nullptr; } struct Node { std::vector>> charMap; ptrdiff_t value = -1; } root; std::vector owningStorage; } trie = { {} }; std::unordered_map Parse(std::wstring_view replacementScript) { std::unordered_map replacements; for (size_t end = 0; ;) { size_t original = replacementScript.find(L"|ORIG|", end); size_t becomes = replacementScript.find(L"|BECOMES|", original); if ((end = replacementScript.find(L"|END|", becomes)) == std::wstring::npos) break; replacements[std::wstring(replacementScript.substr(original + 6, becomes - original - 6))] = replacementScript.substr(becomes + 9, end - becomes - 9); } return replacements; } void UpdateReplacements() { try { if (replaceFileLastWrite.exchange(std::filesystem::last_write_time(REPLACE_SAVE_FILE)) == std::filesystem::last_write_time(REPLACE_SAVE_FILE)) return; std::vector file(std::istreambuf_iterator(std::ifstream(REPLACE_SAVE_FILE, std::ios::binary)), {}); std::scoped_lock l(m); trie = Trie(Parse({ (wchar_t*)file.data(), file.size() / sizeof(wchar_t) })); } catch (std::filesystem::filesystem_error) { replaceFileLastWrite.store({}); } } BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { UpdateReplacements(); if (trie.Empty()) { std::ofstream(REPLACE_SAVE_FILE, std::ios::binary).write((char*)REPLACER_INSTRUCTIONS, wcslen(REPLACER_INSTRUCTIONS) * sizeof(wchar_t)); _spawnlp(_P_DETACH, "notepad", "notepad", REPLACE_SAVE_FILE, NULL); // show file to user } } break; case DLL_PROCESS_DETACH: { } break; } return TRUE; } bool ProcessSentence(std::wstring& sentence, SentenceInfo) { UpdateReplacements(); std::shared_lock l(m); sentence = trie.Replace(sentence); return true; } TEST( { auto replacements = Parse(LR"( |ORIG|さよなら|BECOMES|goodbye |END|Ignore this text And this text ツ   |ORIG|バカ|BECOMES|idiot|END| |ORIG|こんにちは |BECOMES| hello|END||ORIG|delete this|BECOMES||END|)"); assert(replacements.size() == 4); std::wstring original = LR"(Don't replace this  さよなら バカ こんにちは delete this)"; std::wstring replaced = Trie(std::move(replacements)).Replace(original); assert(replaced == L"Don't replace thisgoodbye idiot hello"); } );