吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1045|回复: 8
上一主题 下一主题
收起左侧

[原创] Nobody(大多数)游戏修改学习笔记--第二弹

[复制链接]
跳转到指定楼层
楼主
huchen 发表于 2026-6-4 03:09 回帖奖励

书接上回

上一贴:[b]Nobody(大多数)游戏修改学习笔记

(感觉继续写在上一帖的话太长了,索性就重新开一贴)

我们先处理上一帖留下的问题,衣物整洁度我目前无法实现编辑的功能,只能hook实现无限数值了,代码会在后面按照顺序讲解

我个人喜欢以调试的视角去解析代码,所以我这里会按照执行顺序一步一步解读

首先是主函数

int main()
{
        //读取目标游戏进程ID
        DWORD game_PID = GetProcessIDByName(L"Nobody.exe");
        if (game_PID == 0)
        {
                printf("未找到目标游戏进程\n");
                return 1;
        }

        HANDLE game_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, game_PID);

    ////游戏模块基址                                 
        LPVOID base_address = GetModuleBaseAddress(game_PID, L"GameAssembly.dll");

        ChoiceMenu(game_handle, game_PID, base_address);
}

就是获取游戏的模块基址,怎么获取的呢

DWORD GetProcessIDByName(const wchar_t* process_gamename)
{
        //创建系统快照
        HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        if(snapshot == INVALID_HANDLE_VALUE)
        {
                return 0;
        }
        //初始化进程结构体
        PROCESSENTRY32 process_entry = {0};
        process_entry.dwSize = sizeof(PROCESSENTRY32);
        //获取第一个进程,这里意味着告诉系统“从这个快照的开头开始遍历”
        if(!Process32First(snapshot, &process_entry))
                {
                CloseHandle(snapshot);
                return 0;
        }
        //遍历所有进程
        do
        {
                if(wcscmp(process_entry.szExeFile, process_gamename) == 0)
                {
                        CloseHandle(snapshot);
                        return process_entry.th32ProcessID;
                }
        } while (Process32Next(snapshot, &process_entry));

        CloseHandle(snapshot);
        return 0;

}

这是根据给定的进程名查找并返回该进程的 ID(PID),这算是一种模板了,还有其它也可以实现,感兴趣的可以自行去了解

游戏进程我们拿到了,先前也说过了,游戏的运算逻辑绝大部分是在DLL里实现的,因此我们需要获取dll的模块基址,并非Nobody.exe的基址

LPVOID GetModuleBaseAddress(DWORD processID, const wchar_t* moduleName)
{
        LPVOID lpBaseAddress = nullptr;
        //打开目标进程
        HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processID);
        if(hProcess != NULL)
        {
                HMODULE hmod[MAX_PATH];
                DWORD cbNeeded;
                //枚举进程内的模块
                if (EnumProcessModules(hProcess, hmod, sizeof(hmod), &cbNeeded))
                {
                        //计算模块数量
                        DWORD moduleCount = cbNeeded / sizeof(HMODULE);
                        for (DWORD i = 0; i < moduleCount; i++)
                        {
                                wchar_t szModuleName[MAX_PATH];
                                //遍历并匹配名称
                                if (GetModuleBaseName(hProcess, hmod[i], szModuleName, sizeof(szModuleName) / sizeof(wchar_t)))
                                {
                                        if (wcscmp(szModuleName, moduleName) == 0)
                                        {
                                                lpBaseAddress = hmod[i];
                                                break;
                                        }
                                }
                        }
                }
                CloseHandle(hProcess);
        }

        return lpBaseAddress;
}

这是获取模块基址的方法,也算是一种模板了,与上面的函数搭配起来就是查询基址的双子星,嘿嘿,当然也有其它方法,可以自行了解

然后进入我们的选择菜单

