// -----------------------------------------------------------------------------
// memory.cpp
// -----------------------------------------------------------------------------
// Implementation of ProcessMemory. Process lookup uses a snapshot scan,
// module lookup uses the module snapshot of the attached PID, and pattern
// scanning reads the whole region in one batch before matching (fast).
// -----------------------------------------------------------------------------

#include "memory.h"

#include <TlHelp32.h>
#include <cstring>
#include <vector>

namespace core {

bool ProcessMemory::Attach(const std::string& processName)
{
	HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (snapshot == INVALID_HANDLE_VALUE)
		return false;

	PROCESSENTRY32 entry{};
	entry.dwSize = sizeof(entry);

	bool found = false;
	if (Process32First(snapshot, &entry)) {
		do {
			if (processName == entry.szExeFile) {
				m_pid = entry.th32ProcessID;
				m_process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID);
				found = (m_process != nullptr);
				break;
			}
		} while (Process32Next(snapshot, &entry));
	}
	CloseHandle(snapshot);
	return found;
}

void ProcessMemory::Detach()
{
	if (m_process != INVALID_HANDLE_VALUE) {
		CloseHandle(m_process);
		m_process = INVALID_HANDLE_VALUE;
	}
	m_pid = 0;
}

bool ProcessMemory::IsAttached() const
{
	return m_process != INVALID_HANDLE_VALUE;
}

// Returns true while the attached process still runs.
bool ProcessMemory::IsProcessAlive() const
{
	if (m_process == INVALID_HANDLE_VALUE) {
		return false;
	}

	return WaitForSingleObject(m_process, 0) == WAIT_TIMEOUT;
}

DWORD ProcessMemory::GetPid() const
{
	return m_pid;
}

// Looks up a module by name and returns its base address and size.
ModuleInfo ProcessMemory::GetModule(const std::string& moduleName) const
{
	if (m_process == INVALID_HANDLE_VALUE)
		return ModuleInfo{};

	DWORD pid = GetProcessId(m_process);
	HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
	if (snapshot == INVALID_HANDLE_VALUE)
		return ModuleInfo{};

	MODULEENTRY32 entry{};
	entry.dwSize = sizeof(entry);

	ModuleInfo result{};
	if (Module32First(snapshot, &entry)) {
		do {
			if (moduleName == entry.szModule) {
				result.base = reinterpret_cast<uintptr_t>(entry.modBaseAddr);
				result.size = entry.modBaseSize;
				break;
			}
		} while (Module32Next(snapshot, &entry));
	}
	CloseHandle(snapshot);
	return result;
}

std::string ProcessMemory::ReadString(uintptr_t address, size_t maxLength) const
{
	std::string result(maxLength, '\0');
	SIZE_T bytesRead = 0;
	if (ReadProcessMemory(m_process, reinterpret_cast<LPCVOID>(address),
		&result[0], maxLength, &bytesRead)) {
		const size_t nul = result.find('\0');
		if (nul != std::string::npos)
			result.resize(nul);
	} else {
		result.clear();
	}
	return result;
}

uintptr_t ProcessMemory::FindPattern(uintptr_t start, size_t length,
	const char* pattern, const char* mask) const
{
	const size_t patternLength = strlen(mask);

	std::vector<BYTE> buffer(length);
	SIZE_T bytesRead = 0;
	if (!ReadProcessMemory(m_process, reinterpret_cast<LPCVOID>(start),
		buffer.data(), length, &bytesRead))
		return 0;

	for (size_t i = 0; i + patternLength <= bytesRead; i++) {
		bool match = true;
		for (size_t j = 0; j < patternLength; j++) {
			if (mask[j] == 'x' && buffer[i + j] != static_cast<BYTE>(pattern[j])) {
				match = false;
				break;
			}
		}
		if (match)
			return start + i;
	}
	return 0;
}

} // namespace core