吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2936|回复: 86
上一主题 下一主题
收起左侧

[原创] 植物大战僵尸修改器----------新手学习CE和利用AI做修改器

  [复制链接]
跳转到指定楼层
楼主
ZhangYixiSuccee 发表于 2026-4-6 18:22 回帖奖励
本帖最后由 ZhangYixiSuccee 于 2026-4-6 22:12 编辑

植物大战僵尸修改器

利用CE修改植物大战僵尸游戏逻辑,CE:cheat engine,欺骗引擎,,笔者用到的主要有以下功能

  • 搜索数据+继续搜索(精确值搜索)

  • 地址找到汇编代码(代码查找)

  • 脚本注入代码 or 直接修改内存代码

实现CE脚本之后,可以将CE脚本做成修改器,利用图形界面实现,效果如下图所示。

CE 部分操作说明

  • First Scan:首次搜索的结果列表显示
  • Next Scan:再第一次搜索的结果里面继续搜索,可以多次搜索,Scan Type决定搜索的策略是什么?直到结果搜索出来

  • 点击数据,右击可以搜索是哪个地址访问了这个数据,可以搜到相关指令
  • 双击可以显示寄存器等信息,右击可以显示汇编等信息,很关键,因为可以找到数据相关的汇编逻辑,从而实现脚本 或者代码注入



  • 脚本代码注入:选中指令,右上角选择选择自动汇编,
  • 然后选择cheat table framework code,然后选择code inject,可以修改汇编指令,从而实现脚本修改代码逻辑

1、阳光修改

看到阳光值为50,所以搜索50的数据,然后消耗阳光值,,则基本可以定位阳光的地址

  • 点击first scan,然后就列出了有50数据的地址,
  • 此后不继续刷新,观察到数据会一直变化,
  • 观察到查看阳光的变化,此时变为40,那么该地址基本可以锁定,
  • 此后再继续观察其变化就可以确定

然后接着可以修改阳值,发现修改成功,那么此时可以随意消耗阳光,种植植物。

2、阳光基址

下一关完成后,发现此时的地址不再是阳光的地址了,那么说明每次地址都会变化,需要寻找基地址,

  • 重复上次搜索,然后寻找是谁访问了这个地址
  • 可能搜索结果较多,继续观察count变化,如果杨光变化时,count增加,那么可以锁定
  • 地址为ESI,偏移为0x5560

然后继续重复上面的测试,

  • ESI 地址为0E4BDC38,继续搜索这个地址
  • 查找谁访问该指针内容,发现有比较,以及赋值的,基本可以确定其为二级地址
  • 观察下面的地址访问,可以看到EAX和ECX值一样,
  • 所以二级地址为0x02A59E40


继续去搜索0x02A59E40,注意一二级地址都会变化,所以需要找到基地址

  • 测试几次之后,发现一大堆赋值,且访问次数一直在增加,那么就是地址
  • 游戏完成后,重新也开始也不会变化,那么就是基地址
  • 基地址:0x6A9F38

可以看到这个基地址的数据和一级地址数据一致


3、阳光最大值

正常阳光最大值为9990,即是修改超过这个值,由于代码逻辑判断

  • 开始阳光最大值
  • 之后增加阳光值,

  • 之后捡起阳光值,发现退回为9990,说明其有最大值
  • 通过阳光的地址,可以搜到访问这个地址的指令,
  • 下图中这两个值,点击阳光之后,增加一次,说明其有访问逻辑

从这里可以看到有最大值的限制,以及一次阳光的增加多少

  • 25的阳光增加,以及50阳光的增加
  • 这里可以修改更大的阳光最大值,比如50000

修改后最大阳光为50000,这里注意,不能超过16位数据,高16位被去掉。

这里可以修改阳光一次增加1000

  • 这里植入一个脚本,修改mov ecx,3E8,一次增加1000,原来是25
  • 晚上的阳关,小蘑菇是15,大蘑菇是25,这里修改的黄色大蘑菇的值
  • 也可以修改小蘑菇的逻辑


阳光最大值50000,同样可以修改那一块判断的逻辑

  • 赋值最大为50000

超过了9990的阳光值

3、初始阳光

需要利用到基地址,

  • 搜索基地址的指针的访问,每个关卡结束的时候会将地址改写成50,这个时候会有一个访问值
  • 这里就是初始值

脚本修改成10000,


4、无限阳光

即种植植物不需要消耗阳光值,

  • 同样首先找到阳光的地址即可,不需要基地址
  • 然后看谁访问了这个地址,
  • 然后点击种植植物,如果访问次数加一,则说明这里是种植植物的逻辑
  • 简单将递减阳光的逻辑去掉jike

下面15次,每次种植阳光都会递减,所以这里就是阳关处理的逻辑


去掉了sub的逻辑,阳光值不改变

5、植物无CD

搜索步骤如下:

  • CD刚开始为0,之后一直变大,超过某个定值后为0
  • 所以先搜索0,然后继续搜索变大的值,值不会太大,
  • 符合条件的值不多,所以可以看到如下值比较符合
  • 后面观察,之后值归0,基本可以确认这块就是相应的逻辑

打开相应的汇编代码,将跳转逻辑nop掉,则默认会清零


可以看到随时随地种植植物

6、秒杀僵尸