void ChoiceMenu(HANDLE game_handle, DWORD game_PID, LPVOID base_address)
{
        DWORD choice;

        printf("*********************************\n");
        printf("*\t欢迎游玩大多数游戏\t*\n");

        printf("*********************************\n");

        printf("* 模块基址: 0x%" PRIxPTR "\t*\n", base_address);

        printf("* 请选择要修改的数值类型:\t*\n");
        printf("* 0.退出修改\t\t\t*\n");
        printf("* 1.金钱\t\t\t*\n");
        printf("* 2.饱腹度\t\t\t*\n");
        printf("* 3.情绪值\t\t\t*\n");
        printf("* 4.心态\t\t\t*\n");
        printf("* 5.衣物整洁度\t\t\t*\n");
        printf("* 6.技能点\t\t\t*\n");

    //可后续添加新的属性修改

        printf("*********************************\n");

        do
        {
                printf("请选择要修改的数值:\n");
                scanf_s("%u", &choice);

                switch (choice)
                {
                        case 1:
                                {
                                        FLOAT num;
                                        printf("请输入金钱数值: ");
                                        scanf_s("%f", &num);
                                        SetGameValue(game_handle, base_address, Money, num);
                                        printf("金钱数值已修改为: %.2f\n", num);
                                        break;
                                }
                        case 2:
                                {
                                        FLOAT num;
                                        printf("请输入饱腹度数值: ");
                                        scanf_s("%f", &num);
                                        SetGameValue(game_handle, base_address, Food, num);
                                        printf("饱腹度数值已修改为: %f\n", num);
                                        break;
                                }
                        case 3:
                                {
                                        FLOAT num;
                                        printf("请输入情绪值数值: ");
                                        scanf_s("%f", &num);
                                        SetGameValue(game_handle, base_address, emotion, num);
                                        printf("情绪值数值已修改为: %f\n", num);
                                        break;
                                }
                        case 4:
                                {
                                        FLOAT num;
                                        printf("请输入心态数值: ");
                                        scanf_s("%f", &num);
                                        SetGameValue(game_handle, base_address, Mindset, num);
                                        printf("心态数值已修改为: %f\n", num);
                                        break;
                                }
                        case 5:
                                {
                                        CHAR tmp;
                                        static LPVOID hook_addr;
                                        printf("请选择 a.无限整洁度 or b.取消无限整洁度\n");
                                        scanf_s(" %c", &tmp, 1);
                                        if (tmp == 'a')
                                        {
                                                hook_addr = HookGetClothClean(game_handle, base_address);
                                                if (hook_addr != NULL)
                                                {
                                                        printf("已成功安装衣物整洁度无限的Hook\n");
                                                }
                                                else
                                                {
                                                        printf("安装衣物整洁度无限的Hook失败\n");
                                                }

                                        }
                                        else if (tmp == 'b')
                                        {
                                                if (UnHook(game_handle, hook_addr, Clothingclean))
                                                {
                                                        printf("已成功取消衣物整洁度无限的Hook\n");
                                                }
                                                else
                                                {
                                                        printf("取消衣物整洁度无限的Hook失败\n");
                                                }

                                        }
                                        else
                                        {
                                                printf("无效的选择\n");
                                        }

                                        break;
                                }
                        case 6:
                                {
                                        UINT num;
                                        CHAR tmp;
                                        static LPVOID hook_addr;
                                        printf("请选择 a.编辑技能点 or b.无限技能点 or c.取消无限技能点\n");
                                        scanf_s(" %c", &tmp,1);
                                        if (tmp == 'a')
                                        {
                                                printf("请输入技能点数值: ");
                                                scanf_s("%u", &num);
                                                SetGameValue(game_handle, base_address, SkillPoint, num);
                                                printf("技能点数值已修改为: %u\n", num);
                                                break;
                                        }
                                        else if (tmp == 'b')
                                        {
                                                hook_addr = HookGetSkillPiont(game_handle, base_address);
                                                if (hook_addr != NULL)
                                                {
                                                        printf("已成功安装技能点无限的Hook\n");
                                                }
                                                else
                                                {
                                                        printf("安装技能点无限的Hook失败\n");
                                                }
                                                break;
                                        }
                                        else if (tmp == 'c')
                                        {
                                                if (UnHook(game_handle, hook_addr, SkillPoint))
                                                {
                                                        printf("已成功取消技能点无限的Hook\n");
                                                }
                                                else
                                                {
                                                        printf("取消技能点无限的Hook失败\n");
                                                }
                                                break;
                                        }
                                        else
                                        {
                                                printf("无效的选择\n");
                                                break;
                                        }
                                }
          //可后续添加新的属性修改
                }
        } while (choice);

}

这里感觉没什么说的,主要是选择菜单的搭建以及修改操作的逻辑判断与执行

这里先介绍一下常量和一些定义

typedef enum Type{
        Money, Food, emotion, Mindset, Clothingclean, SkillPoint, TypeCount
}ValueType;

typedef struct {
        DWORD* offset;
        size_t offsetsize;
        size_t valuesize;
        const char* name;
} ValueInfo;

//各属性偏移
static DWORD money_offset[] = { 0xB8, 0, 0xB0, 0x28, 0x18 };
static DWORD food_offset[] = { 0xB8, 0, 0xB0, 0x28, 0x20 };
static DWORD emotion_offset[] = { 0xB8, 0, 0xB0, 0x30, 0x10 };
static DWORD Mindset_offset[] = { 0xB8, 0, 0xB0, 0x28, 0x40 };
static DWORD Clothingclean_offset[] = { 0xB8, 0, 0xB0, 0x28, 0x4C };
static DWORD skillpoint_offset[] = { 0xB8, 0, 0xB0, 0x28, 0xB0};

//技能点数值地址偏移和Hook代码
static UINT64 get_skillpoint_offset = 0x377CD0;
static DWORD Original_get_skillpointCode[6] = { 0x40, 0x53, 0x48, 0x83, 0xEC, 0x40 };
static DWORD Hook_get_skillpointCode[6] = { 0xB8, 0, 0, 0, 0, 0xC3 };

