diff --git a/deploy.ps1 b/deploy.ps1
index 8457ef5..ff115c6 100644
--- a/deploy.ps1
+++ b/deploy.ps1
@@ -15,6 +15,7 @@ foreach ($arch in @("86", "64")) {
 		"Google Translate.dll",
 		"Regex Filter.dll",
 		"Remove Repetition.dll",
+		"Replacer.dll",
 		"Thread Linker.dll",
 		"platforms",
 		"styles"
diff --git a/extensions/CMakeLists.txt b/extensions/CMakeLists.txt
index 6f141cb..78cbe4e 100644
--- a/extensions/CMakeLists.txt
+++ b/extensions/CMakeLists.txt
@@ -11,6 +11,7 @@ add_library(Extra\ Window SHARED extrawindow.cpp extensionimpl.cpp)
 add_library(Google\ Translate SHARED googletranslate.cpp extensionimpl.cpp)
 add_library(Regex\ Filter SHARED regexfilter.cpp extensionimpl.cpp)
 add_library(Remove\ Repetition SHARED removerepeat.cpp extensionimpl.cpp)
+add_library(Replacer SHARED replacer.cpp extensionimpl.cpp)
 add_library(Thread\ Linker SHARED threadlinker.cpp extensionimpl.cpp)
 
 add_executable(Extension_Tests extensiontester.cpp)
diff --git a/extensions/removerepeat.cpp b/extensions/removerepeat.cpp
index 22aa47c..0af7847 100644
--- a/extensions/removerepeat.cpp
+++ b/extensions/removerepeat.cpp
@@ -66,6 +66,6 @@ TEST(
 		ProcessSentence(empty, { &tester });
 		ProcessSentence(one, { &tester });
 		ProcessSentence(normal, { &tester });
-		assert(empty == L"" && one == L" " && normal == L"This is a normal sentence. はい");
+		assert(empty == L"" && one == L" " && normal == L"This is a normal sentence. はい");
 	}
 );
diff --git a/extensions/replacer.cpp b/extensions/replacer.cpp
new file mode 100644
index 0000000..493f123
--- /dev/null
+++ b/extensions/replacer.cpp
@@ -0,0 +1,100 @@
+#include "extension.h"
+#include "defs.h"
+#include "text.h"
+#include <cwctype>
+#include <fstream>
+
+std::shared_mutex m;
+
+struct
+{
+public:
+	void Put(std::wstring original, std::wstring replacement)
+	{
+		Node* current = &root;
+		for (auto c : original)
+			if (Ignore(c));
+			else if (auto& next = current->next[c]) current = next.get();
+			else current = (next = std::make_unique<Node>()).get();
+		current->value = replacement;
+	}
+
+	std::pair<int, std::wstring> Lookup(const std::wstring& text)
+	{
+		int length = 0;
+		Node* current = &root;
+		for (auto c : text)
+			if (Ignore(c)) ++length;
+			else if (auto& next = current->next[c]) ++length, current = next.get();
+			else break;
+		return { length, current->value };
+	}
+
+private:
+	static bool Ignore(wchar_t c)
+	{
+		return c <= 0x20 || std::iswspace(c);
+	}
+
+	struct Node
+	{
+		std::unordered_map<wchar_t, std::unique_ptr<Node>> next;
+		std::wstring value;
+	} root;
+} replacementTrie;
+
+void Parse(const std::wstring& file)
+{
+	std::lock_guard l(m);
+	size_t end = 0;
+	while (true)
+	{
+		size_t original = file.find(L"|ORIG|", end);
+		size_t becomes = file.find(L"|BECOMES|", original);
+		end = file.find(L"|END|", becomes);
+		if (end != std::wstring::npos) replacementTrie.Put(file.substr(original + 6, becomes - original - 6), file.substr(becomes + 9, end - becomes - 9));
+		else break;
+	}
+}
+
+BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
+{
+	switch (ul_reason_for_call)
+	{
+	case DLL_PROCESS_ATTACH:
+	{
+		std::vector<BYTE> file(std::istreambuf_iterator<char>(std::ifstream(REPLACE_SAVE_FILE, std::ios::binary)), {});
+		Parse(std::wstring((wchar_t*)file.data(), file.size() / sizeof(wchar_t)));
+	}
+	break;
+	case DLL_PROCESS_DETACH:
+	{
+	}
+	break;
+	}
+	return TRUE;
+}
+
+bool ProcessSentence(std::wstring& sentence, SentenceInfo)
+{
+	std::shared_lock l(m);
+	for (int i = 0; i < sentence.size(); ++i)
+		if (sentence.size() > 10000) return false; // defend against infinite looping
+		else if (auto[length, replacement] = replacementTrie.Lookup(sentence.substr(i)); !replacement.empty()) sentence.replace(i, length, replacement);
+	return true;
+}
+
+TEST(
+	{
+		Parse(LR"(|ORIG|さよなら|BECOMES|goodbye|END|
+|ORIG|バカ|BECOMES|idiot|END|
+|ORIG|こんにちは|BECOMES|hello|END|)");
+		std::wstring replaced = LR"(hello 
+ さよなら バカ こんにちは)";
+		ProcessSentence(replaced, { nullptr });
+		assert(replaced.rfind(L"さよなら") == std::wstring::npos);
+		assert(replaced.rfind(L"バカ") == std::wstring::npos);
+		assert(replaced.rfind(L"こんにちは") == std::wstring::npos);
+		replacementTrie = {};
+	}
+);
diff --git a/include/defs.h b/include/defs.h
index 542e81a..00680a6 100644
--- a/include/defs.h
+++ b/include/defs.h
@@ -25,6 +25,7 @@ constexpr auto CONFIG_FILE = u8"Textractor.ini";
 constexpr auto HOOK_SAVE_FILE = u8"SavedHooks.txt";
 constexpr auto GAME_SAVE_FILE = u8"SavedGames.txt";
 constexpr auto EXTEN_SAVE_FILE = u8"SavedExtensions.txt";
+constexpr auto REPLACE_SAVE_FILE = u8"SavedReplacements.txt";
 
 // Functions
 
diff --git a/release/SavedReplacements.txt b/release/SavedReplacements.txt
new file mode 100644
index 0000000..f53e807
Binary files /dev/null and b/release/SavedReplacements.txt differ