僵尸的血量一直会降低,

  • 僵尸一出场的搜索血量值不确定的值,
  • 然后搜索数值降低的值,
  • 然后会搜索到如下的值,可以看到这里普通僵尸是270的血量
  • 然后看汇编处理这块的逻辑

找到如下值进行,然后查看汇编逻辑如下

脚步直接将0赋值回去,则僵尸秒杀

僵尸类型很多,所以上面只是针对普通僵尸,其他僵尸类似

其他高级僵尸



7、植物无限血

同理上面找到血量的逻辑

  • 坚果血值较大,可看到这块逻辑

只需要植物不掉血,则可以一直在,即血量+0,

8、向日葵无限生成阳光

向日葵生成阳光的时候,生产完阳光之后,会有间隔,这个间隔时间

  • 这个间隔时间会会变大,然后清零,然后产生阳光
  • 这个间隔时间会变小,然后到零,然后产生阳光,

我们可以测试两张场景,直到确认阳光 的生产地址,下图这个地址很像,每次递减到零,就会产生阳光

查看地址处的汇编代码,

  • 将这里的add -1 nop掉即可实现

9、金币无限

金币的数值并不是本身的数字,而是乘以10倍的,所以搜索的数据要除以10(笔者搜索实际数据未搜到,然后尝试除以10得知)

直接赋值,可以修改金币

汇编代码如下:

  • 一级偏移:28

继续搜索

  • 二级偏移82C


继续搜

  • 6A9EC0:金币基地址

  • 6A9F38:金币基地址

两个基地址均可以正常使用

10、自动收集阳光

  • 阳光产生的时候,扫描0的值,

  • 点击之后阳光之后,扫描1的值,

  • 最后会发现地址值,然后看汇编,之后可以看到点击之后,count值变化,说明就是关键汇编代码


这些都有访问,可以挨个测试一下,哪些可以触发自动收集阳光

地址都是相同的


11、僵尸出厂倒计时

游戏刚开始时,僵尸出厂有倒计时,倒计时结束,僵尸出厂,所示搜索思路就是,

  • 出厂时搜索未初始化的值
  • 然后搜索数值减少
  • 一直搜索到几个时,可以看到如下值,每次倒计时结束,则会有僵尸出来,
  • 看到修改这里,僵尸就会出来



image-20260328223548728

image-20260328223707236

倒计时一直递减


倒计时为0,僵尸全部出动
全部出动僵尸


其余脚本修改都类似,大家可以自行实现

12、卡槽植物修改

13、投掷僵尸

14、土豆地雷无CD

15、食僵尸花无CD

16、寒冰菇一直冰冻

17、寒冰射手一直冰冻

利用AI进行修改器制作

直接将CE脚本的内容发给AI,并输入要求:输出MFC的框架代码,其大致给你输出一个框架,利用该框架可以实现基本的一个修改器功能,后续的修改器功能就是舔砖加瓦即可。

基础知识

  • 主要用到的windows API函数如下:
    • OpenProcess:打开进程,前提是找到进场ID
    • ReadProcessMemeory、WriteProcessMemory:读取内存数据以及写入内存数据,这里很关键,注入代码就是靠这两个函数实现
    • VirtualProtect:内存数据保护与关闭操作

进程查找与附加

  • 提示词如下:这里22D3A3F0是太阳光数值的临时地址,修改其可以直接看到太阳光数值。

进程查找与附加,脱离进程的代码如下:

// 查找进程ID
BOOL CPlantsCEDlg::FindProcessId(LPCTSTR szProcessName, DWORD& dwProcessId)
{
    AddLog(_T("[查找进程] 正在查找: %s"), szProcessName);

    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnapshot == INVALID_HANDLE_VALUE)
    {
        AddLog(_T("[查找进程] 创建进程快照失败,错误码: %d"), GetLastError());
        return FALSE;
    }

    PROCESSENTRY32 pe;
    pe.dwSize = sizeof(PROCESSENTRY32);

    BOOL bFound = FALSE;
    if (Process32First(hSnapshot, &pe))
    {
        do
        {
            if (_tcsicmp(pe.szExeFile, szProcessName) == 0)
            {
                dwProcessId = pe.th32ProcessID;
                bFound = TRUE;
                AddLog(_T("[查找进程] 找到进程: %s (PID: %d)"), pe.szExeFile, dwProcessId);
                break;
            }
        } while (Process32Next(hSnapshot, &pe));
    }

    CloseHandle(hSnapshot);

    if (!bFound)
    {
        AddLog(_T("[查找进程] 未找到进程: %s"), szProcessName);

        // 列出所有运行中的进程,帮助用户排查
        AddLog(_T("[查找进程] 当前运行的进程列表:"));
        hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        if (hSnapshot != INVALID_HANDLE_VALUE)
        {
            pe.dwSize = sizeof(PROCESSENTRY32);
            if (Process32First(hSnapshot, &pe))
            {
                int nCount = 0;
                do
                {
                    if (nCount < 20)  // 只显示前20个
                    {
                        AddLog(_T("[查找进程]   %s (PID: %d)"), pe.szExeFile, pe.th32ProcessID);
                    }
                    nCount++;
                } while (Process32Next(hSnapshot, &pe));
                if (nCount > 20)
                {
                    AddLog(_T("[查找进程]   ... 还有 %d 个进程"), nCount - 20);
                }
            }
            CloseHandle(hSnapshot);
        }
    }

    return bFound;
}