static ValueInfo value_infos[TypeCount] = {
        { money_offset, sizeof(money_offset) / sizeof(DWORD), sizeof(int), "Money" },
        { food_offset, sizeof(food_offset) / sizeof(DWORD), sizeof(int), "Food" },
        { emotion_offset, sizeof(emotion_offset) / sizeof(DWORD), sizeof(int), "Emotion" },
        { Mindset_offset, sizeof(Mindset_offset) / sizeof(DWORD), sizeof(int), "Mindset" },
        { Clothingclean_offset, sizeof(Clothingclean_offset) / sizeof(DWORD), sizeof(int), "Clothingclean" },
        { skillpoint_offset, sizeof(skillpoint_offset) / sizeof(DWORD), sizeof(int), "SkillPoint" }
};

这里的通用属性,我的想法是写个结构体来存储,方便后续的通用函数的传参,不用特定的去为不特别属性重新定义

这里感觉通过名称很容易辨识出来,就不多说

然后就是SetGameValue函数,顾名思义--设置游戏数值

template<typename T>
void SetGameValue(HANDLE handle, LPVOID baseaddr, UINT Type, T EditValue)
{
        LPVOID value_address = (LPVOID)((uintptr_t)baseaddr + 0x29b9d78); //示例偏移地址
        switch (Type)
        {
        case Money:
                ReadProcessMemory(handle, value_address, &value_address , sizeof(value_address), NULL);
                value_address = (LPVOID)GetValueAddress(handle, value_address, Money);
                printf("金钱数值地址: 0x%" PRIxPTR "\n", value_address);
                WriteProcessMemory(handle, value_address, &EditValue, sizeof(EditValue), NULL);
                break;
        case Food:
                ReadProcessMemory(handle, value_address, &value_address, sizeof(value_address), NULL);
                value_address = (LPVOID)GetValueAddress(handle, value_address, Food);
                printf("饱腹度数值地址: 0x%" PRIxPTR "\n", value_address);
                WriteProcessMemory(handle, value_address, &EditValue, sizeof(EditValue), NULL);
                break;
        case emotion:
                ReadProcessMemory(handle, value_address, &value_address, sizeof(value_address), NULL);
                value_address = (LPVOID)GetValueAddress(handle, value_address, emotion);
                printf("情绪值数值地址: 0x%" PRIxPTR "\n", value_address);
                WriteProcessMemory(handle, value_address, &EditValue, sizeof(EditValue), NULL);
                break;
        case Mindset:
                ReadProcessMemory(handle, value_address, &value_address, sizeof(value_address), NULL);
                value_address = (LPVOID)GetValueAddress(handle, value_address, Mindset);
                printf("心态数值地址: 0x%" PRIxPTR "\n", value_address);
                WriteProcessMemory(handle, value_address, &EditValue, sizeof(EditValue), NULL);
                break;
        case SkillPoint:
                ReadProcessMemory(handle, value_address, &value_address, sizeof(value_address), NULL);
                value_address = (LPVOID)GetValueAddress(handle, value_address, SkillPoint);
                printf("技能点数值地址: 0x%" PRIxPTR "\n", value_address);
                EditSkillPoint(handle, value_address, (UINT)EditValue);
                break;
        }
}

这里通过枚举,来定向选择想要修改的数值,方法就是通过模块基址,依据提前写好的偏移,一步一步正确的地址,然后通过API去修改该地址的数据,从而达到修改游戏数据的操作

函数的参数我用了模板,因为有不同类型的参数会传进来,后续的属性也不一定是同类型,所以选择用模板更通用

注意:偏移并不是单纯的相加,而是一个指针链(GPT这么说的),通俗点讲就是俄罗斯套娃,一个地址套一个,所以应该是该地址指向的值去加上偏移,然后指向数据的直接地址,还不懂的话,那就看代码

uintptr_t GetValueAddress(HANDLE hprocess, LPVOID baseaddr, ValueType type)
 {
        if (type > TypeCount) return 0;
        ValueInfo* desc = &value_infos[type];
        return DerefPointerChain(hprocess, baseaddr, desc->offset, desc->offsetsize);
}

uintptr_t DerefPointerChain(HANDLE hprocess, LPVOID baseaddr, DWORD offset[], size_t offsetCount)
{
        uintptr_t currentAddress = (uintptr_t)baseaddr;
        for (size_t i = 0; i < offsetCount; i++)
        {
                currentAddress += offset[i];

                if (i == offsetCount - 1)
                {
                        return currentAddress;
                }

                if (!ReadProcessMemory(hprocess, (LPCVOID)currentAddress, ¤tAddress, sizeof(currentAddress), NULL))
                {
                        return 0;
                }
        }
        return 0;
}

GetValueAddress函数算是个过渡函数,整理下一个函数需要的参数,主要是DerefPointerChain函数需要的参数有点多

从代码来看其实也没有多难,无非就是循环一下拼指针链而已

接下来就是编辑技能点了(吐个槽:我就纳闷了,一个单机游戏为啥数值还要加密,真就不明白了)

