diff --git a/GUI/CMakeLists.txt b/GUI/CMakeLists.txt
index a27c832..e8d0276 100644
--- a/GUI/CMakeLists.txt
+++ b/GUI/CMakeLists.txt
@@ -9,7 +9,7 @@ add_executable(Textractor WIN32
 	host/exception.cpp
 	host/host.cpp
 	host/textthread.cpp
-	host/util.cpp
+	host/hookcode.cpp
 	Textractor.rc
 	Textractor.ico
 )
diff --git a/GUI/host/cli.cpp b/GUI/host/cli.cpp
index 47bd8ba..63fc38a 100644
--- a/GUI/host/cli.cpp
+++ b/GUI/host/cli.cpp
@@ -1,5 +1,5 @@
 #include "host.h"
-#include "util.h"
+#include "hookcode.h"
 #include <io.h>
 #include <fcntl.h>
 #include <iostream>
@@ -12,7 +12,16 @@ int main()
 	fflush(stdout);
 	Host::Start([](auto) {}, [](auto) {}, [](auto&) {}, [](auto&) {}, [](TextThread& thread, std::wstring& output)
 	{
-		wprintf_s(L"[%I64X:%I32X:%I64X:%I64X:%I64X:%s:%s] %s\n", thread.handle, thread.tp.processId, thread.tp.addr, thread.tp.ctx, thread.tp.ctx2, thread.name.c_str(), Util::GenerateCode(thread.hp, thread.tp.processId).c_str(), output.c_str());
+		wprintf_s(L"[%I64X:%I32X:%I64X:%I64X:%I64X:%s:%s] %s\n",
+			thread.handle,
+			thread.tp.processId,
+			thread.tp.addr,
+			thread.tp.ctx,
+			thread.tp.ctx2,
+			thread.name.c_str(),
+			HookCode::Generate(thread.hp, thread.tp.processId).c_str(),
+			output.c_str()
+		);
 		fflush(stdout);
 		return false;
 	});
@@ -24,7 +33,7 @@ int main()
 		if (swscanf(input, L"%500s -P%d", command, &processId) != 2) ExitProcess(0);
 		if (_wcsicmp(command, L"attach") == 0) Host::InjectProcess(processId);
 		else if (_wcsicmp(command, L"detach") == 0) Host::DetachProcess(processId);
-		else if (auto hp = Util::ParseCode(command)) Host::InsertHook(processId, hp.value());
+		else if (auto hp = HookCode::Parse(command)) Host::InsertHook(processId, hp.value());
 		else ExitProcess(0);
 	}
 	ExitProcess(0);
diff --git a/GUI/host/exception.cpp b/GUI/host/exception.cpp
index 625c06c..9215f9d 100644
--- a/GUI/host/exception.cpp
+++ b/GUI/host/exception.cpp
@@ -1,4 +1,5 @@
-#include "util.h"
+#include "common.h"
+#include "module.h"
 #include <sstream>
 
 namespace
@@ -36,7 +37,7 @@ namespace
 		errorMsg << std::uppercase << std::hex <<
 			L"Error code: " << exception->ExceptionRecord->ExceptionCode << std::endl <<
 			L"Error address: " << exception->ExceptionRecord->ExceptionAddress << std::endl <<
-			L"Error in module: " << Util::GetModuleFilename((HMODULE)info.AllocationBase).value_or(L"Could not find") << std::endl <<
+			L"Error in module: " << GetModuleFilename((HMODULE)info.AllocationBase).value_or(L"Could not find") << std::endl <<
 			L"Additional info: " << info.AllocationBase << std::endl;
 
 		if (exception->ExceptionRecord->ExceptionCode == 0xE06D7363)