// 附加进程
BOOL CPlantsCEDlg::AttachToProcess()
{
    if (m_strProcessName.IsEmpty())
    {
        AddLog(_T("[附加进程] 错误: 请先选择进程"));
        MessageBox(_T("请先点击【选择进程】按钮选择游戏进程!"),
            _T("错误"), MB_OK | MB_ICONERROR);
        return FALSE;
    }

    if (!FindProcessId(m_strProcessName, m_dwProcessId))
    {
        AddLog(_T("[附加进程] 错误: 未找到进程 %s"), m_strProcessName);
        AddLog(_T("[附加进程] 请确保游戏已经运行,并且进程名称正确"));

        CString strMsg;
        strMsg.Format(_T("未找到进程: %s\n\n请确保:\n1. 游戏已经运行\n2. 进程名称正确\n3. 以管理员权限运行此程序"),
            m_strProcessName);
        MessageBox(strMsg, _T("错误"), MB_OK | MB_ICONERROR);
        return FALSE;
    }

    AddLog(_T("[附加进程] 找到进程: %s (PID: %d, 0x%08X)"),
        m_strProcessName, m_dwProcessId, m_dwProcessId);

    // 尝试打开进程
    m_hProcess = OpenProcess(
        PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION |
        PROCESS_QUERY_INFORMATION | PROCESS_SET_INFORMATION,
        FALSE,
        m_dwProcessId
    );

    if (!m_hProcess)
    {
        DWORD dwError = GetLastError();
        AddLog(_T("[附加进程] 打开进程失败,错误码: %d"), dwError);

        if (dwError == 5)
        {
            AddLog(_T("[附加进程] 权限不足,请以管理员权限运行"));
            MessageBox(_T("无法打开进程!\n\n请以管理员身份运行此程序。"),
                _T("错误"), MB_OK | MB_ICONERROR);
        }
        return FALSE;
    }

    m_bAttached = TRUE;
    AddLog(_T("[附加进程] 成功附加进程 %s"), m_strProcessName);
    AddLog(_T("[附加进程] 进程句柄: 0x%08X"), (DWORD_PTR)m_hProcess);

    return TRUE;
}

// 分离进程
void CPlantsCEDlg::DetachFromProcess()
{
    if (m_hProcess)
    {
        CloseHandle(m_hProcess);
        m_hProcess = NULL;
    }
    m_bAttached = FALSE;
    m_dwProcessId = 0;
    m_sunlightAddr.finalAddress = 0;

    // 重置地址
    m_dwNoPlantCDAddress = 0;
    m_dwAutoFastSunAddress = 0;
    m_dwAutoCollectSunAddress = 0;
    m_dwFastShootAddress = 0;

}

// 按钮点击附加进程
void CPlantsCEDlg::OnBnClickedAttachprocess()
{
    // TODO: 在此添加控件通知处理程序代码
    if (m_bAttached)
    {
        DetachFromProcess();
        return;
    }

    if (AttachToProcess())
    {
        // 获取模块基址并显示初始值功能地址
        DWORD_PTR dwModuleBase = GetModuleBaseAddress();
        if (dwModuleBase)
        {
            m_dwTargetAddress = dwModuleBase + TARGET_INIT_VALUE_OFFSET;
            AddLog(_T("[初始值10000] 目标地址: 0x%08X"), m_dwTargetAddress);

            // 无CD功能地址
            m_dwNoPlantCDAddress = dwModuleBase + NO_PLANT_CD_OFFSET;
            AddLog(_T("[无CD功能] 目标地址: 0x%08X"), m_dwNoPlantCDAddress);
        }

        // 自动读取一次阳光值
        OnBnClickedReadSunValue();
    }
}

// 添加日志
void CPlantsCEDlg::AddLog(LPCTSTR szFormat, ...)
{
    CString strLog;
    va_list args;
    va_start(args, szFormat);
    strLog.FormatV(szFormat, args);
    va_end(args);

    CTime time = CTime::GetCurrentTime();
    CString strTime = time.Format(_T("[%H:%M:%S] "));

    m_listLog.AddString(strTime + strLog);

    int nCount = m_listLog.GetCount();
    if (nCount > 0)
        m_listLog.SetCurSel(nCount - 1);
}

读取当前阳光数值:利用阳光基地址

// 读取指针链:[[baseAddress] + offset1] + offset2
DWORD_PTR CPlantsCEDlg::ReadPointerChain(DWORD_PTR baseAddress, DWORD_PTR offset1, DWORD_PTR offset2)
{
    if (!m_hProcess)
        return 0;

    DWORD_PTR ptr1 = 0;
    SIZE_T bytesRead = 0;

    // 第一次读取:基址 -> 指针1
    if (!ReadProcessMemory(m_hProcess, (LPCVOID)baseAddress, &ptr1, sizeof(DWORD_PTR), &bytesRead))
    {
        AddLog(_T("读取基址失败,错误码: %d"), GetLastError());
        return 0;
    }

    if (bytesRead != sizeof(DWORD_PTR))
    {
        AddLog(_T("读取基址数据不完整"));
        return 0;
    }

    AddLog(_T("读取基址[0x%08X] -> 指针1: 0x%08X"), baseAddress, ptr1);

    // 第一次偏移计算
    DWORD_PTR addr1 = ptr1 + offset1;
    DWORD_PTR ptr2 = 0;

    // 第二次读取:指针1+偏移1 -> 指针2
    if (!ReadProcessMemory(m_hProcess, (LPCVOID)addr1, &ptr2, sizeof(DWORD_PTR), &bytesRead))
    {
        AddLog(_T("读取第一层偏移失败,错误码: %d"), GetLastError());
        return 0;
    }

    if (bytesRead != sizeof(DWORD_PTR))
    {
        AddLog(_T("读取第一层偏移数据不完整"));
        return 0;
    }

    AddLog(_T("读取地址[0x%08X] -> 指针2: 0x%08X"), addr1, ptr2);

    // 第二次偏移计算,得到最终阳光地址
    DWORD_PTR finalAddr = ptr2 + offset2;

    AddLog(_T("最终阳光地址: 0x%08X"), finalAddr);

    return finalAddr;
}