这个技能点呢我刚开始改的时候是挺迷茫的,无从下手,但是看到风大大的修改器却可以做到,我就纳闷了,我又回到CE,认真研究起来看到玩家属性里有个_talenPoints

看到这个类型是MemoryCheck.MemoryInt,感觉这里面藏着加密的算法,然后就去看看,在搜索栏里输入,选择MemoryCheck.MemoryInt,可以看到

在询问了GPT后,他告诉我GetDecrypted里或许有我想找的算法,

这两个代码让我警觉起来,原因是在之前的尝试中_talenPoints的值也是高位与低位相异或,所以我询问GPT,他也说这两行代码比较可疑,于是这个rcx的值怎么确定呢

我尝试下断点,结果没有停下来,我突然想起_talenPoints的地址就是rcx吗,不然这个怎么高低位去异或呢,想到着我就去验证,在CE中去改变值,然后通过异或来得到想要的值,发现真的可以,哈哈,总算被我逮到了

接下来就是代码的演示了

void EditSkillPoint(HANDLE hprocess, LPVOID SkillPointAddr, UINT EditNum)
{
        UINT64 OriginalValue = 0;
        DWORD OriHighValue;
        DWORD OriLowValue;

        ReadProcessMemory(hprocess, (LPCVOID)SkillPointAddr, &OriginalValue, sizeof(OriginalValue), NULL);
        if(OriginalValue == 0)
        {
                printf("未找到技能点数值地址\n");
                return;
        }

        OriHighValue = (DWORD)(OriginalValue >> 32);
        OriLowValue = (DWORD)(OriginalValue & 0xFFFFFFFF);
        //构造自己想要的数值
        OriLowValue = OriHighValue ^ EditNum;
        OriginalValue = ((UINT64)OriHighValue << 32) | OriLowValue;

        //修改内存保护以允许写入
        DWORD oldProtect;
        VirtualProtectEx(hprocess, (LPVOID)SkillPointAddr, sizeof(OriginalValue), PAGE_EXECUTE_READWRITE, &oldProtect);
        //写入修改的值
        WriteProcessMemory(hprocess, (LPVOID)SkillPointAddr, &OriginalValue, sizeof(OriginalValue), NULL);
        //恢复原来的内存保护
        VirtualProtectEx(hprocess, (LPVOID)SkillPointAddr, sizeof(OriginalValue), oldProtect, &oldProtect);
}

他的偏移可以在那界面看到,是0xB0

后面就是构造自己想要的值,然后通过异或来编辑成自己修改的数值

然后是两个hook

一个技能点一个衣物整洁度,我感觉实现的代码都大差不差,主要是hook那个地址,这里我就简单说一下

技能点的hook的是

这个直接修改获取的结果,实现无限的功能

整洁度的首先把第一层的改写地址找到,应该是get_Clean函数,然后进入到汇编界面,一直拉到函数最后,发现有个函数

在跟踪到这个函数,这个函数就是管理衣物整洁的函数,就可以hook了

注意:这个hook的是当前装备的衣服,不包含放在背包的衣服

这里贴上代码

//hook衣物整洁度
LPVOID HookGetClothClean(HANDLE hprocess, LPVOID base_address)
{
        static UINT64 getclothclean_offset = 0x382250;
        LPVOID hook_addr = (LPVOID)((uintptr_t)base_address + getclothclean_offset);
        BYTE hook_code[] = { 
                0xFF, 0x31,                                                                // push [rcx]
                0xC7, 0x01, 0x00, 0x00, 0xC8, 0x42,                // mov dword ptr [rcx], 0x42C80000 (100.0f的IEEE 754表示) + ret
                0xF3, 0x0F, 0x10, 0x01,                                        // movss xmm0, [rcx]
                0x8F, 0x01,                                                                // pop [rcx]
                0xC3                                                                        // ret
        };
        BYTE original_code[sizeof(hook_code)] = {0};

        if (!ReadProcessMemory(hprocess, hook_addr, original_code, sizeof(original_code), NULL))
        {
                return 0;
        }
        if (!WriteProcessMemory(hprocess, hook_addr, hook_code, sizeof(hook_code), NULL))
        {
                printf("写入Hook代码失败\n");
                return NULL;
        }

        return hook_addr;
}

//hook技能点
LPVOID HookGetSkillPiont(HANDLE hprocess, LPVOID base_address)
{
        static UINT64 get_skillpoint_offset = 0x377CD0;
        LPVOID hook_addr = (LPVOID)((uintptr_t)base_address + get_skillpoint_offset);
        BYTE original_code[6] = {0}, hook_code[6] = {0xB8, 0x37, 0x25, 0x00, 0x00, 0xC3};
        if(!ReadProcessMemory(hprocess, hook_addr, original_code, sizeof(original_code), NULL))
        {
                return 0;
        }
        if (!WriteProcessMemory(hprocess, hook_addr, hook_code, sizeof(hook_code), NULL))
        {
                printf("写入Hook代码失败\n");
                return NULL;
        }
        return hook_addr;
}

