#include <iostream>
#include <windows.h>
#include <psapi.h>
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
#include <ctime>
#include <Zydis/Zydis.h>

// Ensure that you have the correct library linkage
#pragma comment(lib, "psapi.lib")

class Logger {
public:
    explicit Logger(const std::string& filepath) : logFilePath(filepath) { InitLog(); }

    void Log(const std::string& message, const std::string& level = "INFO") {
        std::ofstream logFile(logFilePath, std::ios::app);
        if (logFile.is_open()) {
            logFile << "[" << GetFormattedTime() << "] [" << level << "] " << message << "\n";
        }
    }

private:
    std::string logFilePath;

    void InitLog() {
        std::ofstream logFile(logFilePath, std::ios::trunc);
        if (logFile.is_open()) {
            logFile << "[" << GetFormattedTime() << "] [INIT] Log initiated\n";
        }
    }

    std::string GetFormattedTime() {
        std::time_t currentTime = std::time(nullptr);
        char buffer[100];
        struct tm timeInfo;
        localtime_s(&timeInfo, &currentTime);
        std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &timeInfo);
        return std::string(buffer);
    }
};

DWORD GetProcessIdByName(const std::wstring& processName) {
    DWORD processIds[1024], cbNeeded;
    if (!EnumProcesses(processIds, sizeof(processIds), &cbNeeded)) {
        return 0;
    }

    DWORD processCount = cbNeeded / sizeof(DWORD);
    for (DWORD i = 0; i < processCount; ++i) {
        if (processIds[i] != 0) {
            HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processIds[i]);
            if (hProcess) {
                HMODULE hMod;
                DWORD cbNeededModule;
                WCHAR szProcessName[MAX_PATH];
                if (EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeededModule)) {
                    GetModuleBaseNameW(hProcess, hMod, szProcessName, sizeof(szProcessName) / sizeof(WCHAR));
                    if (!_wcsicmp(szProcessName, processName.c_str())) {
                        CloseHandle(hProcess);
                        return processIds[i];
                    }
                }
                CloseHandle(hProcess);
            }
        }
    }
    return 0;
}

void DisassembleAndFindHashes(Logger& logger, HANDLE hProcess, uintptr_t baseAddress, SIZE_T size) {
    std::vector<BYTE> buffer(size);
    SIZE_T bytesRead;
    if (!ReadProcessMemory(hProcess, (LPCVOID)baseAddress, buffer.data(), size, &bytesRead)) {
        logger.Log("Failed to read memory at address 0x" + std::to_string(baseAddress) + ". Error: " + std::to_string(GetLastError()), "ERROR");
        return;
    }

    ZydisDecoder decoder;
    ZydisFormatter formatter;
    if (ZYAN_FAILED(ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_ADDRESS_WIDTH_64))) {
        logger.Log("Failed to initialize Zydis decoder", "ERROR");
        return;
    }
    if (ZYAN_FAILED(ZydisFormatterInit(&formatter, ZYDIS_FORMATTER_STYLE_INTEL))) {
        logger.Log("Failed to initialize Zydis formatter", "ERROR");
        return;
    }

    size_t offset = 0;
    ZydisDecodedInstruction instruction;

    ZyanU64 runtime_address = baseAddress;

    while (ZYAN_SUCCESS(ZydisDecoderDecodeBuffer(&decoder, buffer.data() + offset, bytesRead - offset, &instruction))) {
        char formattedInstruction[256];
        if (ZYAN_SUCCESS(ZydisFormatterFormatInstruction(&formatter, &instruction, formattedInstruction, sizeof(formattedInstruction)))) {
            // Log the instruction with the runtime address
            logger.Log("0x" + std::to_string(runtime_address) + ": " + std::string(formattedInstruction));

            // Simple example: look for calls to `SHA256` or similar (Note: This is illustrative; actual implementation depends on the disassembly)
            if (std::string(formattedInstruction).find("SHA256") != std::string::npos ||
                std::string(formattedInstruction).find("MD5") != std::string::npos) {
                logger.Log("Potential Integrity Check at 0x" + std::to_string(runtime_address) + ": " + formattedInstruction, "HASH_CHECK");
            }
        }
        else {
            logger.Log("Failed to format instruction at 0x" + std::to_string(runtime_address), "ERROR");
        }
        offset += instruction.length;
        runtime_address += instruction.length;
    }
}

void AnalyzeModulesForIntegrityChecks(Logger& logger, HANDLE hProcess) {
    HMODULE hMods[1024];
    DWORD cbNeeded;
    if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
        for (size_t i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
            MODULEINFO moduleInfo;
            if (GetModuleInformation(hProcess, hMods[i], &moduleInfo, sizeof(moduleInfo))) {
                logger.Log("Analyzing module at Base Address 0x" + std::to_string((uintptr_t)moduleInfo.lpBaseOfDll), "MODULE");
                DisassembleAndFindHashes(logger, hProcess, (uintptr_t)moduleInfo.lpBaseOfDll, moduleInfo.SizeOfImage);
            }
        }
    }
}

int main() {
    std::wstring processName = L"YourProcess.exe"; // Replace with your process name
    Logger logger("integrity_log.log");

    DWORD pid = GetProcessIdByName(processName);
    if (pid == 0) {
        logger.Log("Process not found: " + std::string(processName.begin(), processName.end()), "ERROR");
        return 1;
    }

    HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
    if (!hProcess) {
        logger.Log("Failed to open process with PID: " + std::to_string(pid) + ", Error code: " + std::to_string(GetLastError()), "ERROR");
        return 1;
    }

    AnalyzeModulesForIntegrityChecks(logger, hProcess);

    CloseHandle(hProcess);
    return 0;
}