查看: 39|回复: 0

[教程] 《技术分享》0828源码拆解

[复制链接]

25

主题

0

回帖

131

积分

超级版主

积分
131
怒气
0
声望
53
战力
0
发表于 前天 12:24 | 显示全部楼层 |阅读模式
本帖最后由 阿拉德流云 于 2026-5-28 12:32 编辑

【技术分享】0828源码拆解
很多朋友拿到这套源码之后打开就懵了,全是没注释的C/C++,文件夹一堆版本号,不知道从哪下手。我就把我理解的东西写出来,大家一起研究,讲错了欢迎指出。
先说清楚这套源码是什么东西
首先这套东西不是DNF的官方服务端,别搞混了。
我们手头有的服务端内核是老版本的(0627那套),但是我们想用0828的客户端玩,因为0828有女鬼剑、二觉、武器装扮这些东西。问题就来了,老服务端发出去的封包是按老格式打包的,新客户端按新格式去读,两边对不上,进游戏直接闪退。
所以这套源码干的事情说白了就是:在服务端和客户端中间夹了个中间层,专门负责把不合格的包改造成合格的包再喂给客户端。
两端各是什么东西,干什么的
服务端这边(Linux环境跑的)
核心文件就是根目录那个 df_game_r.c,编译出来是个 .so 文件。
用的是Linux的 LD_PRELOAD 机制。简单说就是,你让服务端主进程启动之前,先把这个.so塞进去,它就能把老服务端里面的一些函数提前拦截掉,换成我们自己写的版本。服务端本体不知道被动了手脚,照常跑。
客户端这边(Windows,玩家本地)
核心文件在那个VS工程里,主要是 ProtocolMonitor.cpp 还有各个 PacketXXFix.inc,编译出来是个 .dll。
注入到游戏进程里,用的是IAT Hook,就是拦截Windows底层的收发包函数 recv 和 send,游戏客户端收到数据之前,先让我们的dll过一遍,改完了再给游戏,游戏本体同样不知道。
拿背包扩容和武器装扮槽来举例,讲服务端和客户端怎么配合的
0627服务端根本没有武器装扮和收集箱这系统,背包也只有48格。0828客户端要求背包更大,而且每件物品的数据结构也变了,从25字节变成了32字节。
服务端干的事(df_game_r.c)
打开 df_game_r.c 文件头部,你会看到这些:
// 基于 IDA 逆向老服务端的汇编入口基址
static const uintptr_t k_ida_image_base = 0x8048000u;
static const uintptr_t k_va_InterfacePacketBuf_finalize = 0x80cb958u;
// 强行修改底层背包上限
#define TYPE0_RECORD_COUNT 312
#define TYPE0_RECORD_SIZE 61
#define TYPE0_RECORD_BYTES (TYPE0_RECORD_COUNT * TYPE0_RECORD_SIZE)
这些地址是通过IDA逆向老服务端二进制文件得到的,对应的是服务端里面分配背包内存的位置。
TYPE0_RECORD_COUNT 312 这个就是强制把背包格子数扩到312。老服务端本来只打算分配48格的内存,我们的.so在它分配内存那一刻拦下来,强行改成312格。服务端不管这些多出来的格子是干嘛的,它只知道内存够用,不会越界崩溃。
服务端这边做的事情其实就这一件:把内存撑大,让自己不崩,数据塞进去发出去就完事了。
7.jpg
客户端干的事(Packet14Fix.inc)
服务端虽然把内存撑大了,但它发出来的每件物品数据还是老格式,25字节一件。0828客户端要求32字节一件,直接读就炸。
客户端这里:客户端干的事(Packet14Fix.inc)
服务端虽然把内存撑大了,但它发出来的每件物品数据还是老格式,25字节一件。0828客户端要求32字节一件,直接读就炸。
客户端dll拦到这个包之后,做的事情是这样的:
cppconst unsigned int legacy_record_size = 25u; // 老版本每件物品25字节
const unsigned int promoted_record_size = 32u; // 新版本要求32字节
for (unsigned int i = 0; i < (unsigned int)count; ++i) {
const unsigned char* old_rec = payload + 3u + i * legacy_record_size;
unsigned char promoted[32] = {};
// 把25字节按规则塞进32字节的新结构里
PromotePacket13LegacyRecord25To32(old_rec, promoted);
// 槽位重新映射,因为0828多了武器装扮槽,原来的槽位号全错位了
unsigned short old_slot = /* 读老服务端的穿戴槽位号 */;
const unsigned short mapped_slot = MapPacket13Type0Slot0627To0828(old_slot);
promoted[0] = (unsigned char)(mapped_slot & 0xFFu);
promoted[1] = (unsigned char)((mapped_slot >> 8) & 0xFFu);
// 拼回去
}
两件事:
第一,把25字节拉长到32字节,多出来的部分用0或者默认属性补上,0828客户端按32字节读,正好对得上。
第二,槽位号重新映射。因为0828加了武器装扮槽,导致原来老版本里宠物槽是9号,新版宠物槽变成10号了,你不改的话穿戴数据全错位。MapPacket13Type0Slot0627To0828 这个函数就是个对照表,把老槽位号换算成新槽位号再写回去。
这两步做完,伪造出一个0828客户端认识的包,喂给它,它以为收到了正常的数据,正常运行。
20.jpg
g_characlist_expand_enabled(开启角色列表数量扩容) g_town_oob_bypass_enabled(绕过城镇坐标越界检测) g_town_oob_log_silence_enabled(关闭城镇越界时的报错日志) g_creature_script_bypass_enabled(绕过宠物脚本异常检测) g_disjoint_bypass_enabled(绕过分解机功能异常检测) g_disjoint_log_silence_enabled(关闭分解机异常报错日志) g_quest_new_enemy_type_compat_enabled(开启任务新怪物类型兼容) 主要工作确实是大小写还有一些额外补丁。


本文转自:https://tieba.baidu.com/p/10748451847?fr=frs

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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