//取消hook
BOOL UnHook(HANDLE hprocess, LPVOID hook_addr, UINT Type)
{
        if (Type == SkillPoint)
        {
                BYTE original_code[6] = { 0x40, 0x53, 0x48, 0x83, 0xEC, 0x40 };
                if (!WriteProcessMemory(hprocess, hook_addr, original_code, sizeof(original_code), NULL))
                {
                        printf("恢复原始代码失败\n");
                        return FALSE;
                }
                return TRUE;
        }
        else if(Type == Clothingclean)
        {
                BYTE original_code[] = 
                {
                        0x48,0x89,0x5C,0x24,0x08,
                        0x48,0x89,0x74,0x24,0x10,
                        0x57,0x48,0x83,0xEC,0x40
                };
                if (!WriteProcessMemory(hprocess, hook_addr, original_code, sizeof(original_code), NULL))
                {
                        printf("恢复原始代码失败\n");
                        return FALSE;
                }
                return TRUE;
        }
        else
        {
                printf("无效的数值类型\n");
                return FALSE;
        }

}

下面的就是取消钩子,没什么说的了,硬编码就是在改之前提前存储的

PS:这里的地址我也是按照偏移来弄得,但是我实验了好几次,发现偏移不会变,因此就直接写死偏移了,想要更稳妥一点看可以尝试AOB扫描。

至此,现阶段的修改器就介绍完毕,后续有新添加的属性我也会继续写在这篇帖子之后

附上完整代码

#include<stdio.h>
#include <iostream>
#include<windows.h>
#include <tlhelp32.h>
#include <string.h>
#include <shlwapi.h>
#include <psapi.h>
#include <inttypes.h>

typedef enum Type{
        Money, Food, emotion, Mindset, Clothingclean, SkillPoint, TypeCount
}ValueType;

typedef struct {
        DWORD* offset;
        size_t offsetsize;
        size_t valuesize;
        const char* name;
} ValueInfo;

//各属性偏移
static DWORD money_offset[] = { 0xB8, 0, 0xB0, 0x28, 0x18 };
static DWORD food_offset[] = { 0xB8, 0, 0xB0, 0x28, 0x20 };
static DWORD emotion_offset[] = { 0xB8, 0, 0xB0, 0x30, 0x10 };
static DWORD Mindset_offset[] = { 0xB8, 0, 0xB0, 0x28, 0x40 };
static DWORD Clothingclean_offset[] = { 0xB8, 0, 0xB0, 0x28, 0x4C };
static DWORD skillpoint_offset[] = { 0xB8, 0, 0xB0, 0x28, 0xB0};

//技能点数值地址偏移和Hook代码
static UINT64 get_skillpoint_offset = 0x377CD0;
static DWORD Original_get_skillpointCode[6] = { 0x40, 0x53, 0x48, 0x83, 0xEC, 0x40 };
static DWORD Hook_get_skillpointCode[6] = { 0xB8, 0, 0, 0, 0, 0xC3 };

static ValueInfo value_infos[TypeCount] = {
        { money_offset, sizeof(money_offset) / sizeof(DWORD), sizeof(int), "Money" },
        { food_offset, sizeof(food_offset) / sizeof(DWORD), sizeof(int), "Food" },
        { emotion_offset, sizeof(emotion_offset) / sizeof(DWORD), sizeof(int), "Emotion" },
        { Mindset_offset, sizeof(Mindset_offset) / sizeof(DWORD), sizeof(int), "Mindset" },
        { Clothingclean_offset, sizeof(Clothingclean_offset) / sizeof(DWORD), sizeof(int), "Clothingclean" },
        { skillpoint_offset, sizeof(skillpoint_offset) / sizeof(DWORD), sizeof(int), "SkillPoint" }
};

uintptr_t DerefPointerChain(HANDLE hprocess, LPVOID baseaddr, DWORD offset[], size_t offsetCount)
{
        uintptr_t currentAddress = (uintptr_t)baseaddr;
        for (size_t i = 0; i < offsetCount; i++)
        {
                currentAddress += offset[i];

                if (i == offsetCount - 1)
                {
                        return currentAddress;
                }

                if (!ReadProcessMemory(hprocess, (LPCVOID)currentAddress, ¤tAddress, sizeof(currentAddress), NULL))
                {
                        return 0;
                }
        }
        return 0;
}

uintptr_t GetValueAddress(HANDLE hprocess, LPVOID baseaddr, ValueType type)
 {
        if (type > TypeCount) return 0;
        ValueInfo* desc = &value_infos[type];
        return DerefPointerChain(hprocess, baseaddr, desc->offset, desc->offsetsize);
}