// 读取阳光值
DWORD CPlantsCEDlg::ReadSunlightValue()
{
    if (!m_hProcess || !m_bAttached)
    {
        AddLog(_T("错误: 未附加进程"));
        return 0;
    }

    // 获取最终阳光地址
    m_sunlightAddr.finalAddress = ReadPointerChain(
        m_sunlightAddr.baseAddress,
        m_sunlightAddr.offset1,
        m_sunlightAddr.offset2
    );

    if (m_sunlightAddr.finalAddress == 0)
    {
        AddLog(_T("无法获取阳光地址"));
        return 0;
    }

    // 读取阳光值
    DWORD dwSunlight = 0;
    SIZE_T bytesRead = 0;

    if (ReadProcessMemory(m_hProcess, (LPCVOID)m_sunlightAddr.finalAddress,
        &dwSunlight, sizeof(DWORD), &bytesRead))
    {
        if (bytesRead == sizeof(DWORD))
        {
            AddLog(_T("当前阳光值: %d"), dwSunlight);
            return dwSunlight;
        }
    }

    AddLog(_T("读取阳光值失败,错误码: %d"), GetLastError());
    return 0;
}

// 写入阳光值
BOOL CPlantsCEDlg::WriteSunlightValue(DWORD dwNewValue)
{
    if (!m_hProcess || !m_bAttached)
    {
        AddLog(_T("错误: 未附加进程"));
        return FALSE;
    }

    if (m_sunlightAddr.finalAddress == 0)
    {
        AddLog(_T("错误: 未获取阳光地址,请先读取阳光值"));
        return FALSE;
    }

    // 修改内存保护属性
    DWORD dwOldProtect = 0;
    if (!VirtualProtectEx(m_hProcess, (LPVOID)m_sunlightAddr.finalAddress,
        sizeof(DWORD), PAGE_EXECUTE_READWRITE, &dwOldProtect))
    {
        AddLog(_T("修改内存保护失败,错误码: %d"), GetLastError());
        // 继续尝试写入
    }

    // 写入新值
    SIZE_T bytesWritten = 0;
    BOOL bResult = WriteProcessMemory(m_hProcess, (LPVOID)m_sunlightAddr.finalAddress,
        &dwNewValue, sizeof(DWORD), &bytesWritten);

    // 恢复内存保护属性
    VirtualProtectEx(m_hProcess, (LPVOID)m_sunlightAddr.finalAddress,
        sizeof(DWORD), dwOldProtect, &dwOldProtect);

    if (bResult && bytesWritten == sizeof(DWORD))
    {
        AddLog(_T("成功修改阳光值: %d -> %d"),
            ReadSunlightValue() - dwNewValue + dwNewValue, dwNewValue);

        // 验证修改
        DWORD dwVerify = ReadSunlightValue();
        if (dwVerify == dwNewValue)
        {
            AddLog(_T("验证成功: 阳光值已更新为 %d"), dwVerify);
            return TRUE;
        }
        else
        {
            AddLog(_T("警告: 验证失败,当前值为 %d"), dwVerify);
            return FALSE;
        }
    }
    else
    {
        AddLog(_T("写入失败,错误码: %d"), GetLastError());
        return FALSE;
    }
}

// 按钮点击阳光数值
void CPlantsCEDlg::OnBnClickedReadSunValue()
{
    // TODO: 在此添加控件通知处理程序代码
    if (!m_bAttached || !m_hProcess)
    {
        AddLog(_T("错误: 请先附加进程"));
        return;
    }

    AddLog(_T("开始读取阳光值..."));

    DWORD dwSunlight = ReadSunlightValue();

    if (dwSunlight > 0 || dwSunlight == 0)  // 允许读取到0
    {
        CString strValue;
        strValue.Format(_T("%d"), dwSunlight);
        m_editSunBaseValue.SetWindowText(strValue);

        CString strStatus;
        strStatus.Format(_T("阳光: %d"), dwSunlight);
    }
    else
    {
        AddLog(_T("读取失败,请检查游戏是否运行正常"));
    }
}