diff --git a/GUI/host/util.cpp b/GUI/host/hookcode.cpp
similarity index 79%
rename from GUI/host/util.cpp
rename to GUI/host/hookcode.cpp
index 0ad4e50..1bba481 100644
--- a/GUI/host/util.cpp
+++ b/GUI/host/hookcode.cpp
@@ -1,5 +1,5 @@
-#include "util.h"
-#include <Psapi.h>
+#include "hookcode.h"
+#include "module.h"
 
 namespace
 {
@@ -250,7 +250,7 @@ namespace
 		if (processId && !(hp.type & MODULE_OFFSET))
 			if (AutoHandle<> process = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, processId))
 				if (MEMORY_BASIC_INFORMATION info = {}; VirtualQueryEx(process, (LPCVOID)hp.address, &info, sizeof(info)))
-					if (auto moduleName = Util::GetModuleFilename(processId, (HMODULE)info.AllocationBase))
+					if (auto moduleName = GetModuleFilename(processId, (HMODULE)info.AllocationBase))
 					{
 						hp.type |= MODULE_OFFSET;
 						hp.address -= (uint64_t)info.AllocationBase;
@@ -265,53 +265,9 @@ namespace
 	}
 }
 
-namespace Util
+namespace HookCode
 {
-	std::optional<std::wstring> GetModuleFilename(DWORD processId, HMODULE module)
-	{
-		std::vector<wchar_t> buffer(MAX_PATH);
-		if (AutoHandle<> process = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, processId)) 
-			if (GetModuleFileNameExW(process, module, buffer.data(), MAX_PATH)) return buffer.data();
-		return {};
-	}
-
-	std::optional<std::wstring> GetModuleFilename(HMODULE module)
-	{
-		std::vector<wchar_t> buffer(MAX_PATH);
-		if (GetModuleFileNameW(module, buffer.data(), MAX_PATH)) return buffer.data();
-		return {};
-	}
-
-	std::vector<std::pair<DWORD, std::optional<std::wstring>>> GetAllProcesses()
-	{
-		std::vector<DWORD> processIds(10000);
-		DWORD spaceUsed = 0;
-		EnumProcesses(processIds.data(), 10000 * sizeof(DWORD), &spaceUsed);
-		std::vector<std::pair<DWORD, std::optional<std::wstring>>> processes;
-		for (int i = 0; i < spaceUsed / sizeof(DWORD); ++i) processes.push_back({ processIds[i], Util::GetModuleFilename(processIds[i]) });
-		return processes;
-	}
-
-	std::optional<std::wstring> GetClipboardText()
-	{
-		if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) return {};
-		if (!OpenClipboard(NULL)) return {};
-
-		std::optional<std::wstring> text;
-		if (AutoHandle<Functor<GlobalUnlock>> clipboard = GetClipboardData(CF_UNICODETEXT)) text = (wchar_t*)GlobalLock(clipboard);
-		CloseClipboard();
-		return text;
-	}
-
-	std::optional<std::wstring> StringToWideString(const std::string& text, UINT encoding)
-	{
-		std::vector<wchar_t> buffer(text.size() + 1);
-		if (int length = MultiByteToWideChar(encoding, 0, text.c_str(), text.size() + 1, buffer.data(), buffer.size())) 
-			return std::wstring(buffer.data(), length - 1);
-		return {};
-	}
-
-	std::optional<HookParam> ParseCode(std::wstring code)
+	std::optional<HookParam> Parse(std::wstring code)
 	{
 		if (code[0] == L'/') code.erase(0, 1); // legacy/AGTH compatibility
 		if (code[0] == L'R') return ParseRCode(code.erase(0, 1));
@@ -319,7 +275,7 @@ namespace Util
 		return {};
 	}
 
-	std::wstring GenerateCode(HookParam hp, DWORD processId)
+	std::wstring Generate(HookParam hp, DWORD processId)
 	{
 		return hp.type & DIRECT_READ ? GenerateRCode(hp) : GenerateHCode(hp, processId);
 	}