DWORD GetProcessIDByName(const wchar_t* process_gamename)
{
        //创建系统快照
        HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        if(snapshot == INVALID_HANDLE_VALUE)
        {
                return 0;
        }
        //初始化进程结构体
        PROCESSENTRY32 process_entry = {0};
        process_entry.dwSize = sizeof(PROCESSENTRY32);
        //获取第一个进程,这里意味着告诉系统“从这个快照的开头开始遍历”
        if(!Process32First(snapshot, &process_entry))
                {
                CloseHandle(snapshot);
                return 0;
        }
        //遍历所有进程
        do
        {
                if(wcscmp(process_entry.szExeFile, process_gamename) == 0)
                {
                        CloseHandle(snapshot);
                        return process_entry.th32ProcessID;
                }
        } while (Process32Next(snapshot, &process_entry));

        CloseHandle(snapshot);
        return 0;

}

void EditSkillPoint(HANDLE hprocess, LPVOID SkillPointAddr, UINT EditNum)
{
        UINT64 OriginalValue = 0;
        DWORD OriHighValue;
        DWORD OriLowValue;

        ReadProcessMemory(hprocess, (LPCVOID)SkillPointAddr, &OriginalValue, sizeof(OriginalValue), NULL);
        if(OriginalValue == 0)
        {
                printf("未找到技能点数值地址\n");
                return;
        }

        OriHighValue = (DWORD)(OriginalValue >> 32);
        OriLowValue = (DWORD)(OriginalValue & 0xFFFFFFFF);
        //构造自己想要的数值
        OriLowValue = OriHighValue ^ EditNum;
        OriginalValue = ((UINT64)OriHighValue << 32) | OriLowValue;

        //修改内存保护以允许写入
        DWORD oldProtect;
        VirtualProtectEx(hprocess, (LPVOID)SkillPointAddr, sizeof(OriginalValue), PAGE_EXECUTE_READWRITE, &oldProtect);
        //写入shellcode
        WriteProcessMemory(hprocess, (LPVOID)SkillPointAddr, &OriginalValue, sizeof(OriginalValue), NULL);
        //恢复原来的内存保护
        VirtualProtectEx(hprocess, (LPVOID)SkillPointAddr, sizeof(OriginalValue), oldProtect, &oldProtect);
}

LPVOID HookGetClothClean(HANDLE hprocess, LPVOID base_address)
{
        static UINT64 getclothclean_offset = 0x382250;
        LPVOID hook_addr = (LPVOID)((uintptr_t)base_address + getclothclean_offset);
        BYTE hook_code[] = { 
                0xFF, 0x31,                                                                // push [rcx]
                0xC7, 0x01, 0x00, 0x00, 0xC8, 0x42,                // mov dword ptr [rcx], 0x42C80000 (100.0f的IEEE 754表示) + ret
                0xF3, 0x0F, 0x10, 0x01,                                        // movss xmm0, [rcx]
                0x8F, 0x01,                                                                // pop [rcx]
                0xC3                                                                        // ret
        };
        BYTE original_code[sizeof(hook_code)] = {0};

        if (!ReadProcessMemory(hprocess, hook_addr, original_code, sizeof(original_code), NULL))
        {
                return 0;
        }
        if (!WriteProcessMemory(hprocess, hook_addr, hook_code, sizeof(hook_code), NULL))
        {
                printf("写入Hook代码失败\n");
                return NULL;
        }

        return hook_addr;
}

LPVOID HookGetSkillPiont(HANDLE hprocess, LPVOID base_address)
{
        static UINT64 get_skillpoint_offset = 0x377CD0;
        LPVOID hook_addr = (LPVOID)((uintptr_t)base_address + get_skillpoint_offset);
        BYTE original_code[6] = {0}, hook_code[6] = {0xB8, 0x37, 0x25, 0x00, 0x00, 0xC3};
        if(!ReadProcessMemory(hprocess, hook_addr, original_code, sizeof(original_code), NULL))
        {
                return 0;
        }
        if (!WriteProcessMemory(hprocess, hook_addr, hook_code, sizeof(hook_code), NULL))
        {
                printf("写入Hook代码失败\n");
                return NULL;
        }
        return hook_addr;
}

BOOL UnHook(HANDLE hprocess, LPVOID hook_addr, UINT Type)
{
        if (Type == SkillPoint)
        {
                BYTE original_code[6] = { 0x40, 0x53, 0x48, 0x83, 0xEC, 0x40 };
                if (!WriteProcessMemory(hprocess, hook_addr, original_code, sizeof(original_code), NULL))
                {
                        printf("恢复原始代码失败\n");
                        return FALSE;
                }
                return TRUE;
        }
        else if(Type == Clothingclean)
        {
                BYTE original_code[] = 
                {
                        0x48,0x89,0x5C,0x24,0x08,
                        0x48,0x89,0x74,0x24,0x10,
                        0x57,0x48,0x83,0xEC,0x40
                };
                if (!WriteProcessMemory(hprocess, hook_addr, original_code, sizeof(original_code), NULL))
                {
                        printf("恢复原始代码失败\n");
                        return FALSE;
                }
                return TRUE;
        }
        else
        {
                printf("无效的数值类型\n");
                return FALSE;
        }

}