//按钮修改阳光数值
void CPlantsCEDlg::OnBnClickedModifySunValue()
{
    // TODO: 在此添加控件通知处理程序代码
    if (!m_bAttached || !m_hProcess)
    {
        AddLog(_T("错误: 请先附加进程"));
        return;
    }

    CString strValue;
    m_editSunBaseValue.GetWindowText(strValue);

    if (strValue.IsEmpty())
    {
        AddLog(_T("错误: 请输入要修改的阳光值"));
        return;
    }

    DWORD dwNewValue = _ttoi(strValue);

    if (dwNewValue > 999999)
    {
        AddLog(_T("警告: 阳光值过大 (%d),可能影响游戏体验"), dwNewValue);
    }

    AddLog(_T("准备修改阳光值为: %d"), dwNewValue);

    if (WriteSunlightValue(dwNewValue))
    {

        // 刷新显示
        DWORD dwCurrent = ReadSunlightValue();
        CString strCurrent;
        strCurrent.Format(_T("%d"), dwCurrent);
        m_editSunBaseValue.SetWindowText(strCurrent);
    }
    else
    {

    }
}

实现效果

效果如下:


AI进行脚本实现

  • 这里将基本的逻辑跑通之后,说明主体框架已经OK,
  • 剩下的就是工作量的事情,将一个个脚本都按照框架进行实现

生成代码如下:

  • 注意如果代码修改的指令有错误,导致程序崩溃,可以利用CE查看修改的指令
  • 比如返回地址错误,需要修正生成的指令,或者告诉AI,让其进行修改,
// PlantsVsZombiesDlg.cpp - 添加以下内容

// 在 OnInitDialog 函数中添加 CheckBox 初始化
BOOL CPlantsVsZombiesDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    // ... 原有代码 ...

    // 初始化自动快速生产阳光 CheckBox
    m_checkAutoFastSun.SetCheck(BST_UNCHECKED);
    m_bAutoFastSunEnabled = FALSE;
    m_dwAutoFastSunAddress = 0;
    m_pAutoFastSunMemory = NULL;
    m_checkAutoFastSun.EnableWindow(FALSE);  // 附加进程后才能使用

    // 显示功能说明
    SetDlgItemText(IDC_STATIC_AUTO_FAST_SUN_TIP, _T("功能说明: 获取阳光时自动变为100阳光,实现快速生产"));
    AddLog(_T("自动快速生产阳光功能已加载"));
    AddLog(_T("提示: 需要先附加进程才能使用此功能"));

    return TRUE;
}

// 获取模块基址(如果之前没有)
DWORD_PTR CPlantsVsZombiesDlg::GetModuleBaseAddress()
{
    if (!m_hProcess || !m_bAttached)
        return 0;

    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, m_dwProcessId);
    if (hSnapshot == INVALID_HANDLE_VALUE)
        return 0;

    MODULEENTRY32 moduleEntry;
    moduleEntry.dwSize = sizeof(MODULEENTRY32);

    DWORD_PTR dwModuleBase = 0;
    if (Module32First(hSnapshot, &moduleEntry))
    {
        do
        {
            if (_tcsicmp(moduleEntry.szModule, TARGET_PROCESS_NAME) == 0)
            {
                dwModuleBase = (DWORD_PTR)moduleEntry.modBaseAddr;
                break;
            }
        } while (Module32Next(hSnapshot, &moduleEntry));
    }

    CloseHandle(hSnapshot);
    return dwModuleBase;
}

