Steam 云同步机制解析
最后更新时间:
文章总字数:
页面浏览: 加载中...
本文重点解析了 Steam Cloud 的两种同步机制(API vs Auto-Cloud)、Root ID 映射原理、Auto-Cloud 存档“删不掉”的底层原因,以及通过 UFS 注入和 API 调用实现的实用解决方案。文中部分结论是基于观察和推断得出的,并不一定百分之百准确,如有错误,欢迎指正。
由于 Steam 官方的 Remote Storage 网页端(客户端路径:帮助 -> Steam 客服 -> 我的账户 -> 您 Steam 帐户的相关数据 -> 上传的内容 - Steam 云)仅支持查看和下载,不提供删除等管理操作,若要深度管理云存档就必须调用底层接口。
然而官方 API 文档对云同步内部机制的描述十分匮乏。在开发 Steam Cloud File Manager(以下简称云存档管理器)的过程中,我深入研究并梳理了 Steam 云的底层同步逻辑。
Steam 云的工作逻辑
Steam 的云同步架构其实分为三层:
- 游戏本地存档根目录(Root Path):游戏在本地文件系统中读写存档的基准位置(比如
我的文档/My Games/、AppData/Local/等)。 - Steam Userdata 远程缓存目录:
Steam/userdata/<UserID>/<AppID>/remote/。 - Steam 云端:Valve 的服务器。
Steam 客户端主要维护的是Steam Userdata 远程缓存目录和云端的一致性。至于存档如何进入这个目录并同步到云端,可以分为两种机制。
两种同步机制
Steam 云主要通过两种方式实现同步。根据 Steamworks 文献库 的描述,这两种方式可以独立使用,也可以同时配置。而游戏具体使用了哪种方式,直接决定了存档同步是由游戏程序主动控制,还是交由 Steam 客户端根据预设规则自动同步。
Steam Cloud API (ISteamRemoteStorage)
游戏代码直接调用 Steam 提供的 ISteamRemoteStorage 接口来管理云端文件。文件的读写、删除操作都通过 API 完成,而不是直接操作文件系统。它的特点是相对可控,开发者明确知道哪些文件需要同步。
官方文档中对关键 API 有明确定义:
FileWrite: 创建一个新文件,将字节写入文件,再关闭文件。 目标文件若已存在,将被覆盖。
FileDelete: 从本地磁盘中删除一个文件,并将该删除传播到云端。
因为是游戏明确发出的删除指令,Steam 会直接执行同步,不会像自动云那样尝试重新下载已经在本地被手动删除的文件。
场景一:写入存档 (FileWrite)
当游戏调用 FileWrite API 时,Steam 优先处理本地操作:立即将数据写入硬盘,并在本地云缓存索引文件 remotecache.vdf(以下简称”云缓存”)中更新元数据(大小、哈希值等),同时将 syncstate(同步状态)标记为待上传。API 立即返回成功,实际的网络上传由 Steam 客户端在后台异步完成。
1 | |
场景二:删除存档 (FileDelete)
当游戏调用 FileDelete API 时,Steam 删除本地物理文件,但保留云缓存中的记录并将同步状态标记为待删除——后台同步程序需要依据该记录才能通知服务器删除对应文件。待云端确认删除后,这条记录才被最终清除。
1 | |
VDF 的容错校验机制
一个自然的疑问是:如果用户手动删除了云缓存中的记录(而非通过 API),Steam 会怎么处理?
为了验证这一点,我做了一组实验:在游戏关闭后,手动从 VDF 中删除了几条 root=0 的文件记录,然后用云存档管理器短暂连接 API 再断开,触发一次同步。结果发现:被我删掉的 VDF 记录全部自动恢复了。
查阅 Steam 客户端的 cloud_log.txt,可以清晰地看到整个容错校验的过程:
1 | |
这段日志揭示了 Steam 的两层容错机制:
ChangeNumber 版本追踪:每次云端同步都会递增一个全局版本号(ChangeNumber)。手动清空 VDF 后,本地版本号被重置为
0,而服务器端仍为40。Steam 检测到版本差异,触发了一次full完整同步。ValidateCache 校验:同步过程中,Steam 会逐一比对本地物理文件的 SHA 与缓存记录。由于我只删除了 VDF 记录而没有删除物理文件,校验发现
SHA mismatch with cache(本地文件存在但缓存记录缺失),于是执行setting local changes——根据本地物理文件重建 VDF 记录,并标记为待上传。
也就是说,remotecache.vdf 虽然是本地核心索引,但并非唯一数据来源。服务器端 ChangeNumber 始终作为校验基准,即使本地 VDF 被篡改或清空,下次同步时 Steam 也会自动修复。这也解释了为什么手动删除 VDF 记录来清除云存档往往无效——服务器端元数据会在下次同步时恢复这些记录。
Steam Auto-Cloud (自动云)
开发者只需在 Steamworks 后台配置好存档文件的路径匹配规则即可。这些配置会被打包进本地的应用配置文件 appinfo.vdf(以下简称”应用配置”)中的 ufs(User File System,用户文件系统,以下简称 UFS)配置节,并下发到玩家的客户端。