diff --git a/GUI/host/hookcode.h b/GUI/host/hookcode.h
new file mode 100644
index 0000000..42c352a
--- /dev/null
+++ b/GUI/host/hookcode.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include "common.h"
+#include "types.h"
+
+namespace HookCode
+{
+	std::optional<HookParam> Parse(std::wstring code);
+	std::wstring Generate(HookParam hp, DWORD processId = 0);
+}
diff --git a/GUI/host/host.cpp b/GUI/host/host.cpp
index b055e14..cdcf0f2 100644
--- a/GUI/host/host.cpp
+++ b/GUI/host/host.cpp
@@ -1,6 +1,6 @@
 #include "host.h"
 #include "defs.h"
-#include "util.h"
+#include "hookcode.h"
 #include "../texthook/texthook.h"
 
 extern const wchar_t* ALREADY_INJECTED;
@@ -48,7 +48,7 @@ namespace
 
 		Host::HookEventHandler OnHookFound = [](HookParam hp, std::wstring text)
 		{
-			Host::AddConsoleOutput(Util::GenerateCode(hp) + L": " + text);
+			Host::AddConsoleOutput(HookCode::Generate(hp) + L": " + text);
 		};
 
 	private:
@@ -107,9 +107,9 @@ namespace
 					std::wstring wide = info.text;
 					if (wide.size() > STRING) OnHookFound(info.hp, std::move(info.text));
 					info.hp.type &= ~USING_UNICODE;
-					if (auto converted = Util::StringToWideString((char*)info.text, info.hp.codepage))
+					if (auto converted = StringToWideString((char*)info.text, info.hp.codepage))
 						if (converted->size() > STRING) OnHookFound(info.hp, std::move(converted.value()));
-					if (auto converted = Util::StringToWideString((char*)info.text, info.hp.codepage = CP_UTF8))
+					if (auto converted = StringToWideString((char*)info.text, info.hp.codepage = CP_UTF8))
 						if (converted->size() > STRING) OnHookFound(info.hp, std::move(converted.value()));
 				}
 				break;
@@ -122,7 +122,7 @@ namespace
 				case HOST_NOTIFICATION_TEXT:
 				{
 					auto info = *(ConsoleOutputNotif*)buffer;
-					Host::AddConsoleOutput(Util::StringToWideString(info.message).value());
+					Host::AddConsoleOutput(StringToWideString(info.message));
 				}
 				break;
 				default:
@@ -179,8 +179,12 @@ namespace Host
 				for (int retry = 0; !clipboardText && retry < 3; ++retry) // retry loop in case something else is using the clipboard
 				{
 					Sleep(10);
-					if (clipboardText = Util::GetClipboardText()) GetThread(clipboard).AddSentence(std::move(clipboardText.value()));
+					if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) continue;
+					if (!OpenClipboard(NULL)) continue;
+					if (AutoHandle<Functor<GlobalUnlock>> clipboard = GetClipboardData(CF_UNICODETEXT)) clipboardText = (wchar_t*)GlobalLock(clipboard);
+					CloseClipboard();
 				}
+				if (clipboardText) GetThread(clipboard).AddSentence(std::move(clipboardText.value()));
 			}
 			throw;
 		}).detach();
diff --git a/GUI/host/textthread.cpp b/GUI/host/textthread.cpp
index f5c7db6..b08556e 100644
--- a/GUI/host/textthread.cpp
+++ b/GUI/host/textthread.cpp
@@ -1,6 +1,5 @@
 #include "textthread.h"
 #include "host.h"
-#include "util.h"
 
 extern const wchar_t* INVALID_CODEPAGE;
 
@@ -16,7 +15,7 @@ static bool RemoveRepetition(std::wstring& text)
 
 TextThread::TextThread(ThreadParam tp, HookParam hp, std::optional<std::wstring> name) :
 	handle(threadCounter++),
-	name(name.value_or(Util::StringToWideString(hp.name).value())),
+	name(name.value_or(StringToWideString(hp.name))),
 	tp(tp),
 	hp(hp)
 {}
@@ -48,7 +47,7 @@ void TextThread::Push(BYTE* data, int length)
 
 	if (hp.type & HEX_DUMP) for (int i = 0; i < length; i += sizeof(short)) buffer.append(FormatString(L"%04hX ", *(short*)(data + i)));
 	else if (hp.type & USING_UNICODE) buffer.append((wchar_t*)data, length / sizeof(wchar_t));