// 启用自动快速生产阳光功能
void CPlantsVsZombiesDlg::EnableAutoFastSun()
{
    if (!m_hProcess || !m_bAttached)
    {
        AddLog(_T("[自动快速阳光] 错误: 未附加进程"));
        return;
    }

    if (m_bAutoFastSunEnabled)
    {
        AddLog(_T("[自动快速阳光] 已经启用"));
        return;
    }

    AddLog(_T("[自动快速阳光] 正在启用..."));

    // 获取模块基址和完整地址
    if (m_dwAutoFastSunAddress == 0)
    {
        DWORD_PTR dwModuleBase = GetModuleBaseAddress();
        if (dwModuleBase)
        {
            m_dwAutoFastSunAddress = dwModuleBase + AUTO_FAST_SUN_OFFSET;
            AddLog(_T("[自动快速阳光] 目标地址: 0x%08X"), m_dwAutoFastSunAddress);
        }
        else
        {
            AddLog(_T("[自动快速阳光] 无法获取模块基址"));
            return;
        }
    }

    // 读取当前字节码
    BYTE currentBytes[5] = {0};  // 原始指令5字节
    SIZE_T bytesRead = 0;
    if (ReadProcessMemory(m_hProcess, (LPCVOID)m_dwAutoFastSunAddress, currentBytes, 5, &bytesRead))
    {
        AddLog(_T("[自动快速阳光] 当前字节码: %02X %02X %02X %02X %02X"),
               currentBytes[0], currentBytes[1], currentBytes[2], currentBytes[3], currentBytes[4]);
    }

    // 修改内存保护属性
    DWORD dwOldProtect = 0;
    if (!VirtualProtectEx(m_hProcess, (LPVOID)m_dwAutoFastSunAddress, 5, 
                          PAGE_EXECUTE_READWRITE, &dwOldProtect))
    {
        AddLog(_T("[自动快速阳光] 修改内存保护失败,错误码: %d"), GetLastError());
        return;
    }

    // 分配内存用于存放自定义代码
    m_pAutoFastSunMemory = VirtualAllocEx(m_hProcess, NULL, 2048, 
                                           MEM_COMMIT | MEM_RESERVE, 
                                           PAGE_EXECUTE_READWRITE);

    if (!m_pAutoFastSunMemory)
    {
        AddLog(_T("[自动快速阳光] 分配内存失败,错误码: %d"), GetLastError());
        VirtualProtectEx(m_hProcess, (LPVOID)m_dwAutoFastSunAddress, 5, dwOldProtect, &dwOldProtect);
        return;
    }

    AddLog(_T("[自动快速阳光] 分配内存地址: 0x%08X"), (DWORD_PTR)m_pAutoFastSunMemory);

    // 创建自定义代码
    // newmem: 
    //   mov [edi+58], 64    ; 将阳光设置为100 (0x64)
    //   mov eax, [edi]      ; 读取阳光值到eax
    // exit:
    //   jmp returnhere
    BYTE customCode[] = {
        0xC7, 0x47, 0x58, 0x64, 0x00, 0x00, 0x00,  // mov [edi+58], 00000064
        0x8B, 0x07,                                 // mov eax, [edi]
        0xE9                                        // jmp (偏移量需要计算)
    };

    // 计算返回地址的JMP偏移
    // 原地址+5 是JMP指令后的返回地址
    DWORD_PTR returnAddress = m_dwAutoFastSunAddress + 5;
    DWORD jmpOffset = (DWORD)(returnAddress - ((DWORD_PTR)m_pAutoFastSunMemory + sizeof(customCode)));

    // 创建完整的代码块(添加JMP偏移)
    BYTE fullCode[sizeof(customCode) + 4];
    memcpy(fullCode, customCode, sizeof(customCode));
    fullCode[sizeof(customCode)] = (BYTE)(jmpOffset & 0xFF);
    fullCode[sizeof(customCode) + 1] = (BYTE)((jmpOffset >> 8) & 0xFF);
    fullCode[sizeof(customCode) + 2] = (BYTE)((jmpOffset >> 16) & 0xFF);
    fullCode[sizeof(customCode) + 3] = (BYTE)((jmpOffset >> 24) & 0xFF);

    // 写入自定义代码到分配的内存
    SIZE_T bytesWritten = 0;
    if (!WriteProcessMemory(m_hProcess, m_pAutoFastSunMemory, fullCode, sizeof(fullCode), &bytesWritten))
    {
        AddLog(_T("[自动快速阳光] 写入自定义代码失败,错误码: %d"), GetLastError());
        VirtualFreeEx(m_hProcess, m_pAutoFastSunMemory, 0, MEM_RELEASE);
        m_pAutoFastSunMemory = NULL;
        VirtualProtectEx(m_hProcess, (LPVOID)m_dwAutoFastSunAddress, 5, dwOldProtect, &dwOldProtect);
        return;
    }

    AddLog(_T("[自动快速阳光] 自定义代码写入成功,大小: %d 字节"), bytesWritten);

    // 创建JMP指令跳转到分配的内存
    DWORD jmpToAllocated = (DWORD)((DWORD_PTR)m_pAutoFastSunMemory - (m_dwAutoFastSunAddress + 5));
    BYTE jmpInstruction[5] = { 0xE9 };
    jmpInstruction[1] = (BYTE)(jmpToAllocated & 0xFF);
    jmpInstruction[2] = (BYTE)((jmpToAllocated >> 8) & 0xFF);
    jmpInstruction[3] = (BYTE)((jmpToAllocated >> 16) & 0xFF);
    jmpInstruction[4] = (BYTE)((jmpToAllocated >> 24) & 0xFF);

    // 写入JMP指令到目标地址
    if (WriteProcessMemory(m_hProcess, (LPVOID)m_dwAutoFastSunAddress, jmpInstruction, 5, &bytesWritten))
    {
        AddLog(_T("[自动快速阳光] 成功启用!获取阳光时自动变为100阳光"));
        AddLog(_T("[自动快速阳光] JMP从 0x%08X 跳转到 0x%08X"), 
               m_dwAutoFastSunAddress, (DWORD_PTR)m_pAutoFastSunMemory);
        m_bAutoFastSunEnabled = TRUE;

        // 验证修改
        BYTE verifyBytes[5] = {0};
        if (ReadProcessMemory(m_hProcess, (LPCVOID)m_dwAutoFastSunAddress, verifyBytes, 5, &bytesRead))
        {
            if (memcmp(jmpInstruction, verifyBytes, 5) == 0)
            {
                AddLog(_T("[自动快速阳光] 验证成功,内存修改正确"));
            }
            else
            {
                AddLog(_T("[自动快速阳光] 验证失败,请检查"));
            }
        }
    }
    else
    {
        AddLog(_T("[自动快速阳光] 写入JMP指令失败,错误码: %d"), GetLastError());
        VirtualFreeEx(m_hProcess, m_pAutoFastSunMemory, 0, MEM_RELEASE);
        m_pAutoFastSunMemory = NULL;
    }

    // 恢复内存保护属性
    VirtualProtectEx(m_hProcess, (LPVOID)m_dwAutoFastSunAddress, 5, dwOldProtect, &dwOldProtect);
}

