五、阶段五:换肤核心(最精妙的部分)
5.1 游戏的模型系统
每个角色对象(英雄/小兵/防御塔)内部有一个角色数据栈:
cppclass CharacterDataStack { std::vector<CharacterStackData> stack; // 变形/特殊状态压栈 CharacterStackData base_skin; // 当前基础外观};class CharacterStackData { AString model; // 模型名(如 "Ahri", 特殊形态 "LuxFire") std::int32_t skin; // 皮肤 ID std::int8_t gear; // 形态索引(部分英雄)};
游戏引擎从这个栈重建角色的视觉模型。这就是杠杆点:改栈里数据 → 调游戏的 Update → 游戏自己加载并渲染新模型。工具不伪造任何模型, 只是"改需求单,让游戏自己换货"。
5.2 换肤三步(AIBaseCommon::change_skin)
cpp1. 写 SkinId 字段: reinterpret_cast<xor_value<int32>*>(this + SkinId偏移)->encrypt(skin); ★ SkinId 在内存里是 Riot 的滚动 XOR 加密存储(xor_value:密钥 + 4 值表 + 轮换索引),必须用同款算法加密后写回——写明文会被 游戏的解密逻辑读出错值;2. 写 base_skin.model 与 base_skin.skin;3. 调游戏的 CharacterDataStack::Update(true) → 引擎按新数据重建模型 → 画面上皮肤变了。
Push/Update都是调用游戏自己的函数(特征码定位),Push的 17 参数签名是逆向得出的固定组合(模型名、皮肤 ID、一组标志位)。
5.3 特殊皮肤处理(checkSpecialSkins)
形态类英雄(卡特琳娜/雷克顿/厄运小姐变体):同步修改gear形态索引,否则模型与特效对不上;凯隐是唯一的 gear 例外;拉克丝大元素使 / 娑娜 DJ:同一 skin_id 对应多种形态——清空 整个栈,压入特殊模型名(LuxFire/SonaDJGenre02);其他英雄:gear 重置为 -1。
5.4 防御塔皮肤 ID 规律
changeTurretSkin:skinId * 2(蓝方)/skinId * 2 + 1(红方)—— 防御塔皮肤 ID 在游戏数据里按蓝红方成对相邻编排(逆向发现的规律)。
5.5 为什么服务器看不见
皮肤数据栈是纯客户端渲染状态——服务器只记录"你选的英雄、拥有的 皮肤",客户端画什么模型它不校验。换肤不影响伤害判定,其他玩家看到的 你不变。风险因此完全来自客户端侧的环境采集(ACE 在你电脑里看到的 模块与行为),而非行为异常。