Привожу код DLL, которая инжектится в процесс, необходимо добавить функциональность, чтобы бинарный код user.ini менялся на бинарный код test.ini, при этом оставляя имя файла user.ini:
#include "pch.h"
#include <Windows.h>
#include <fstream>
#include <vector>
#include <string>
#include <memory>
#include <thread>
#include <iomanip>
#include <sstream>
#include <mutex>
#include <chrono>

// Thread-safe logger class
class Logger {
public:
    explicit Logger(const std::string& filename) : m_logFile(filename, std::ios::out) {
        if (!m_logFile.is_open()) {
            throw std::runtime_error("Failed to open log file.");
        }
    }
    // Logs a message with a timestamp
    void Log(const std::string& message) {
        std::lock_guard<std::mutex> lock(m_mutex);
        auto now = std::chrono::system_clock::now();
        auto now_time_t = std::chrono::system_clock::to_time_t(now);
        std::tm localtm;
        localtime_s(&localtm, &now_time_t);

        m_logFile << std::put_time(&localtm, " %H:%M:%S") << " > " << message << "\n";
        m_logFile.flush();
    }

private:
    std::mutex m_mutex;
    std::ofstream m_logFile;
};

Logger g_logger("patcher_log.txt");

// Converts a pointer to a string representation
std::string PtrToStr(const void* ptr) {
    std::stringstream ss;
    ss << "0x"
        << std::hex << std::uppercase
        << std::setw(8) << std::setfill('0')
        << reinterpret_cast<uintptr_t>(ptr);
    return ss.str();
}

// Wrapper for automatic HANDLE closing
struct HandleDeleter {
    void operator()(HANDLE handle) {
        if (handle != NULL && handle != INVALID_HANDLE_VALUE) {
            CloseHandle(handle);
        }
    }
};

using UniqueHandle = std::unique_ptr<void, HandleDeleter>;

// RAII class for safe memory protection setting
class ScopedVirtualProtect {
public:
    ScopedVirtualProtect(HANDLE hProcess, LPCVOID addr, SIZE_T size, DWORD newProtect)
        : m_hProcess(hProcess), m_addr(const_cast<LPVOID>(addr)), m_size(size), m_oldProtect(0) {
        if (!VirtualProtectEx(hProcess, m_addr, m_size, newProtect, &m_oldProtect)) {
            g_logger.Log("VirtualProtectEx failed to change protection at address " + PtrToStr(addr) + ".");
        }
    }

    ~ScopedVirtualProtect() noexcept {
        DWORD temp;
        if (!VirtualProtectEx(m_hProcess, m_addr, m_size, m_oldProtect, &temp)) {
            g_logger.Log("Failed to revert protection at address " + PtrToStr(m_addr) + ".");
        }
    }

private:
    HANDLE m_hProcess;
    LPVOID m_addr;
    SIZE_T m_size;
    DWORD m_oldProtect;
};

// Apply a patch to a process
bool ApplyPatch(HANDLE hProcess, BYTE* addr, size_t offset, BYTE patchByte) {
    ScopedVirtualProtect protect(hProcess, addr + offset, 1, PAGE_EXECUTE_READWRITE);
    SIZE_T bytesWritten;
    if (!WriteProcessMemory(hProcess, addr + offset, &patchByte, 1, &bytesWritten)) {
        g_logger.Log("Failed to write patch to " + PtrToStr(addr + offset) + ".");
        return false;
    }

    g_logger.Log("Patch successfully applied at " + PtrToStr(addr + offset) + ".");
    return true;
}

// Search and apply a patch to a signature in process memory
bool PatchSignatureInProcessMemory(HANDLE hProcess, const std::vector<BYTE>& signature, BYTE patchByte) {
    MEMORY_BASIC_INFORMATION mbi{};
    std::vector<BYTE> buffer;
    for (BYTE* addr = nullptr; VirtualQueryEx(hProcess, addr, &mbi, sizeof(mbi)) && reinterpret_cast<uintptr_t>(addr) < 0x7FFFFFFF; addr += mbi.RegionSize) {
        if (mbi.State != MEM_COMMIT || (mbi.Protect != PAGE_EXECUTE_READWRITE && mbi.Protect != PAGE_EXECUTE_READ)) {
            continue;
        }

        buffer.resize(mbi.RegionSize);
        SIZE_T bytesRead;
        if (!ReadProcessMemory(hProcess, mbi.BaseAddress, buffer.data(), mbi.RegionSize, &bytesRead)) {
            continue;
        }

        for (size_t j = 0; j <= bytesRead - signature.size(); ++j) {
            if (memcmp(buffer.data() + j, signature.data(), signature.size()) == 0) {
                if (ApplyPatch(hProcess, reinterpret_cast<BYTE*>(mbi.BaseAddress), j + signature.size() - 1, patchByte)) {
                    g_logger.Log("Signature found and patched at " + PtrToStr(reinterpret_cast<BYTE*>(mbi.BaseAddress) + j) + ".");
                    return true;
                }
            }
        }
    }

    g_logger.Log("No signature found for patching.");
    return false;
}

// Entry point for patching process
bool PatchProcess(DWORD processID, const std::vector<BYTE>& signature, BYTE patchByte) {
    UniqueHandle hProcess(OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, FALSE, processID));
    if (!hProcess) {
        g_logger.Log("Failed to open target process.");
        return false;
    }

    return PatchSignatureInProcessMemory(hProcess.get(), signature, patchByte);
}

void AttemptToPatch(DWORD currentPID) {
    std::vector<BYTE> signature = { 0x85, 0xC0, 0x74, 0x57, 0x5F, 0xB8, 0x01 };
    BYTE patchByte = 0x00;

    g_logger.Log("Attempting to patch the current process.");
    if (PatchProcess(currentPID, signature, patchByte)) {
        g_logger.Log("Process patched successfully.");
    }
    else {
        g_logger.Log("Failed to patch the process.");
    }
}

DWORD WINAPI MonitorProcess() { // Changed signature here
    DWORD currentPID = GetCurrentProcessId();
    AttemptToPatch(currentPID);
    return 0;
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
        DisableThreadLibraryCalls(hModule);
        std::thread(MonitorProcess).detach();
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}