// 禁用自动快速生产阳光功能(恢复原始代码)
void CPlantsVsZombiesDlg::DisableAutoFastSun()
{
    if (!m_hProcess || !m_bAttached)
    {
        AddLog(_T("[自动快速阳光] 错误: 未附加进程"));
        return;
    }

    if (!m_bAutoFastSunEnabled)
    {
        AddLog(_T("[自动快速阳光] 已经禁用"));
        return;
    }

    AddLog(_T("[自动快速阳光] 正在禁用,恢复正常阳光获取..."));

    // 获取模块基址
    if (m_dwAutoFastSunAddress == 0)
    {
        DWORD_PTR dwModuleBase = GetModuleBaseAddress();
        if (dwModuleBase)
        {
            m_dwAutoFastSunAddress = dwModuleBase + AUTO_FAST_SUN_OFFSET;
        }
        else
        {
            AddLog(_T("[自动快速阳光] 无法获取模块基址"));
            return;
        }
    }

    // 原始字节码: 89 47 58 8B 07
    // mov [edi+58], eax
    // mov eax, [edi]
    BYTE originalBytes[] = { 0x89, 0x47, 0x58, 0x8B, 0x07 };

    // 修改内存保护属性
    DWORD dwOldProtect = 0;
    if (!VirtualProtectEx(m_hProcess, (LPVOID)m_dwAutoFastSunAddress, 5, 
                          PAGE_EXECUTE_READWRITE, &dwOldProtect))
    {
        AddLog(_T("[自动快速阳光] 修改内存保护失败,错误码: %d"), GetLastError());
        return;
    }

    // 恢复原始字节码
    SIZE_T bytesWritten = 0;
    if (WriteProcessMemory(m_hProcess, (LPVOID)m_dwAutoFastSunAddress, originalBytes, 5, &bytesWritten))
    {
        AddLog(_T("[自动快速阳光] 成功禁用!已恢复正常阳光获取"));
        AddLog(_T("[自动快速阳光] 恢复字节码: %02X %02X %02X %02X %02X"),
               originalBytes[0], originalBytes[1], originalBytes[2], originalBytes[3], originalBytes[4]);
        m_bAutoFastSunEnabled = FALSE;

        // 验证恢复
        BYTE verifyBytes[5] = {0};
        SIZE_T bytesRead = 0;
        if (ReadProcessMemory(m_hProcess, (LPCVOID)m_dwAutoFastSunAddress, verifyBytes, 5, &bytesRead))
        {
            if (memcmp(originalBytes, verifyBytes, 5) == 0)
            {
                AddLog(_T("[自动快速阳光] 验证成功,内存恢复正确"));
            }
            else
            {
                AddLog(_T("[自动快速阳光] 验证失败,请检查"));
            }
        }
    }
    else
    {
        AddLog(_T("[自动快速阳光] 恢复失败,错误码: %d"), GetLastError());
    }

    // 释放分配的内存
    if (m_pAutoFastSunMemory)
    {
        VirtualFreeEx(m_hProcess, m_pAutoFastSunMemory, 0, MEM_RELEASE);
        m_pAutoFastSunMemory = NULL;
        AddLog(_T("[自动快速阳光] 已释放分配的内存"));
    }

    // 恢复内存保护属性
    VirtualProtectEx(m_hProcess, (LPVOID)m_dwAutoFastSunAddress, 5, dwOldProtect, &dwOldProtect);
}

// CheckBox 点击响应函数
void CPlantsVsZombiesDlg::OnBnClickedCheckAutoFastSun()
{
    int nCheck = m_checkAutoFastSun.GetCheck();

    if (!m_bAttached || !m_hProcess)
    {
        AddLog(_T("[自动快速阳光] 错误: 请先附加进程"));
        // 恢复之前的选中状态
        m_checkAutoFastSun.SetCheck(m_bAutoFastSunEnabled ? BST_CHECKED : BST_UNCHECKED);
        return;
    }

    if (nCheck == BST_CHECKED)  // 启用
    {
        EnableAutoFastSun();
    }
    else  // 禁用
    {
        DisableAutoFastSun();
    }
}