LPVOID GetModuleBaseAddress(DWORD processID, const wchar_t* moduleName)
{
        LPVOID lpBaseAddress = nullptr;
        //打开目标进程
        HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processID);
        if(hProcess != NULL)
        {
                HMODULE hmod[MAX_PATH];
                DWORD cbNeeded;
                //枚举进程内的模块
                if (EnumProcessModules(hProcess, hmod, sizeof(hmod), &cbNeeded))
                {
                        //计算模块数量
                        DWORD moduleCount = cbNeeded / sizeof(HMODULE);
                        for (DWORD i = 0; i < moduleCount; i++)
                        {
                                wchar_t szModuleName[MAX_PATH];
                                //遍历并匹配名称
                                if (GetModuleBaseName(hProcess, hmod[i], szModuleName, sizeof(szModuleName) / sizeof(wchar_t)))
                                {
                                        if (wcscmp(szModuleName, moduleName) == 0)
                                        {
                                                lpBaseAddress = hmod[i];
                                                break;
                                        }
                                }
                        }
                }
                CloseHandle(hProcess);
        }

        return lpBaseAddress;
}

template<typename T>
void SetGameValue(HANDLE handle, LPVOID baseaddr, UINT Type, T EditValue)
{
        LPVOID value_address = (LPVOID)((uintptr_t)baseaddr + 0x29b9d78); //示例偏移地址
        switch (Type)
        {
        case Money:
                ReadProcessMemory(handle, value_address, &value_address , sizeof(value_address), NULL);
                value_address = (LPVOID)GetValueAddress(handle, value_address, Money);
                printf("金钱数值地址: 0x%" PRIxPTR "\n", value_address);
                WriteProcessMemory(handle, value_address, &EditValue, sizeof(EditValue), NULL);
                break;
        case Food:
                ReadProcessMemory(handle, value_address, &value_address, sizeof(value_address), NULL);
                value_address = (LPVOID)GetValueAddress(handle, value_address, Food);
                printf("饱腹度数值地址: 0x%" PRIxPTR "\n", value_address);
                WriteProcessMemory(handle, value_address, &EditValue, sizeof(EditValue), NULL);
                break;
        case emotion:
                ReadProcessMemory(handle, value_address, &value_address, sizeof(value_address), NULL);
                value_address = (LPVOID)GetValueAddress(handle, value_address, emotion);
                printf("情绪值数值地址: 0x%" PRIxPTR "\n", value_address);
                WriteProcessMemory(handle, value_address, &EditValue, sizeof(EditValue), NULL);
                break;
        case Mindset:
                ReadProcessMemory(handle, value_address, &value_address, sizeof(value_address), NULL);
                value_address = (LPVOID)GetValueAddress(handle, value_address, Mindset);
                printf("心态数值地址: 0x%" PRIxPTR "\n", value_address);
                WriteProcessMemory(handle, value_address, &EditValue, sizeof(EditValue), NULL);
                break;
        case SkillPoint:
                ReadProcessMemory(handle, value_address, &value_address, sizeof(value_address), NULL);
                value_address = (LPVOID)GetValueAddress(handle, value_address, SkillPoint);
                printf("技能点数值地址: 0x%" PRIxPTR "\n", value_address);
                EditSkillPoint(handle, value_address, (UINT)EditValue);
                break;
        }
}