-	else if (auto converted = Util::StringToWideString(std::string((char*)data, length), hp.codepage ? hp.codepage : Host::defaultCodepage)) buffer.append(converted.value());
+	else if (auto converted = StringToWideString(std::string((char*)data, length), hp.codepage ? hp.codepage : Host::defaultCodepage)) buffer.append(converted.value());
 	else Host::AddConsoleOutput(INVALID_CODEPAGE);
 	if (hp.type & FULL_STRING) buffer.push_back(L'\n');
 	lastPushTime = GetTickCount();
diff --git a/GUI/host/util.h b/GUI/host/util.h
deleted file mode 100644
index aa8eaa4..0000000
--- a/GUI/host/util.h
+++ /dev/null
@@ -1,15 +0,0 @@
-#pragma once
-
-#include "common.h"
-#include "types.h"
-
-namespace Util
-{
-	std::optional<std::wstring> GetModuleFilename(DWORD processId, HMODULE module = NULL);
-	std::optional<std::wstring> GetModuleFilename(HMODULE module = NULL);
-	std::vector<std::pair<DWORD, std::optional<std::wstring>>> GetAllProcesses();
-	std::optional<std::wstring> GetClipboardText();
-	std::optional<std::wstring> StringToWideString(const std::string& text, UINT encoding = CP_UTF8);
-	std::optional<HookParam> ParseCode(std::wstring code);
-	std::wstring GenerateCode(HookParam hp, DWORD processId = 0);
-}
diff --git a/GUI/main.cpp b/GUI/main.cpp
index 9beb513..81c08cc 100644
--- a/GUI/main.cpp
+++ b/GUI/main.cpp
@@ -1,5 +1,5 @@
 #include "mainwindow.h"
-#include "host/util.h"
+#include "module.h"
 #include <winhttp.h>
 #include <QApplication>
 
@@ -24,7 +24,7 @@ int main(int argc, char *argv[])
 					}
 	}).detach();
 
-	QDir::setCurrent(QFileInfo(S(Util::GetModuleFilename().value())).absolutePath());
+	QDir::setCurrent(QFileInfo(S(GetModuleFilename().value())).absolutePath());
 
 	QApplication app(argc, argv);
 	app.setFont(QFont("MS Shell Dlg 2", 10));
diff --git a/GUI/mainwindow.cpp b/GUI/mainwindow.cpp
index 120b427..611a52c 100644
--- a/GUI/mainwindow.cpp
+++ b/GUI/mainwindow.cpp
@@ -1,7 +1,8 @@
 #include "mainwindow.h"
 #include "ui_mainwindow.h"
 #include "defs.h"
-#include "host/util.h"
+#include "module.h"
+#include "host/hookcode.h"
 #include <shellapi.h>
 #include <QStringListModel>
 #include <QScrollBar>
@@ -113,7 +114,7 @@ MainWindow::MainWindow(QWidget *parent) :
 
 	AttachConsole(ATTACH_PARENT_PROCESS);
 	WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), CL_OPTIONS, wcslen(CL_OPTIONS), DUMMY, NULL);
-	auto processes = Util::GetAllProcesses();
+	auto processes = GetAllProcesses();
 	int argc;
 	std::unique_ptr<LPWSTR[], Functor<LocalFree>> argv(CommandLineToArgvW(GetCommandLineW(), &argc));
 	for (int i = 0; i < argc; ++i)
@@ -136,7 +137,7 @@ MainWindow::MainWindow(QWidget *parent) :
 					attachTargets.insert(S(process.split(" , ")[0]));
 
 			if (!attachTargets.empty())
-				for (auto [processId, processName] : Util::GetAllProcesses())
+				for (auto [processId, processName] : GetAllProcesses())
 					if (processName && attachTargets.count(processName.value()) > 0 && alreadyAttached.count(processId) == 0) Host::InjectProcess(processId);
 		}
 	}).detach();