// 修改附加进程函数,在附加成功后初始化地址
void CPlantsVsZombiesDlg::OnBnClickedBtnAttachProcess()
{
    if (m_bAttached)
    {
        // 如果自动快速阳光功能启用,先禁用
        if (m_bAutoFastSunEnabled)
        {
            DisableAutoFastSun();
            m_checkAutoFastSun.SetCheck(BST_UNCHECKED);
        }

        // 如果阳光初始值功能启用,先禁用
        if (m_bSunInitBigValueEnabled)
        {
            DisableSunInitBigValue();
            m_checkSunInitBigValue.SetCheck(BST_UNCHECKED);
        }

        // 如果无CD功能启用,先禁用
        if (m_bNoPlantCDEnabled)
        {
            DisableNoPlantCD();
            m_checkNoPlantCD.SetCheck(BST_UNCHECKED);
        }

        DetachFromProcess();
        m_btnAttachProcess.SetWindowText(_T("附加进程"));
        m_btnReadSunlight.EnableWindow(FALSE);
        m_btnModifySunlight.EnableWindow(FALSE);
        m_editSunBaseValue.EnableWindow(FALSE);
        m_checkSunInitBigValue.EnableWindow(FALSE);
        m_checkNoPlantCD.EnableWindow(FALSE);
        m_checkAutoFastSun.EnableWindow(FALSE);
        UpdateStatus(_T("未附加"));
        return;
    }

    if (AttachToProcess())
    {
        m_btnAttachProcess.SetWindowText(_T("分离进程"));
        m_btnReadSunlight.EnableWindow(TRUE);
        m_btnModifySunlight.EnableWindow(TRUE);
        m_editSunBaseValue.EnableWindow(TRUE);
        m_checkSunInitBigValue.EnableWindow(TRUE);
        m_checkNoPlantCD.EnableWindow(TRUE);
        m_checkAutoFastSun.EnableWindow(TRUE);
        UpdateStatus(_T("已附加"));

        // 获取模块基址并显示地址信息
        DWORD_PTR dwModuleBase = GetModuleBaseAddress();
        if (dwModuleBase)
        {
            // 阳光初始值功能地址
            m_dwTargetAddress = dwModuleBase + TARGET_INIT_VALUE_OFFSET;
            CString strInitAddr;
            strInitAddr.Format(_T("初始值修改地址: 0x%08X"), m_dwTargetAddress);
            SetDlgItemText(IDC_STATIC_INIT_ADDR, strInitAddr);
            AddLog(_T("[阳光初始值] 目标地址: 0x%08X"), m_dwTargetAddress);

            // 无CD功能地址
            m_dwNoPlantCDAddress = dwModuleBase + NO_PLANT_CD_OFFSET;
            CString strNoCDAddr;
            strNoCDAddr.Format(_T("无CD修改地址: 0x%08X"), m_dwNoPlantCDAddress);
            SetDlgItemText(IDC_STATIC_NO_CD_ADDR, strNoCDAddr);
            AddLog(_T("[无CD功能] 目标地址: 0x%08X"), m_dwNoPlantCDAddress);

            // 自动快速阳光功能地址
            m_dwAutoFastSunAddress = dwModuleBase + AUTO_FAST_SUN_OFFSET;
            CString strAutoFastSunAddr;
            strAutoFastSunAddr.Format(_T("自动快速阳光地址: 0x%08X"), m_dwAutoFastSunAddress);
            SetDlgItemText(IDC_STATIC_AUTO_FAST_SUN_ADDR, strAutoFastSunAddr);
            AddLog(_T("[自动快速阳光] 目标地址: 0x%08X"), m_dwAutoFastSunAddress);
        }

        // 自动读取一次阳光值
        OnBnClickedBtnReadSunlight();
    }
}

免费评分

参与人数 28吾爱币 +23 热心值 +26 收起 理由
rainfarm + 1 + 1 我很赞同!
Issacclark1 + 1 谢谢@Thanks!
lichu_2004 + 1 + 1 谢谢@Thanks!
Bison66680 + 1 我很赞同!
PHOTO009 + 1 热心回复!
cscsxiaojie520 + 1 + 1 谢谢@Thanks!
Ez10 + 1 + 1 热心回复!
Crayon0528 + 1 + 1 谢谢@Thanks!
hy503782576 + 1 + 1 我很赞同!
tail88 + 1 + 1 谢谢@Thanks!
精妹 + 1 我很赞同!
wyangdh + 1 + 1 热心回复!
lsrh2000 + 1 + 1 用心讨论,共获提升!
JRGG + 1 谢谢@Thanks!
lizhuowu + 1 热心回复!
happyfox1999 + 1 + 1 我很赞同!
lixinye + 1 + 1 谢谢@Thanks!
khnn + 1 + 1 谢谢@Thanks!
tangchenL + 1 + 1 用心讨论,共获提升!
ZhuJiuZoe + 1 热心回复!
努力的小七 + 1 + 1 我很赞同!
IcePlume + 1 + 1 我很赞同!
peyo + 1 + 1 谢谢@Thanks!
这是一个大陶子 + 1 + 1 谢谢@Thanks!
莫奇 + 1 + 1 谢谢@Thanks!
wzg020228 + 1 + 1 谢谢@Thanks!
你潺潺的泪水 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
JustSurvive + 1 + 1 我很赞同!

查看全部评分

本帖被以下淘专辑推荐:

  • · q |主题: 108, 订阅: 2

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
chishingchan 发表于 2026-4-6 19:55
外置修改器有两个:
https://pvz.tools/
https://get.pvz.tools/

感觉楼主的游戏有点非官方!字体都有几种。
推荐
hongshao987 发表于 2026-4-7 09:00
这个太强了,有图有字,再来个成品就更好了。支持全部的游戏版本吗?还是只支持某个特定的版本?
植物大战僵尸高清版和重值版也可以用吗?
沙发
a8845019 发表于 2026-4-6 18:47
3#
worry4963 发表于 2026-4-6 18:50
急需你修改好的文件,谢谢啦!
4#
wwti 发表于 2026-4-6 19:03
很历害的样子。
5#
xynm 发表于 2026-4-6 19:10
害,我以前还花钱买了破解版
6#
timelessxp 发表于 2026-4-6 19:15
感谢楼主的分享
7#
wsdhgd 发表于 2026-4-6 19:16
地址我访问不了,求国内网盘
8#
m16yjq 发表于 2026-4-6 19:16
不明觉厉,楼主有成品么
9#
 楼主| ZhangYixiSuccee 发表于 2026-4-6 19:36 |楼主
worry4963 发表于 2026-4-6 18:50
急需你修改好的文件,谢谢啦!

已经放了github链接,上面有现成的exe文件
10#
 楼主| ZhangYixiSuccee 发表于 2026-4-6 19:37 |楼主
m16yjq 发表于 2026-4-6 19:16
不明觉厉,楼主有成品么

有的,github上面有现成的exe文件
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-4-9 06:02

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表