void ChoiceMenu(HANDLE game_handle, DWORD game_PID, LPVOID base_address)
{
        DWORD choice;

        printf("*********************************\n");
        printf("*\t欢迎游玩大多数游戏\t*\n");

        printf("*********************************\n");

        printf("* 模块基址: 0x%" PRIxPTR "\t*\n", base_address);

        printf("* 请选择要修改的数值类型:\t*\n");
        printf("* 0.退出修改\t\t\t*\n");
        printf("* 1.金钱\t\t\t*\n");
        printf("* 2.饱腹度\t\t\t*\n");
        printf("* 3.情绪值\t\t\t*\n");
        printf("* 4.心态\t\t\t*\n");
        printf("* 5.衣物整洁度\t\t\t*\n");
        printf("* 6.技能点\t\t\t*\n");

        printf("*********************************\n");

        do
        {
                printf("请选择要修改的数值:\n");
                scanf_s("%u", &choice);

                switch (choice)
                {
                        case 1:
                                {
                                        FLOAT num;
                                        printf("请输入金钱数值: ");
                                        scanf_s("%f", &num);
                                        SetGameValue(game_handle, base_address, Money, num);
                                        printf("金钱数值已修改为: %.2f\n", num);
                                        break;
                                }
                        case 2:
                                {
                                        FLOAT num;
                                        printf("请输入饱腹度数值: ");
                                        scanf_s("%f", &num);
                                        SetGameValue(game_handle, base_address, Food, num);
                                        printf("饱腹度数值已修改为: %f\n", num);
                                        break;
                                }
                        case 3:
                                {
                                        FLOAT num;
                                        printf("请输入情绪值数值: ");
                                        scanf_s("%f", &num);
                                        SetGameValue(game_handle, base_address, emotion, num);
                                        printf("情绪值数值已修改为: %f\n", num);
                                        break;
                                }
                        case 4:
                                {
                                        FLOAT num;
                                        printf("请输入心态数值: ");
                                        scanf_s("%f", &num);
                                        SetGameValue(game_handle, base_address, Mindset, num);
                                        printf("心态数值已修改为: %f\n", num);
                                        break;
                                }
                        case 5:
                                {
                                        CHAR tmp;
                                        static LPVOID hook_addr;
                                        printf("请选择 a.无限整洁度 or b.取消无限整洁度\n");
                                        scanf_s(" %c", &tmp, 1);
                                        if (tmp == 'a')
                                        {
                                                hook_addr = HookGetClothClean(game_handle, base_address);
                                                if (hook_addr != NULL)
                                                {
                                                        printf("已成功安装衣物整洁度无限的Hook\n");
                                                }
                                                else
                                                {
                                                        printf("安装衣物整洁度无限的Hook失败\n");
                                                }

                                        }
                                        else if (tmp == 'b')
                                        {
                                                if (UnHook(game_handle, hook_addr, Clothingclean))
                                                {
                                                        printf("已成功取消衣物整洁度无限的Hook\n");
                                                }
                                                else
                                                {
                                                        printf("取消衣物整洁度无限的Hook失败\n");
                                                }

                                        }
                                        else
                                        {
                                                printf("无效的选择\n");
                                        }

                                        break;
                                }
                        case 6:
                                {
                                        UINT num;
                                        CHAR tmp;
                                        static LPVOID hook_addr;
                                        printf("请选择 a.编辑技能点 or b.无限技能点 or c.取消无限技能点\n");
                                        scanf_s(" %c", &tmp,1);
                                        if (tmp == 'a')
                                        {
                                                printf("请输入技能点数值: ");
                                                scanf_s("%u", &num);
                                                SetGameValue(game_handle, base_address, SkillPoint, num);
                                                printf("技能点数值已修改为: %u\n", num);
                                                break;
                                        }
                                        else if (tmp == 'b')
                                        {
                                                hook_addr = HookGetSkillPiont(game_handle, base_address);
                                                if (hook_addr != NULL)
                                                {
                                                        printf("已成功安装技能点无限的Hook\n");
                                                }
                                                else
                                                {
                                                        printf("安装技能点无限的Hook失败\n");
                                                }
                                                break;
                                        }
                                        else if (tmp == 'c')
                                        {
                                                if (UnHook(game_handle, hook_addr, SkillPoint))
                                                {
                                                        printf("已成功取消技能点无限的Hook\n");
                                                }
                                                else
                                                {
                                                        printf("取消技能点无限的Hook失败\n");
                                                }
                                                break;
                                        }
                                        else
                                        {
                                                printf("无效的选择\n");
                                                break;
                                        }

                                }
                }
        } while (choice);

}

int main()
{
        //读取目标游戏进程ID
        DWORD game_PID = GetProcessIDByName(L"Nobody.exe");
        if (game_PID == 0)
        {
                printf("未找到目标游戏进程\n");
                return 1;
        }

        HANDLE game_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, game_PID);

        //游戏模块基址
        LPVOID base_address = GetModuleBaseAddress(game_PID, L"GameAssembly.dll");

        ChoiceMenu(game_handle, game_PID, base_address);
}

免费评分

参与人数 6吾爱币 +4 热心值 +5 收起 理由
GDExecW + 1 我很赞同!
MJ_B + 1 热心回复!
weidechan + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
FZZZP + 1 + 1 我很赞同!
ZZ730605 + 1 + 1 热心回复!
Bulaixi + 1 + 1 谢谢@Thanks!

查看全部评分

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

沙发
bigmojin 发表于 2026-6-4 07:19
学习一下,留个爪印,谢谢!
3#
ZZ730605 发表于 2026-6-4 09:01
4#
Bankai 发表于 2026-6-4 09:49
5#
baihuxianzi 发表于 2026-6-4 12:16
学习一下
6#
xzz1203 发表于 2026-6-4 17:55
如果在不使用mono功能,仅看附近数据结构的情况下,怎么猜是技能点&#129300;
7#
afti 发表于 2026-6-4 18:24
继续学习大神修改笔记
8#
 楼主| huchen 发表于 2026-6-4 18:52 |楼主
xzz1203 发表于 2026-6-4 17:55
如果在不使用mono功能,仅看附近数据结构的情况下,怎么猜是技能点&#129300;

这里的技能点是被加密的,就是8字节数据,高低位异或得出技能点数值,你说的是哪个数据结构,总之单纯搜数值,是改不了的
9#
GDExecW 发表于 2026-6-4 20:53
学习一下,感谢大佬分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2026-6-4 23:06

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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