《大侠立志传》的存档一打开就是乱码,这是很多想“魔改”玩家卡住的第一道坎。作为一款基于Unity引擎开发的游戏,它默认使用了Easy Save 3(ES3)这套存档方案,而ES3在启用加密后会把整个存档内容变成一段无法直接阅读的二进制密文。你就算把.sav文件用记事本强行打开,看到的也是“ES3”开头的一大堆无意义字符。
这篇文章会把这套ES3存档的完整链路讲透:文件结构长什么样、加密逻辑是什么、怎么把存档还原成明文JSON、改哪些字段最实用、改完如何加密回写,以及我在实际操作中踩过的各种坑。整个过程不需要你精通编程,只要会复制脚本、跟着操作就能跑通,特别适合想自己动手改属性、改功法、改物品数量的玩家,也适合想研究Unity存档机制的Mod作者收藏。
1. 先搞清楚ES3存档的基本盘
1.1 大侠立志传为什么不能直接改存档
很多从老游戏过来的玩家,默认思维是“存档文件就是文本,找到数字改掉就行”。但《大侠立志传》这种现代Unity游戏用的存档方案,跟老游戏的RPGMaker存档或者明文XML存档完全不是一回事。
Easy Save 3是Unity生态里占有率很高的一套序列化插件,它把游戏里的对象(玩家数据、背包、任务进度、世界状态)直接序列化成二进制流,再根据配置决定是否加密。大侠立志传在存盘时开启了加密模式,所以落盘的存档文件经过了两层处理:第一层是序列化,把C#对象变成字节数组;第二层是加密,把字节数组通过对称加密算法变成不可读的密文。
这意味着你拿任何文本编辑器去搜索“金币”“等级”“攻击”之类的关键词,什么都搜不到。更麻烦的是,如果直接拿十六进制编辑器乱改几个字节,存档大概率会因为密文被破坏而无法读取。游戏的存档加载流程是“读取文件 -> 解密 -> 反序列化”这三步,任何一步出错,游戏就会认为存档损坏。
清楚了这一点,思路就很直接了:想改存档,就得把这三步倒着走一遍,先解密、再反序列化,改完数据后正着走一遍,序列化、再加密写回。整个过程核心就是“ES3存档解密修改”这六个字。
1.2 ES3的加密链路拆解
ES3的加密机制可以理解成一层“保险箱”:保险箱本身是文件,箱子里的物品是序列化后的数据,而开锁钥匙就是游戏的加密密码。ES3支持AES加密,这是目前非常常见的对称加密标准,密钥长度、加密模式、IV向量这些参数都由插件在初始化时确定。
实际落地时,ES3存档文件的结构大致分为两段:
- 文件头部:包含ES3格式标识、版本号、加密头信息,这一段是明文,用来告诉ES3插件“这个存档是谁写的、用什么版本写的”。
- 数据主体:保存着全部游戏状态JSON的密文,正常情况下无意义,只有持有密钥才能解开。
为什么要把头信息暴露出来?因为这决定了ES3能不能正确识别文件版本。你修改完数据重新加密时,这段头信息要保持原样或者按规则重建,不能瞎改。我在一开始折腾的时候,就犯过“改了头文件导致游戏完全不认存档”的错,后面会详细说。
从玩家角度,你要搞定的核心问题就两个:
- 游戏程序里写死的加密密钥是什么。
- 数据段用了什么加密参数(算法、模式、填充方式)。
这两个信息都在游戏代码里。用dnSpy或ILSpy这类反编译工具打开游戏的Assembly-CSharp.dll(Unity游戏主要逻辑程序集),搜索“ES3”“Encrypt”“Password”之类的关键词,能找到一段设置加密密码的代码。有了密钥之后,解密就只是写一段脚本的事。
1.3 一眼识别ES3文件的方法
有经验的人拿到一个不知道格式的存档,第一件事是看文件头。我用HxD(十六进制编辑器)打开大侠立志传的存档,头部几个字节一如既往是“ES3”三个ASCII字符,后面跟着版本标识。看到这个开头,基本就能断定是Easy Save 3系。
辨别是不是ES3还有一个简单粗暴的方法:文件体积往往比较大。由于ES3会把整个游戏世界状态都序列化进去,大侠立志传这种地图和队友数量很多的游戏,存档动不动就是几MB到十几MB,跟老游戏几KB的文本存档比起来不在一个量级。
另外一个识别点是文件名的后缀。虽然理论上ES3不挑后缀,但很多游戏开发者会自己定义个独特的后缀名,比如大侠立志传可能用“.sav”“.dat”之类。后缀并不重要,认准二进制头才是关键。
2. 开工前的准备:工具、路径与备份习惯
2.1 定位存档文件的三个常用位置
Unity游戏存档位置通常有三个套路:用户文档目录、本地Low目录、游戏安装目录。大侠立志传这类单机游戏一般会放到“我的文档\My Games\”下以游戏命名的文件夹里,但也不排除放到C盘的用户AppData目录下。
我自己的查找顺序是这样:
- 打开“文档\My Games\”,看有没有游戏名或开发商名的文件夹。
- 如果找不到,按Win+R输入%USERPROFILE%\AppData\LocalLow,找开发商或游戏名目录。
- 再不行,去游戏安装目录下找“Save”“saves”“Data”这类文件夹。
找到存档文件夹后,建议你先看一眼有几个存档文件,大概什么时候修改过,心里有个底。这个文件夹就是之后反复刷新、反复实验的地方。
2.2 需要准备的修改工具清单
解密修改ES3存档不需要重型设备,但下面几样东西是免不了的:
- HxD或其他十六进制编辑器:用来查看文件头和验证解密结果,不是必须,但强烈建议装一个。
- Python 3环境:跑解密脚本和加密回写脚本。没有Python就去官网装,装的时候勾选“Add Python to PATH”。
- pycryptodome库:Python的加密库,用来做AES解密和加密。装完Python后在命令行执行pip install pycryptodome。
- dnSpy或ILSpy:可选,但如果你拿到的游戏密钥不对,就得靠它去游戏程序集里找真正的密钥。
- 文本编辑器:推荐Notepad++或VS Code,用来编辑解密后的JSON文件。
不要被这么多工具吓到,实际流程里你真正深度用到的就是Python和文本编辑器,其他都是辅助。
2.3 两块不能省的准备工作
第一块是备份。这听起来是老生常谈,但我真见过不少玩家改完存档发现游戏读不了,然后跑来问“怎么恢复”。ES3存档一旦写错,游戏会直接判定存档损坏,没有后悔药。我的习惯是先把整个存档文件夹原封不动复制一份到桌面或别的盘,做好标注“原始备份”,每次改之前都重新备份一次当前版本。
第二块是关掉云存档。如果开启了Steam云存档或官方云存档,游戏启动时可能会从云端拉取旧存档,把你刚改好的本地存档覆盖回去。我踩过这个坑:辛辛苦苦改了一上午,重新进游戏发现角色数据回滚了,原因是云同步把旧档拉了下来。所以改存档期间,要么在游戏设置里关闭云存档,要么在Steam云存档管理里禁用同步。
准备工作做完后,整个工作流就固定下来了:备份存档 -> 拷贝一份存档到工作目录 -> 解密 -> 编辑JSON -> 加密回写 -> 放回存档目录 -> 进游戏验证。
3. 核心解密步骤:把二进制还原成看得懂的JSON
3.1 ES3文件结构与边界定位
解密的第一步,是先把文件头和数据区分开。这一步非常关键,因为ES3文件不是“整个文件一层密文”,而是“头部明文+主体密文”的结构。如果你直接把整个文件丢进解密函数,得到的结果一定是一堆乱码。
我这里以最常见的ES3导出格式为例,解析流程是:
- 读取整个文件到内存。
- 从文件开头开始扫描,找头部信息结束的位置。
- 把头部之后的字节作为密文取出来,送到AES解密函数。
- 解密后的字节数组用UTF-8解码,得到JSON字符串。
头部的结束位置怎么判断?ES3格式里,文件头信息的最后会有一段特定的结束标记。不同版本细节不完全一样,但有一个通用的笨办法:从第0个字节开始逐字节扫描,找到“两个连续的换行符”或“头部声明结束后紧跟加密块的标记”。如果硬编码找不到,也可以用固定偏移量。
我自己的经验是:大侠立志传用的ES3版本,头部结束的位置离文件开头并不远,几十个字节就能看到边界。你可以用HxD打开存档,肉眼观察前面一小段明文部分到哪里截止,后面开始出现完全随机的字节,那个位置就是“明文头和密文体”的分界线。记录下这个字节偏移量,后面脚本直接按这个偏移量切割。
3.2 写一个可用的解密脚本(含代码)
下面这个Python脚本是我在实际操作中调整出来的基础版,逻辑简单直接,你复制保存成es3_decrypt.py就能用:
import os from Crypto.Cipher import AES from Crypto.Util.Padding import unpad # ===== 需要根据实际情况修改的配置 ===== FILE_PATH = r"C:\path\to\your\save.sav" OUTPUT_PATH = r"C:\path\to\your\save_decrypted.json" HEADER_SIZE = 64 # 根据HxD观察到的头文件长度来改 KEY = b"your-game-encryption-key" # 换成在游戏代码里找到的密钥 IV = b"0000000000000000" # 大部分ES3默认用空IV,或者去代码里找 with open(FILE_PATH, "rb") as f: encrypted_data = f.read()[HEADER_SIZE:] cipher = AES.new(KEY, AES.MODE_CBC, iv=IV) plaintext = unpad(cipher.decrypt(encrypted_data), AES.block_size) with open(OUTPUT_PATH, "wb") as f: f.write(plaintext) print("解密完成,明文已保存到:", OUTPUT_PATH)脚本里的几个参数需要你根据实际情况替换:
- FILE_PATH:拷贝出来的存档文件路径,注意是对着副本操作。
- HEADER_SIZE:文件头部长度,用HxD数一下明文头占了多少字节。
- KEY:加密密钥,字符串需要按字节匹配,不够16字节要补足,超过16字节要裁切。AES密钥长度必须是16、24或32字节。
- IV:初始化向量,最常见的是全零16字节,如果你的游戏代码里没特别设置IV,就保持全零。
这里有同学会问:密钥长度不够16字节怎么办?ES3处理密钥时通常会做SHA256哈希后截取,但不同的游戏可能直接用原始密码的字节数组并做PKCS7填充。最稳妥的方式还是从代码里看它怎么生成Key和IV,照搬逻辑。如果实在找不到,可以用Python先把字符串转成bytearray再补零到16的倍数试试。
3.3 解密成功的判断标准
脚本跑完以后,如果输出文件大小正常且能打开,恭喜你,存档已经成功解密。
解密完成的明显标志是:用文本编辑器打开生成的JSON文件,能看到成段的可读内容,比如角色名字、等级、经验值、物品ID、任务状态等。大侠立志传的存档解密后通常是一个超大的嵌套JSON,里面会有“playerInfo”“inventory”“questData”之类的顶层字段。
如果你打开输出文件,看到的还是一堆“锟斤拷”或者明显乱码,那就说明密钥不对、偏移量不对、或者加密参数不匹配。解密不是一次就能成功的,需要来回试几次。我自己的经验是:先在HxD里确定头部偏移,再去反编译里核对密钥,两步都对了基本一次过。
4. 改存档的实战:属性、金钱与可变数据
4.1 解密后的数据长什么样
当你第一次成功解密出完整JSON,大概率会懵,字段实在太多了。别慌,你只需要关注跟你修改目标相关的几个部分。
以大侠立志传为例,常见的数据区块大致可以归成几类:
- 玩家角色数据:等级、经验、生命、内力、攻击、防御、六维属性(体质、灵敏、悟性等)通常都在一个叫“player”或“role”或“actor”的对象里。
- 资源数据:金钱、门派贡献、声望、制作材料等,可能散落在“inventory”“resources”“wallet”这类字段里。
- 物品数据:背包物品列表、数量、装备数据,通常是一个数组,每个元素有物品ID和数量字段。
- 功法/技能数据:已学习的武功、等级、经验等,一般叫“skills”“kungfu”“abilities”。
- 任务与剧情数据:主线任务进度、分支选择、NPC好感度等,随处可见“quest”“relation”“favorability”。
不建议一口气把整个JSON都搞懂,那工作量太大。你需要做的就是:先在游戏里看一眼当前角色的某项数值,然后去JSON里搜索这个数值。比如游戏里显示金钱是1580,那就在解密后的文件里搜“1580”。这个数字绝大部分情况下能直接定位到对应字段,非常高效。
4.2 修改玩家属性与资源(含代码/示例)
定位到目标字段后,修改本身没什么技术含量,就是把数字改大、改小,或者把物品数量改成你想要的值。但有几个细节需要特别注意:
第一,要区分字段类型。有的字段是数字类型,改成字符串会导致反序列化失败;有的字段是字符串,写成数字也会报错。解密后的JSON里,数字不带引号,字符串带引号,一眼就能分辨。
第二,物品数量如果是一个独立字段,直接改数值通常有效;但有些游戏的背包系统,数量是按堆叠计算的,同一个物品可能存在多行记录,你只改一行可能不够。还是老办法,在游戏里确认数量,搜索到所有出现的位置,一起改。
第三,修改要符合数据逻辑。举例来说,大侠立志传的人物属性可能有上限(比如单项属性上限是100),你改成9999,游戏加载时可能会触发非法数据检测,直接拒绝读档。改数值不是越大越好,我的原则是:先改成略高于正常范围的数值,进游戏测试,如果没问题再继续加。
修改完的字段,我自己会做一个简单的“改动记录表”,在文本编辑器里搜到哪个字段、原值是多少、改成多少,全部记下来。这样如果进游戏发现异常,能快速定位到是哪一项改出的问题。
4.3 动手改之前的三条原则
第一条是“一次只改一类数据”。想同时改金钱、等级、物品和好感度,听起来很爽,但一旦出错,你根本不知道是哪个字段写坏了。我的流程是:第一轮只改金钱,进游戏验证;第二轮改属性,再验证;所有改动都通过后再做一次“最终版”。
第二条是“注意JSON的合法性”。JSON对引号、逗号、花括号极其敏感。你哪怕多打了一个逗号,游戏加载时JSON解析就会失败,存档直接作废。所以修改完以后,最好用JSON校验工具检查一下,或者用VS Code打开,看整个文件有没有红色波浪线报错。
第三条是“尊重游戏的数据计算规则”。很多属性不是直接存储的裸值,而是带计算公式的。比如攻击力可能等于基础攻击力加装备加成加功法加成,你只改存储的“攻击力”字段,可能被其他系统覆盖或者被重新计算。遇到这种情况,就要去找基础属性、功法等级、装备品质这些“源头字段”,改源头才稳定。
5. 把改好的数据重新加密写回
5.1 加密回写脚本(含代码)
修改完JSON文件,剩下的事就是把它重新序列化、加密、并拼回头部,生成一个游戏能正常读取的存档。加密回写的Python脚本跟解密脚本是对称的:
import json from Crypto.Cipher import AES from Crypto.Util.Padding import pad # ===== 配置,与解密时保持一致 ===== JSON_PATH = r"C:\path\to\your\save_decrypted.json" OUTPUT_PATH = r"C:\path\to\your\save_new.sav" HEADER_PATH = r"C:\path\to\your\original_header.bin" # 从原存档提取的头部 KEY = b"your-game-encryption-key" IV = b"0000000000000000" with open(JSON_PATH, "rb") as f: plaintext = f.read() cipher = AES.new(KEY, AES.MODE_CBC, iv=IV) encrypted_data = cipher.encrypt(pad(plaintext, AES.block_size)) with open(HEADER_PATH, "rb") as hf: header = hf.read() with open(OUTPUT_PATH, "wb") as f: f.write(header + encrypted_data) print("加密完成,新存档已保存到:", OUTPUT_PATH)回写脚本里有一个容易忽略的步骤:你千万不要用解密前读取的整个原文件,而是要用“原文件的头部”去拼接“新加密的数据”。原因很简单,头部是明文,里面记录了文件的格式版本和加密参数,这些信息在修改过程中不能动。所以我在解密之前,会先单独把原文件的头部字节保存成一个bin文件,供加密回写时使用。
如果你一开始没保存头部,可以从原始备份的存档文件里重新读取前HEADER_SIZE个字节,效果一样。
5.2 回写后的验证流程
新存档生成之后,先别急着替换原存档,一定要先在游戏外用工具验证一次。验证分几步:
- 用HxD打开新存档,确认头部前几个字节跟原存档完全一致,后面数据区的字节顺序跟原存档不同(因为内容改了)。
- 试着用解密脚本再解一次新存档,看能不能解出一个跟你修改后JSON完全一样的明文。
- 确认无误后,把新存档复制到存档目录,替换或改名成游戏能识别的文件名。
第一次替换建议走“保守路线”:把原存档改名成save_backup.sav,把新存档命名成save.sav。这样万一新档有问题,马上能把旧档改回来,不用重新解包备份文件夹。
替换完以后进游戏,看主菜单能否正常显示存档信息,能否正常进入游戏、查看角色面板。如果一切正常,恭喜,存档修改大成功。
6. 常见报错与排查实录
6.1 解密乱码的几种原因
| 问题现象 | 最常见原因 | 解决办法 |
|---|---|---|
| 解密输出跟原文件差不多长但全是乱码 | 密钥错误或密钥编码方式不对 | 用dnSpy去游戏程序集里核对密钥字符串,注意字符串用UTF-8还是ASCII,循环左补或右补到16字节 |
| 解密输出只有一小段可读,后面全乱 | 数据偏移量不对,切割位置偏了 | 重新用HxD核对头部边界,多试几个偏移量 |
| 报错“Input data must be a multiple of the block size” | 解密前没有按块大小对齐 | 确认你取出的密文长度是16的倍数,如果不是,说明切割位置错了 |
| 报错“Padding is incorrect” | IV不对或密钥不对 | 检查IV是否为全零;密钥是否做了额外变换(如哈希取前16位) |
第一个乱码问题最常见,原因是游戏代码里设置的加密密码不是平铺的字符串,而是先做了某种变换。ES3的AES加密密码默认会用SHA256做哈希,取前32字节作为256位密钥。如果我在脚本里直接用原始密码的16字节,解密出来的就是乱码。这种情况下,你需要先对密码做hashlib.sha256计算再把digest传给AES。
6.2 回写后游戏无法读档的排查
游戏不认新存档,九成原因是文件头被破坏了,或者JSON不合法。
我记得很清楚,第一次回写时偷懒,直接用“整个原文件”跟“新加密数据”做了拼接,忘了头文件里其实还藏了文件长度和校验信息。结果游戏读完档直接弹“存档损坏”。后来老老实实只切头部、只拼数据,问题就消失了。
还有一个容易忽略的点:加密后的数据长度可能跟原数据长度不一致。如果你修改的字段变长(比如把物品名称改成更长的字符串),加密块大小也会变化。游戏加载时如果对文件长度有固定校验,就会拒绝读取。碰到这种情况,你得在JSON里保持数据规模跟原版接近,不要新增或删除大段结构,只改数值是最稳妥的。
6.3 数值改了但游戏里没变化的坑
你解密、修改、加密、回写全都成功了,进游戏却发现数值纹丝不动,这种挫败感我太懂了。
这个问题的根源通常是“改错地方”。游戏数据可能存在多份:一份是角色面板显示的“派生属性”,一份是底层的“基础属性”。金币也可能在多个地方有记录,比如背包里有一份“钱包数值”,任务系统里有一份“累计获得”,你只改了前者,后者读档时把前者覆盖了。
解决办法是先确认字段唯一性。在JSON文件里搜索你要改的数值,如果搜出来好几个地方都有,全部改成目标值再加密回写。如果只有一个地方,那基本就是正确字段。
另外有一种特殊情况:有些游戏读档后会触发“自动校验修复”,发现数据异常直接悄悄改成合理值。这种就没办法靠改存档解决,只能先改一个小幅度数值试水,看游戏是否容忍。
6.4 我的存档修改工作流最终定稿
做了这么多,最后把我现在的完整工作流整理给大家,照着做基本不会再翻车:
- 关闭Steam云存档,打开存档目录,完整备份整个文件夹到桌面。
- 复制一份存档到工作目录,用HxD确认文件头部和数据区边界,计算出HEADER_SIZE,并把头部单独保存成header.bin。
- 用dnSpy确认游戏加密密钥,跑解密脚本,得到明文JSON。
- 在JSON里搜索游戏里对应的数值,定位目标字段,一次只改一类数据,改完用VS Code检查JSON合法性。
- 跑加密回写脚本,生成新存档,用解密脚本反向验证一遍。
- 把新存档复制进存档目录,把旧存档改名留底。
- 进游戏验证,如果失败,删掉新存档,把旧存档改回来。
这套流程我前前后后改了几十次存档,除了第一次因为头文件拼接踩了坑,后面基本每次都能一次成功。真正关键的节点其实就三个:头部边界能不能找对、密钥能不能拿对、JSON改完合不合法。三个节点都控制住,ES3存档解密修改就不存在什么玄学问题。
最后分享一个小技巧:解密后的JSON文件建议保留一份,不用重复解密。以后想再改,直接对明文JSON动手,重新加密一次就行。省去每次解密的步骤,还能顺便练手做一套自己专用的人物模板,比如“开局满资质模板”“锁定功法模板”,以后新开周目直接套用,能省掉大量重复坐牢时间。