Steam 客户端根据应用配置中的规则,在游戏启动和退出时对指定目录进行全量扫描,若检测到文件变更则立即在后台自动上传同步。
值得一提的是,在 Auto-Cloud 监控的存档目录下,Steam 会自动放置一个 steam_autocloud.vdf 文件,内容仅包含当前登录账户的 ID:
1 | |
该文件用于标识存档目录归属的 Steam 账户,在多账户共用时避免存档混淆,本身不参与同步逻辑。
Steamworks 文献库 中指出,每条自动云路径配置均由 Root(根目录)和 Subdirectory(子目录)组成。Root 是特定操作系统上一组预定义的路径(例如 我的文档/My Games/ 或 AppData/Local/),而 Subdirectory 则是相对于 Root 的具体路径。
这种基于预定义根文件夹 (Roots) 的设计至关重要。例如,开发者配置的规则通常是:“监控代表‘我的文档’的 WinMyDocuments (根目录) 下的 My Games/GameName/*.sav (子目录)”。
这意味着 Steam 需要在底层维护一套跨平台的根目录映射表,将抽象的变量名(如 WinMyDocuments)动态映射到当前玩家电脑上真实的落地路径(比如解析为你电脑里的 C:\Users\你的用户名\Documents)。这也正是我这项研究的核心 —— 弄清楚这些抽象的 “Root ID” 到底是如何跟实际文件系统挂钩的(详见下文 “Root ID” 章节)。
关于 UFS 注入与 AppInfo
既然规则存在本地文件中,理论上我们就能修改它。云存档管理器正是利用这一点,通过修改本地的应用配置来注入自定义的监控规则(UFS),从而直接改变 Steam Auto-Cloud 的默认扫描策略,使其客户端能够将任意未经官方设定的本地路径纳入自动同步的检测队列中。
这种基于单方面修改本地缓存的注入方案,在实际运作时面临以下局限与风险:
- 必须重启 Steam:Steam 仅在启动时加载配置,修改后需重启客户端生效。
- 配置重置:每次重启 Steam 客户端都会向服务器重新拉取请求从而覆盖本地应用配置(详见
logs/appinfo_log.txt)。此时自定义 UFS 配置即会失效。 - 强制静默删除:注入规则一旦失效,Steam 会在状态对账时发现大量“不受当前规则管辖”的文件。出于保护逻辑,后台会直接静默删除关联根目录下的这批数据以保持一致性。
为了绕开这个自动删除机制,云存档管理器利用了 Steamworks 里本来就支持的一项配置:跨平台路径映射 (Override)。Steam 设计这个配置是为了在不同操作系统的根目录间同步文件。在注入自定义规则时,只要加上 Override 设置,将目标的系统属性指定为 Linux 等其他平台。这样一来,Steam 依然能正常把文件传到云端;而在客户端重启引发规则重置时,当前处于 Windows 的 Steam 客户端就会判定“这些冗余的云端数据属于 Linux 平台,与我无关”,从而顺理成章地跳过针对本地数据的清理判定。
衍生讨论:软链接(Symlink)挂载方案可行性分析
在研究初期我产生过一个想法:既然 Steam 把 API 云存档存储在 userdata/{UID}/{AppID}/remote/ 目录下,那能不能通过 symlink 把外部文件夹挂载到 remote 里面,让 Steam Cloud 把这些文件也同步上去?
围绕 symlink 与 Steam Cloud 的话题,在不同游戏的社区里其实有过不少相关讨论(例如 r/Grimdawn 的讨论、Steam 社区的 Dwarf Fortress 指南 甚至玩家在 r/SteamDeck 上分享的 OpenCloudSaves 第三方同步工具 等)。
这些社区方案普遍有个共性:它们都放弃了让 Steam Cloud 主动同步,转而利用第三方云盘。 其本质是向外链接 (remote → 第三方云盘):用云盘挂载点替换本地的 remote 目录,Steam 写入时会被系统透明重定向完成跨设备同步。
而我最初的设想恰好相反,是 向内挂载 (外部文件夹 → remote 内),指望 Steam Cloud 能自动发现并上传它们。
但实测证明,挂载外部文件进 remote 无效,因为 Steam 不会主动扫描目录,只认 remotecache.vdf 的索引。即便我尝试在工具里自动调 API 注册文件,也面临“每次产生新文件都需手动重注册”的致命缺陷。说白了就是没卵用😅,比直接用云盘还折腾,因此最终我还是放弃了该思路(虽然已经写完了😋),全面转向 UFS 注入方案。
Auto-Cloud 存档难删的原因
主要是因为 Auto-Cloud 的策略核心是数据保护。Steam 默认假设:只要云端有文件而本地没有,即视为本地数据丢失,必须立即恢复。
这就导致了经典的“死循环”:用户在本地删除归档 -> 启动游戏 -> Steam 检测缺失 -> 自动从云端下载 -> 存档“复活”。
1 | |
如何彻底删除?
既然本地删除无效,就需要绕过保护机制强制执行远端移除。
1. 常规手段(制造冲突)
利用冲突解决机制覆盖云端数据是社区较常用的方案(参考 XT’s Steam Guide - 管理云存档):
- 关闭云同步(防止自动恢复)。
- 删除本地文件及
userdata下对应的remotecache.vdf索引。 - 开启云同步并启动游戏,触发冲突状态。
- 出现对话框后选择”上传本地文件”,以当前“空状态”强制覆盖删除云端旧存档。
2. Steam 控制台强制同步法
这是 Reddit - How to permanently delete Steam Cloud saves 给出的最新变通方案,利用 Steam 内置控制台的未公开命令强制上传本地“空状态”:
- 在本地磁盘中定位并直接删除该游戏的存档文件。
- 按
Win + R键,输入steam://open/console唤出 Steam 控制台。 - 在控制台输入命令
cloud_sync_up [AppID](替换为目标游戏的 AppID) 并执行。 - 这条指令会强制 Steam 以当前本地的最新状态(无文件)向云端发起全量上传覆盖。当你下次启动游戏时,Steam 便会同步这个“空壳”状态,从而实现清除云存档的目的。
3. 借助第三方工具直接调用 API
由于手动制造冲突费时费力,借助工具直接执行 API 调用是更高效且彻底的解法。
核心误区:实测证明,几乎所有 Auto-Cloud 上传的云端文件依然能被 API 强行删除。API 的真正限制在于写入权限的不对等:你可以用 API 删掉 Auto-Cloud 文件,却无法用 API 将新文件写回到特定的 Auto-Cloud 根目录(Root ≠ 0)。这正是上传非0目录必须依赖 UFS 注入的原因。
这种接口层“允许全局删除、限制定向写入”的割裂权限,加上官方管理面板长期缺失删除按钮,迫使玩家折腾各种偏门手段,也引来了社区无数针对这种极其固执的管理的吐槽(如 Steam 社区长线讨论)。
4. 未安装游戏能否直接调用 API 删除?
在未安装游戏的情况下直接调 API 删存档表现极不稳定:有的能成功连接抹除,有的则失败、无响应或仅能读到极少文件残片。就算换用 Watt Toolkit 或 SteamCloudFileManagerLite 也同样如此。这可能是由于本地缓存残缺、特殊的 Root 属性校验或服务端临时策略限制导致。
因此,如果遇上未安装游戏引发的 API 调用失败,目前最稳妥的保底方案仍是:安装游戏并运行一次 -> 确认同步网络已就绪 -> 使用工具删除 -> 删除成功后卸载游戏。
Root ID:通过反向验证建立映射表
为了实现 Auto-Cloud 的跨平台支持,Steam 使用了一套 ID 映射系统(Root ID)。每个 ID 代表一个抽象的系统路径。
问题在于:官方文档及网页管理后台仅公开了 Root 的字符串名称(如 WinMyDocuments),但在本地 remotecache.vdf 索引中记录的却是数字 ID。虽然在 Steamworks SDK 内部可以找到它的底层枚举定义,但缺乏跨平台具体的路径落地说明,很多开发者和玩家并不清楚其映射关系。
验证方法:自定义 UFS 注入
由于官方未公开这两者的对应关系,需要通过逆向实验来推导其映射表。验证思路如下:
找到一个已有云存储配额但没有配置 UFS
savefiles的现有应用 (即只有云空间配额quota,但没有配置自动同步规则的游戏,如骑砍1、黑魂系列、戴森球计划、Undertale或者一些工具类的应用)。使用 Steam 云存档管理器向该应用注入自定义的 UFS 配置,为每个已知的 Root 类型各创建一条
savefiles规则。

- 每条配置使用不同的 Root 名称 (
WinMyDocuments、MacHome等),并各绑定一个独立的测试文件。

- 注入配置后重启 Steam 客户端,通过云存档管理器连接并断开一次触发上传,再次连接即可看到上传成功的数据。

- 读取
remotecache.vdf,将每个测试文件对应的数字root值与其 UFS 配置中的字符串名称进行比对。

通过这种逐一验证的方法,就能精确地将 字符串名称 和 数字 ID 对应起来。
验证结果
以下是部分常见的 Root ID 映射关系预览:
| ID | 对应的 Root 名称 | 路径 |
|---|---|---|
| 0 | Default |
{Steam}/userdata/{UID}/{AppID}/remote/ (仅限 API 调用) |
| 1 | GameInstall |
{Steam}/steamapps/common/{Game}/ |
| 2 | WinMyDocuments |
%USERPROFILE%\My Documents\ |
| 3 | WinAppDataLocal |
%LOCALAPPDATA% |
| 4 | WinAppDataRoaming |
%APPDATA% |
| 10 | WinProgramData |
%PROGRAMDATA% |
| 11 | SteamCloudDocuments |
见下方说明 |
| 13 | MacCaches |
~/Library/Caches/ |
| 17 | AndroidSteamPackageRoot |
(Android 专用) |
| … | … | … |
数据来源:以上 Root ID 与名称的完整对应关系来自 Steam SDK 内部枚举
ERemoteStorageFileRoot(OpenSteamworks 镜像),路径映射则通过实际游戏测试和cloud_log.txt分析确认。
说来惭愧,这个枚举是在写这篇文章的过程中才找到的。也就是说,前面花了大量精力搞 UFS 注入、逐个游戏交叉对比来反向推导的 Root ID 映射表,人家 SDK 头文件里一直都写得清清楚楚——包括之前一直标为”未知”的 Root 5、10、13、17,全都有现成的命名 🤣
完整的 Root ID 映射表见 GitHub 仓库:ROOT_PATH_MAPPING.md
SteamCloudDocuments (Root 11) 路径说明
SteamCloudDocuments 是 Auto-Cloud 专用的 Root 类型,用于将存档存放在用户文档目录。官方文档中仅给出了”平台特定路径”的笼统描述,并以 Linux 为例提供了一条路径:~/.SteamCloud/[username]/[Game Folder]/,其余平台的路径并未公开。以下 macOS 和 Windows 的路径是通过分析各平台 cloud_log.txt 日志确认的:
| 平台 | SteamCloudDocuments (Root 11) 实际路径 |
|---|---|
| macOS | ~/Documents/Steam Cloud/[Steam用户名]/[游戏名]/ |
| Linux | ~/.SteamCloud/[Steam用户名]/[游戏名]/ |
| Windows | %USERPROFILE%\Documents\Steam Cloud\[Steam用户名]\[游戏名]\ |
额外发现:Android 支持
在分析 Steam 配置时,还发现 Steamworks 加入了对 Android 平台的云同步支持:

在 Steam 的配置数据 (ufs_raw) 中,可以看到这样一段 Root 定义:
1 | |
实际上,Android 的支持在 SDK 内部远比公开文档要早得多。在前面提到的 ERemoteStorageFileRoot 枚举中,k_ERemoteStorageFileRootAndroidSteamPackageRoot(Root 17)早在 2019 年 1 月的初始 commit 中就已经被定义了。但 Steamworks 后台 UI 直到最近(2026年2月份)才正式向开发者开放了 Android 平台的 Auto-Cloud 配置项,在官方的 Android APK 上传文档中的 Steam Cloud 配置步骤里,明确要求开发者选择 AndroidExternalData 作为 Root,并设置对应的子目录路径来匹配 Android 上的存档目录。
也就是说,Valve 在底层为 Android 云同步埋了至少五年的桩,才逐步将其暴露到面向开发者的公开接口中。
特殊的 AppID
在折腾和研究 Steam 云存储的过程中,我还在远程存储页面(Remote Storage)里发现了一些特殊的 AppID。
Steam Client AppID 7
起初注意到这个特殊的 AppID,正是因为它在远程存储页面中直接被定名为了 Steam Client:
通过 SteamDB (AppID 7) 进一步查证可以得知,它并非某款隐藏或未上架的游戏,而被明确标记为 Config 类型的内置应用。这正是 Steam 客户端专门用于存放账号级全局设定的一块云空间。

比较离谱的是🤔,明明只是一个用来存配置的“内部应用”,SteamDB 数据显示现在都还有 41 个人保持在线😱,不知道在挂什么神仙卡片😂,可能这 41 个人都是V社的员工
通过访问对应的本地 remote 目录(或利用云存档管理器从云端下载),可以提取出两个核心配置文件:

提取这些文件内的 VDF 文本结构进行分析,我大致推断出了这些数据的实际作用(如果有人了解更深层的机制,欢迎讨论和修正):

serverbrowser_hist.vdf:整个文件由一个根节点"Filters"组成,内部包含"favorites"(收藏)与"history"(历史)两个子域。以"history"为例,它以单调递增的数字作为键,逐条保存了连接过的服务器信息:其中"address"和"name"标明了服务器的 IP 与端口,"LastPlayed"是最后一次游玩的 UNIX 时间戳。值得注意的是,这些记录中的"appid"与"accountid"字段均为"0"。结合它的结构,这显然是 Steam 控制面板里调出的那个“服务器浏览器”的公共历史与收藏记录。sharedconfig.vdf:文件由"UserRoamingConfigStore"(用户漫游配置库)为根节点,用于在不同设备间同步你的各个客户端状态配置:- 在
"Software" -> "Valve" -> "Steam"节点下,存放着类似于"SurveyDate"(硬件调查日期)以及快捷方式创建选项等参数。 - 其子节点
"apps",以各个游戏的真实系统 AppID(如"730"、"504230")作为键名,分别记录着它们各自是否在本地开启了云同步("cloudenabled" "1")。 - 下方的
"JSClientStorage"等节点则偏向于 UI 层的交互状态,比如记录timeline_intro这种新特性弹窗是否已经被你阅览过(用"rtSeen"时间戳标记)。
- 在
实测发现:面对 AppID 7,服务端会直接拒绝基础的 API 连接请求。为了能下载这部分数据,我在代码中增加了拦截逻辑:遇到 AppID 7 直接跳过 API 调用,改用 CDP (Chrome DevTools Protocol) 从网页版抓取(见 src/app_handlers.rs):
1 | |
通过 CDP 虽能顺利读取下载,但在尝试发包删除时,服务端仍会报错拦截。因此对外部而言,这就是个被锁定的只读空间。
AppID 744350:聊天的图片缓存
在远程存储页面里,我还看到了一个没有名字的软件:AppID 744350。

虽然网页端完全没有任何描述,但通过 API 连接读取后,可以明确看到它对应的名字其实是“Steam Chat Images”:

为验证其功能机制,进行了一组针对性的测试:通过 Steam 客户端向好友发送了一张 GIF 动图。

发送动作完成后,回到云存档管理器刷新数据,就能看到刚刚上传的的图片已作为新的文件记录出现在列表中:

将其下载后,发现就是刚才发送的图:

相比 AppID 7 那种被严格保护的配置空间,这部分数据的读写权限十分开放,与普通游戏存档一致。这直接验证了前面的猜想:Steam Cloud 在底层的定位不仅是“游戏存档”,它完全可以作为一个标准的云文件存储系统,供 Steam 平台侧的各项内部业务直接复用。
结语
这个项目从去年 9 月断断续续写到现在,中间为理清 Root ID 映射表和 API 逻辑踩了不少坑。文中部分结论基于个人摸索和有限平台的测试,难免疏漏。如果你发现了错误或不同的表现,欢迎交流纠正。
如果本文分析或 SteamCloudFileManager 这个工具对你有帮助,真诚求个 GitHub Star⭐️,感谢!
参考链接
官方文档
- Steamworks - Steam Cloud
- Steamworks - ISteamRemoteStorage
- Steamworks - Steam Cloud Best Practices
- Steamworks - How to upload Android APKs to Steam
- Steam - Steam Remote Storage
SDK 内部结构
- GitHub - ERemoteStorageFileRoot.h (SteamStructs)
- GitHub - ERemoteStorageFileRoot.h (OpenSteamworks)
- GitHub - SteamStructs 初始 commit
社区资源
- GameDeveloper - Quick Guide to Steam Cloud Saves
- GrimTalin - Elena Temple Steam Cloud Saves
- Stack Exchange - Steam Cloud
- XT’s Steam Guide - 云存档
- SteamDB - AppID 7
社区讨论
- Reddit - How to permanently delete Steam Cloud saves
- Steam 社区 - 恳求”加个删除按钮”的帖子
- Reddit - Grim Dawn 云同步讨论
- Reddit - OpenCloudSaves 第三方同步工具
- Steam 社区 - Dwarf Fortress 云存档指南
开源项目
- GitHub - SteamCloudFileManager
- GitHub - ROOT_PATH_MAPPING.md
- GitHub - SteamCloudFileManagerLite
- GitHub - Watt Toolkit
- GitHub - ValvePython/vdf
- GitHub - SteamAppInfo
- GitHub - Steam-Metadata-Editor
引流