@@ -159,7 +160,7 @@ void MainWindow::ProcessConnected(DWORD processId)
 {
 	alreadyAttached.insert(processId);
 
-	QString process = S(Util::GetModuleFilename(processId).value_or(L"???"));
+	QString process = S(GetModuleFilename(processId).value_or(L"???"));
 	QMetaObject::invokeMethod(this, [this, process, processId]
 	{
 		ui->processCombo->addItem(QString::number(processId, 16).toUpper() + ": " + QFileInfo(process).fileName());
@@ -173,7 +174,7 @@ void MainWindow::ProcessConnected(DWORD processId)
 	auto hookList = std::find_if(allProcesses.rbegin(), allProcesses.rend(), [&](QString hookList) { return hookList.contains(process); });
 	if (hookList != allProcesses.rend())
 		for (auto hookInfo : hookList->split(" , "))
-			if (auto hp = Util::ParseCode(S(hookInfo))) Host::InsertHook(processId, hp.value());
+			if (auto hp = HookCode::Parse(S(hookInfo))) Host::InsertHook(processId, hp.value());
 			else swscanf_s(S(hookInfo).c_str(), L"|%I64d:%I64d:%[^\n]", &savedThreadCtx, &savedThreadCtx2, savedThreadCode, (unsigned)std::size(savedThreadCode));
 }
 
@@ -187,7 +188,7 @@ void MainWindow::ProcessDisconnected(DWORD processId)
 
 void MainWindow::ThreadAdded(TextThread& thread)
 {
-	std::wstring threadCode = Util::GenerateCode(thread.hp, thread.tp.processId);
+	std::wstring threadCode = HookCode::Generate(thread.hp, thread.tp.processId);
 	bool savedMatch = savedThreadCtx == thread.tp.ctx && savedThreadCtx2 == thread.tp.ctx2 && savedThreadCode == threadCode;
 	if (savedMatch) savedThreadCtx = savedThreadCtx2 = savedThreadCode[0] = 0;
 	QMetaObject::invokeMethod(this, [this, savedMatch, ttString = TextThreadString(thread) + S(FormatString(L" (%s)", threadCode))]
@@ -290,7 +291,7 @@ std::optional<std::wstring> MainWindow::UserSelectedProcess()
 void MainWindow::AttachProcess()
 {
 	QMultiHash<QString, DWORD> allProcesses;
-	for (auto [processId, processName] : Util::GetAllProcesses())
+	for (auto [processId, processName] : GetAllProcesses())
 		if (processName && (showSystemProcesses || processName->find(L":\\Windows\\") == std::wstring::npos))
 			allProcesses.insert(QFileInfo(S(processName.value())).fileName(), processId);
 
@@ -348,7 +349,7 @@ void MainWindow::DetachProcess()
 
 void MainWindow::ForgetProcess()
 {
-	std::optional<std::wstring> processName = Util::GetModuleFilename(GetSelectedProcessId());
+	std::optional<std::wstring> processName = GetModuleFilename(GetSelectedProcessId());
 	if (!processName) processName = UserSelectedProcess();
 	DetachProcess();
 	if (!processName) return;
@@ -369,7 +370,7 @@ void MainWindow::AddHook(QString hook)
 {
 	if (QString hookCode = QInputDialog::getText(this, ADD_HOOK, CODE_INFODUMP, QLineEdit::Normal, hook, &ok, Qt::WindowCloseButtonHint); ok)
 		if (hookCode.startsWith("S") || hookCode.startsWith("/S")) FindHooks();
-		else if (auto hp = Util::ParseCode(S(hookCode))) try { Host::InsertHook(GetSelectedProcessId(), hp.value()); } catch (std::out_of_range) {}
+		else if (auto hp = HookCode::Parse(S(hookCode))) try { Host::InsertHook(GetSelectedProcessId(), hp.value()); } catch (std::out_of_range) {}
 		else Host::AddConsoleOutput(INVALID_CODE);
 }
 
@@ -403,7 +404,7 @@ void MainWindow::RemoveHooks()
 
 void MainWindow::SaveHooks()
 {
-	auto processName = Util::GetModuleFilename(GetSelectedProcessId());
+	auto processName = GetModuleFilename(GetSelectedProcessId());
 	if (!processName) return;
 	QHash<uint64_t, QString> hookCodes;
 	for (int i = 0; i < ui->ttCombo->count(); ++i)
@@ -412,12 +413,12 @@ void MainWindow::SaveHooks()
 		if (tp.processId == GetSelectedProcessId())
 		{
 			HookParam hp = Host::GetThread(tp).hp;
-			if (!(hp.type & HOOK_ENGINE)) hookCodes[tp.addr] = S(Util::GenerateCode(hp, tp.processId));
+			if (!(hp.type & HOOK_ENGINE)) hookCodes[tp.addr] = S(HookCode::Generate(hp, tp.processId));
 		}
 	}
 	auto hookInfo = QStringList() << S(processName.value()) << hookCodes.values();
 	ThreadParam tp = current->tp;
-	if (tp.processId == GetSelectedProcessId()) hookInfo << QString("|%1:%2:%3").arg(tp.ctx).arg(tp.ctx2).arg(S(Util::GenerateCode(current->hp, tp.processId)));
+	if (tp.processId == GetSelectedProcessId()) hookInfo << QString("|%1:%2:%3").arg(tp.ctx).arg(tp.ctx2).arg(S(HookCode::Generate(current->hp, tp.processId)));
 	QTextFile(HOOK_SAVE_FILE, QIODevice::WriteOnly | QIODevice::Append).write((hookInfo.join(" , ") + "\n").toUtf8());
 }
 
@@ -489,7 +490,7 @@ void MainWindow::FindHooks()
 			layout.addRow(label, spinBox);
 			connect(spinBox, qOverload<int>(&QSpinBox::valueChanged), [&value](int newValue) { value = newValue; });
 		}
-		QLineEdit boundInput(QFileInfo(S(Util::GetModuleFilename(GetSelectedProcessId()).value_or(L""))).fileName(), &dialog);
+		QLineEdit boundInput(QFileInfo(S(GetModuleFilename(GetSelectedProcessId()).value_or(L""))).fileName(), &dialog);
 		layout.addRow(SEARCH_MODULE, &boundInput);
 		for (auto [value, label] : Array<uintptr_t&, const char*>{
 			{ sp.minAddress, MIN_ADDRESS },
@@ -530,7 +531,7 @@ void MainWindow::FindHooks()
 	try
 	{
 		Host::FindHooks(processId, sp, 
-			[hooks, filter](HookParam hp, std::wstring text) { if (std::regex_search(text, filter)) *hooks << sanitize(S(Util::GenerateCode(hp) + L" => " + text)); });
+			[hooks, filter](HookParam hp, std::wstring text) { if (std::regex_search(text, filter)) *hooks << sanitize(S(HookCode::Generate(hp) + L" => " + text)); });
 	}
 	catch (std::out_of_range) { return; }
 	std::thread([this, hooks]
diff --git a/extensions/extrawindow.cpp b/extensions/extrawindow.cpp
index 5c92ca6..546d211 100644
--- a/extensions/extrawindow.cpp
+++ b/extensions/extrawindow.cpp
@@ -2,7 +2,6 @@
 #include "extension.h"
 #include "ui_extrawindow.h"
 #include "defs.h"
-#include "util.h"
 #include "blockmarkup.h"
 #include <fstream>
 #include <process.h>
diff --git a/extensions/googletranslate.cpp b/extensions/googletranslate.cpp
index e6ce5c8..1722ace 100644
--- a/extensions/googletranslate.cpp
+++ b/extensions/googletranslate.cpp
@@ -1,6 +1,5 @@
 #include "extension.h"
 #include "network.h"
-#include "util.h"
 #include <ctime>
 #include <QStringList>
 
diff --git a/extensions/lua.cpp b/extensions/lua.cpp
index b41b336..f636b74 100644
--- a/extensions/lua.cpp
+++ b/extensions/lua.cpp
@@ -1,6 +1,5 @@
 #include "qtcommon.h"
 #include "extension.h"
-#include "util.h"
 #include <fstream>
 #include <QPlainTextEdit>
 
diff --git a/extensions/network.cpp b/extensions/network.cpp
index 5899b18..fc7e9a9 100644
--- a/extensions/network.cpp
+++ b/extensions/network.cpp
@@ -1,5 +1,4 @@
 #include "network.h"
-#include "util.h"
 
 HttpRequest::HttpRequest(
 	const wchar_t* agentName,
diff --git a/extensions/removerepeatphrase.cpp b/extensions/removerepeatphrase.cpp
index 3785832..f8dc5ee 100644
--- a/extensions/removerepeatphrase.cpp
+++ b/extensions/removerepeatphrase.cpp
@@ -2,34 +2,34 @@
 
 std::vector<int> GenerateSuffixArray(const std::wstring& text)
 {
-	std::vector<int> identity(text.size());
-	for (int i = 0; i < text.size(); ++i) identity[i] = i;
-	std::vector<int> suffixArray = identity;
+	std::vector<int> suffixArray(text.size());
+	for (int i = 0; i < text.size(); ++i) suffixArray[i] = i;
 	// The below code is a more efficient way of doing this:
 	// std::sort(suffixArray.begin(), suffixArray.end(), [&](int a, int b) { return wcscmp(text.c_str() + a, text.c_str() + b) > 0; });
 	std::stable_sort(suffixArray.begin(), suffixArray.end(), [&](int a, int b) { return text[a] > text[b]; });
-	std::vector<int> classes(text.begin(), text.end());
+	std::vector<int> eqClasses(text.begin(), text.end());
+	std::vector<int> count(text.size());
 	for (int length = 1; length < text.size(); length *= 2)
 	{
 		// Determine equivalence class up to length, by checking length / 2 equivalence of suffixes and their following length / 2 suffixes
-		std::vector<int> oldClasses = classes;
-		classes[suffixArray[0]] = 0;
+		std::vector<int> prevEqClasses = eqClasses;
+		eqClasses[suffixArray[0]] = 0;
 		for (int i = 1; i < text.size(); ++i)
 		{
 			int currentSuffix = suffixArray[i];
 			int lastSuffix = suffixArray[i - 1];
-			if (currentSuffix + length < text.size() && oldClasses[currentSuffix] == oldClasses[lastSuffix] &&
-				oldClasses[currentSuffix + length / 2] == oldClasses.at(lastSuffix + length / 2)) // not completely certain that this will stay in range
-				classes[currentSuffix] = classes[lastSuffix];
-			else classes[currentSuffix] = i;
+			if (currentSuffix + length < text.size() && prevEqClasses[currentSuffix] == prevEqClasses[lastSuffix] &&
+				prevEqClasses[currentSuffix + length / 2] == prevEqClasses.at(lastSuffix + length / 2)) // not completely certain that this will stay in range
+				eqClasses[currentSuffix] = eqClasses[lastSuffix];
+			else eqClasses[currentSuffix] = i;
 		}
 
 		// Sort within equivalence class based on order of following suffix after length (orders up to length * 2)
-		std::vector<int> count = identity;
+		for (int i = 0; i < text.size(); ++i) count[i] = i;
 		for (auto suffix : std::vector(suffixArray))
 		{
 			int precedingSuffix = suffix - length;
-			if (precedingSuffix >= 0) suffixArray[count[classes[precedingSuffix]]++] = precedingSuffix;
+			if (precedingSuffix >= 0) suffixArray[count[eqClasses[precedingSuffix]]++] = precedingSuffix;
 		}
 	}
 	for (int i = 0; i + 1 < text.size(); ++i)
diff --git a/extensions/translatewrapper.cpp b/extensions/translatewrapper.cpp
index 9725cad..43e6a93 100644
--- a/extensions/translatewrapper.cpp
+++ b/extensions/translatewrapper.cpp
@@ -1,7 +1,6 @@
 #include "qtcommon.h"
 #include "extension.h"
 #include "defs.h"
-#include "util.h"
 #include "blockmarkup.h"
 #include "network.h"
 #include <map>
diff --git a/extensions/util.h b/extensions/util.h
deleted file mode 100644
index 3291367..0000000
--- a/extensions/util.h
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma once
-
-#include "common.h"
-
-inline std::wstring StringToWideString(const std::string& text)
-{
-	std::vector<wchar_t> buffer(text.size() + 1);
-	MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, buffer.data(), buffer.size());
-	return buffer.data();
-}
-
-inline std::string WideStringToString(const std::wstring& text)
-{
-	std::vector<char> buffer((text.size() + 1) * 4);
-	WideCharToMultiByte(CP_UTF8, 0, text.c_str(), -1, buffer.data(), buffer.size(), nullptr, nullptr);
-	return buffer.data();
-}
diff --git a/include/common.h b/include/common.h
index a303bb2..1eeb2c0 100644
--- a/include/common.h
+++ b/include/common.h
@@ -114,6 +114,28 @@ inline std::wstring FormatString(const wchar_t* format, const Args&... args)
 }
 #pragma warning(pop)
 
+inline std::optional<std::wstring> StringToWideString(const std::string& text, UINT encoding)
+{
+	std::vector<wchar_t> buffer(text.size() + 1);
+	if (int length = MultiByteToWideChar(encoding, 0, text.c_str(), text.size() + 1, buffer.data(), buffer.size()))
+		return std::wstring(buffer.data(), length - 1);
+	return {};
+}
+
+inline std::wstring StringToWideString(const std::string& text)
+{
+	std::vector<wchar_t> buffer(text.size() + 1);
+	MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, buffer.data(), buffer.size());
+	return buffer.data();
+}
+
+inline std::string WideStringToString(const std::wstring& text)
+{
+	std::vector<char> buffer((text.size() + 1) * 4);
+	WideCharToMultiByte(CP_UTF8, 0, text.c_str(), -1, buffer.data(), buffer.size(), nullptr, nullptr);
+	return buffer.data();
+}
+
 template <typename... Args>
 inline void TEXTRACTOR_MESSAGE(const wchar_t* format, const Args&... args) { MessageBoxW(NULL, FormatString(format, args...).c_str(), L"Textractor", MB_OK); }
 
diff --git a/include/module.h b/include/module.h
new file mode 100644
index 0000000..ddf508e
--- /dev/null
+++ b/include/module.h
@@ -0,0 +1,27 @@
+#include "common.h"
+#include <Psapi.h>
+
+inline std::optional<std::wstring> GetModuleFilename(DWORD processId, HMODULE module = NULL)
+{
+	std::vector<wchar_t> buffer(MAX_PATH);
+	if (AutoHandle<> process = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, processId))
+		if (GetModuleFileNameExW(process, module, buffer.data(), MAX_PATH)) return buffer.data();
+	return {};
+}
+
+inline std::optional<std::wstring> GetModuleFilename(HMODULE module = NULL)
+{
+	std::vector<wchar_t> buffer(MAX_PATH);
+	if (GetModuleFileNameW(module, buffer.data(), MAX_PATH)) return buffer.data();
+	return {};
+}
+
+inline std::vector<std::pair<DWORD, std::optional<std::wstring>>> GetAllProcesses()
+{
+	std::vector<DWORD> processIds(10000);
+	DWORD spaceUsed = 0;
+	EnumProcesses(processIds.data(), 10000 * sizeof(DWORD), &spaceUsed);
+	std::vector<std::pair<DWORD, std::optional<std::wstring>>> processes;
+	for (int i = 0; i < spaceUsed / sizeof(DWORD); ++i) processes.push_back({ processIds[i], GetModuleFilename(processIds[i]) });
+	return processes;
+}