I have a C++ project with some errors like `unresolved external symbol ReadMemory referenced in function ApplyCheats`.
The files with the code:
```cpp
// cheats.cpp
#include "cheats.h"

uintptr_t caveBase = 0;   // address of our 0x1000-byte cave
uintptr_t cavePlayerSlot = 0;   // address inside the cave that holds rsi

bool enableCheats = false;
bool noDamage = false;
bool infiniteAmmo = false;
bool noSpread = false;
float speedMultiplier = 1.0f;
float jumpHeight = 5.0f;
float swimSpeed = 4.5f;

void UpdatePlayerPointer()
{
    if (caveBase)            // already hooked
        return;

    // 1. remote AOB scan
    std::vector<uint8_t> pattern = {
        0x48, 0x8B, 0x86, 0xA0, 0x00, 0x00, 0x00,
        0x48, 0x8B, 0xC8, 0x48, 0x8B, 0xF2
    };
    uintptr_t patchSite = 0;
    if (!PatternScan(pattern, patchSite))
        return;              // pattern not found

    // 2. allocate RWX cave in the game
    caveBase = reinterpret_cast<uintptr_t>(
        VirtualAllocEx(g_processHandle, nullptr, 0x1000,
            MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE));
    if (!caveBase)
        return;

    cavePlayerSlot = caveBase + 0x800;   // safe scratch space

    // 3. build the trampoline
    std::vector<uint8_t> caveCode = {
        // mov [cavePlayerSlot], rsi
        0x48, 0x89, 0x34, 0x25,
        static_cast<uint8_t>(cavePlayerSlot & 0xFF),
        static_cast<uint8_t>((cavePlayerSlot >> 8) & 0xFF),
        static_cast<uint8_t>((cavePlayerSlot >> 16) & 0xFF),
        static_cast<uint8_t>((cavePlayerSlot >> 24) & 0xFF),

        // original instruction
        0x48, 0x8B, 0x86, 0xA0, 0x00, 0x00, 0x00,

        // jmp back
        0xE9, 0x00, 0x00, 0x00, 0x00
    };

    int32_t relBack = static_cast<int32_t>(
        (patchSite + 7) - (caveBase + caveCode.size()));
    *reinterpret_cast<int32_t*>(&caveCode[caveCode.size() - 4]) = relBack;

    // 4. write cave
    SIZE_T bw;
    WriteProcessMemory(g_processHandle, (LPVOID)caveBase,
        caveCode.data(), caveCode.size(), &bw);

    // 5. patch the original instruction with a 5-byte JMP
    DWORD oldProt;
    VirtualProtectEx(g_processHandle, (LPVOID)patchSite, 7,
        PAGE_EXECUTE_READWRITE, &oldProt);

    std::vector<uint8_t> patch = { 0xE9 };
    int32_t relToCave = static_cast<int32_t>(caveBase - (patchSite + 5));
    patch.insert(patch.end(),
        reinterpret_cast<uint8_t*>(&relToCave),
        reinterpret_cast<uint8_t*>(&relToCave) + 4);
    patch.push_back(0x90);   // NOP
    patch.push_back(0x90);   // NOP

    WriteProcessMemory(g_processHandle, (LPVOID)patchSite,
        patch.data(), patch.size(), &bw);
    VirtualProtectEx(g_processHandle, (LPVOID)patchSite, 7,
        oldProt, &oldProt);

    // 6. wait a moment for the hook to run
    Sleep(100);
}

void ApplyCheats() {
    if (!g_processHandle || !enableCheats || !cavePlayerSlot)
        return;

    playerPtr = ReadMemory<uintptr_t>(cavePlayerSlot);
    if (!playerPtr)          // hook has not run yet
        return;

    // Always refresh the live pointer from the cave
    if (cavePlayerSlot)
        playerPtr = ReadMemory<uintptr_t>(cavePlayerSlot);

    if (!playerPtr) return;   // still invalid

    // No damage
    if (noDamage) {
        WriteMemory<uint64_t>(playerPtr + 0x100, 0);
    }

    // Health
    float maxHealth = ReadMemory<float>(playerPtr + 0x124);
    if (maxHealth > 0) {
        WriteMemory<float>(playerPtr + 0x120, maxHealth);
    }

    // Balance
    float maxBalance = ReadMemory<float>(playerPtr + 0x12C);
    if (maxBalance > 0) {
        WriteMemory<float>(playerPtr + 0x128, maxBalance);
    }

    // Speed multiplier
    WriteMemory<float>(playerPtr + 0x358, speedMultiplier);

    // Jump height
    uintptr_t jumpPtr = ReadMemory<uintptr_t>(playerPtr + 0x20);
    if (jumpPtr) {
        jumpPtr = ReadMemory<uintptr_t>(jumpPtr + 0x98);
        if (jumpPtr) {
            WriteMemory<float>(jumpPtr + 0x94, jumpHeight);
        }
    }

    // Swim speed
    uintptr_t swimPtr = ReadMemory<uintptr_t>(playerPtr + 0x20);
    if (swimPtr) {
        swimPtr = ReadMemory<uintptr_t>(swimPtr + 0x98);
        if (swimPtr) {
            WriteMemory<float>(swimPtr + 0x8C, swimSpeed);
        }
    }

    // Weapon cheats
    uintptr_t weaponPtr = ReadMemory<uintptr_t>(playerPtr + 0xA0);
    if (weaponPtr) {
        if (infiniteAmmo) {
            WriteMemory<int>(weaponPtr + 0x130, 999);
            WriteMemory<int>(weaponPtr + 0x1D0, 999);
        }

        if (noSpread) {
            uintptr_t spreadBase = weaponPtr + 0x40;
            WriteMemory<float>(spreadBase + 0xB4, 0.0f); // kickback
            WriteMemory<float>(spreadBase + 0xB8, 0.0f); // randomKick
            WriteMemory<float>(spreadBase + 0xBC, 0.0f); // spread
            WriteMemory<float>(spreadBase + 0xC0, 0.0f); // followupSpreadGain
            WriteMemory<float>(spreadBase + 0xC4, 0.0f); // followupMaxSpreadHip
            WriteMemory<float>(spreadBase + 0xC8, 0.0f); // followupMaxSpreadAim
            WriteMemory<float>(spreadBase + 0xCC, 0.0f); // followupSpreadStayTime
            WriteMemory<float>(spreadBase + 0xD0, 0.0f); // followupSpreadDissipateTime
            WriteMemory<float>(spreadBase + 0xD4, 0.0f); // snapMagnitude
            WriteMemory<float>(spreadBase + 0xD8, 0.0f); // snapDuration
        }
    }
}
```
```cpp
// cheats.h
#pragma once
#include <Windows.h>
#include <vector>
#include <unordered_map>
#include "memory.h"

// Cheat states
extern bool enableCheats;
extern bool noDamage;
extern bool infiniteAmmo;
extern bool noSpread;
extern float speedMultiplier;
extern float jumpHeight;
extern float swimSpeed;

void UpdatePlayerPointer();
void ApplyCheats();
```
```cpp
// memory.cpp
#include "memory.h"

DWORD g_processId = 0;
HANDLE g_processHandle = nullptr;
uintptr_t moduleBase = 0;
uintptr_t playerPtr = 0;

// Memory functions
DWORD GetProcessIdByName(const char* processName) {
    DWORD pid = 0;
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32 entry;
    entry.dwSize = sizeof(entry);

    if (Process32First(snapshot, &entry)) {
        do {
            if (!_stricmp(entry.szExeFile, processName)) {
                pid = entry.th32ProcessID;
                break;
            }
        } while (Process32Next(snapshot, &entry));
    }
    CloseHandle(snapshot);
    return pid;
}

uintptr_t GetModuleBaseAddress(DWORD procId, const char* modName) {
    uintptr_t modBaseAddr = 0;
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, procId);
    if (hSnap != INVALID_HANDLE_VALUE) {
        MODULEENTRY32 modEntry;
        modEntry.dwSize = sizeof(modEntry);
        if (Module32First(hSnap, &modEntry)) {
            do {
                if (!_stricmp(modEntry.szModule, modName)) {
                    modBaseAddr = (uintptr_t)modEntry.modBaseAddr;
                    break;
                }
            } while (Module32Next(hSnap, &modEntry));
        }
    }
    CloseHandle(hSnap);
    return modBaseAddr;
}

template<typename T>
T ReadMemory(uintptr_t address) {
    T value;
    ReadProcessMemory(g_processHandle, (LPCVOID)address, &value, sizeof(T), nullptr);
    return value;
}

template<typename T>
void WriteMemory(uintptr_t address, T value) {
    WriteProcessMemory(g_processHandle, (LPVOID)address, &value, sizeof(T), nullptr);
}

bool PatternScan(const std::vector<uint8_t>& pattern, uintptr_t& result) {
    MEMORY_BASIC_INFORMATION mbi;
    uintptr_t currentAddress = 0;

    while (VirtualQueryEx(g_processHandle, (LPCVOID)currentAddress, &mbi, sizeof(mbi))) {
        if (mbi.State == MEM_COMMIT && (mbi.Protect & PAGE_READWRITE || mbi.Protect & PAGE_EXECUTE_READWRITE)) {
            std::vector<uint8_t> buffer(mbi.RegionSize);
            SIZE_T bytesRead;

            if (ReadProcessMemory(g_processHandle, mbi.BaseAddress, buffer.data(), mbi.RegionSize, &bytesRead)) {
                for (size_t i = 0; i <= bytesRead - pattern.size(); i++) {
                    bool found = true;
                    for (size_t j = 0; j < pattern.size(); j++) {
                        if (buffer[i + j] != pattern[j]) {
                            found = false;
                            break;
                        }
                    }
                    if (found) {
                        result = (uintptr_t)mbi.BaseAddress + i;
                        return true;
                    }
                }
            }
        }
        currentAddress = (uintptr_t)mbi.BaseAddress + mbi.RegionSize;
    }
    return false;
}
```
```cpp
// memory.h
#pragma once
#include <Windows.h>
#include <string>
#include <vector>
#include <TlHelp32.h>
#include <cstdint>

extern DWORD g_processId;
extern HANDLE g_processHandle;
extern uintptr_t moduleBase;
extern uintptr_t playerPtr;

DWORD GetProcessIdByName(const char* processName);
uintptr_t GetModuleBaseAddress(DWORD procId, const char* modName);
template<typename T> T ReadMemory(uintptr_t address);
template<typename T> void WriteMemory(uintptr_t address, T value);
bool PatternScan(const std::vector<uint8_t>& pattern, uintptr_t& result);
```
What's wrong with the code?