Skip to content

About

把 KeySteam 重定向出的 Steam 存档自动同步到 Steam 创意工坊,换机自动补档;带本地面板、进度条、卸载清理

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

4 Commits

Folders and files

Repository files navigation

KeySteamSync

把 KeySteam 云存档转存落到本机的存档目录,自动推到 Steam 创意工坊当中转,换电脑后一键补回来。

依赖 Steam 客户端 + 一个"宿主 appid"(默认 480,Spacewar)。不需要自己的 Steamworks 开发者账号。

只想用、不想看代码? 直接去 Releases 下载 KeySteamSync.exe,双击就能跑——单文件、自带运行时、不需要装 Python。 源码仓库里不放 exe(9 MB 的二进制不适合进 git),成品一律作为 Release 附件发布。


1. 它为什么存在

KeySteam 把游戏的存档重定向到本机:

D:\steam\456\<Steam 账号ID>\<游戏 appid>\

好处是不用动游戏本体,坏处是:

  • 存档只在这台机器上,换机器就没了;
  • 这些游戏大多不支持 Steam 云存档(不是 Steam 发行的);
  • 手动打包备份又容易忘。

这个工具做的事:盯着这个目录,谁变了就把谁的存档推到创意工坊;新机器上游戏一装回来,把存档拉回原处。


2. 整体流程

flowchart TD
    A[启动:托盘 + 看门狗] --> B{steam.exe 在跑?}
    B -- 否 --> B
    B -- 是 --> C[拉起同步器 + 面板 :8765]
    C --> D[每 15s 扫描存档根目录]
    D --> E{目录内容指纹变了?}
    E -- 没变 --> D
    E -- 变了 --> F[等安静:连续 60s 无写入]
    F --> G[推前留快照<br/>硬链接去重 · 留 8 份]
    G --> H{云端条目存在?}
    H -- 否 --> I[新建工坊条目]
    H -- 是 --> J[冲突检测<br/>对比 pushed_digest]
    J -- 别人推过 --> K[标冲突 · 不覆盖]
    J -- 一致 --> L[设置标题 + 上传内容]
    I --> L
    L --> M[提交完成]
    M --> N[落地校验<br/>等 30s · 5s 一次 · sha256 逐文件比对]
    N -- 一致 --> O[记为已同步]
    N -- 不一致 --> P[记 push.log · 退避重试]
    P --> D
Loading

另一条支线(重装游戏):

flowchart LR
    X[扫描到某 appid 本地目录为空/不存在] --> Y{state.json 里有 item?}
    Y -- 有 --> Z[从工坊下载并还原文件树]
    Y -- 无 --> W[跳过]
Loading

还有一条支线(卸载游戏):

flowchart LR
    U[appid 连续 90s 不在任何 appmanifest 里] --> V{有未上传改动?}
    V -- 有 --> V2[先推上去,推不上去就不删]
    V -- 没有 --> W[本地存档移进 Windows 回收站]
    W --> X[工坊条目保留 · 记录保留 → 重装能补回]
Loading

3. 一次自动上传:六个阶段

# 阶段 干什么 耗时 / 参数
1 扫描 遍历存档根下 \<账号ID>\<appid>,对每个目录算内容指纹(文件数 / 总字节 / 最新 mtime 的混合哈希) 每 15s 一次
2 等安静 最后一次写入后再等 60s 没有新改动才动手。边玩边传会传上半截存档 面板 --debounce 60;CLI watch 默认 45
3 留快照 推之前把当前存档在本地做一份硬链接快照(没变的文件不占空间),工坊那份被覆盖后还能回到上一版 留最近 8 份,在 history\<appid>\<时间戳>\
4 冲突检测 把"云端副本的哈希"和"上次推成功后记下的 pushed_digest"比。对不上说明别的机器推过,不覆盖,标冲突等你决定 无基线时返回 unknown,不误判
5 提交上传 更新条目内容 + 把条目标题改成游戏名(中文优先)。先试厂商工具,失败回退原生 ISteamUGC 接口 首次上传要新建条目,最慢
6 落地校验 提交成功 ≠ 传成功。等 Steam 生成订阅副本(最多 30s,5s 一轮),再拿本地和副本逐文件 sha256 比对 一致才写"已同步"

失败处理:第 n 次失败后等 min(30 分钟, cooldown × 2^(n-1)) 再试(cooldown 默认 180s),并把最后一条错误显示在面板和托盘。

同一游戏两次自动推送之间还有 180s 冷却,避免边玩边刷。手动点「推送」不受安静期和冷却限制。


4. 面板(http://127.0.0.1:8765)

只监听回环地址,三个页签:

本地存档

  • 每行:游戏名(可点 → Steam 商店页)、大小、文件数、状态徽章
  • 状态:已同步 / 待推送 · 稳定中 Ns / 推送中 · 当前步骤 / 失败重试 / 已卸载 · 回收倒计时 Ns
  • 顶部同步条说明"现在在干什么":正在上传谁、排队几个、下一个等多久
  • 倒计时每秒本地递减(服务端只发绝对时刻,前端换算)

工坊物品

  • 工坊里所有条目、对应 appid、大小、公开性
  • 可以拉取 / 删除 / 认领成某个游戏的存档

活动记录

  • push.log 的可读版:推送、校验、补档、卸载清理,一条一行

接口(GET):/api/state、/api/workshop、/api/log、/api/task、/api/doctor 接口(POST):/api/push、/api/pull、/api/export、/api/setdir、/api/setroot、/api/clearlog

跨域只接受本面板自己的 Origin,别的站点发起的 POST 一律 403。


5. 命令行

不带参数(双击)= 托盘 + 看门狗 + 面板,最省事的用法。

KeySteamSync.exe                      # 后台启动:托盘 + 看门狗 + 面板
KeySteamSync.exe dashboard            # 面板 + 自动同步(同一进程)
KeySteamSync.exe status               # 看门狗/同步器活没活、端口通不通
KeySteamSync.exe stop                 # 全部停掉
KeySteamSync.exe doctor [--json]      # 链路体检(内核是否加载、存档是否还在被重定向)

存档操作:

KeySteamSync.exe push  <目录> [--game-appid N] [--verify] [--force-new] [--no-snap]
KeySteamSync.exe pull  <目录> [--game-appid N] [--item ID]
KeySteamSync.exe verify [--game-appid N]
KeySteamSync.exe restore                        # 把工坊里有、本地缺的补回来(只在空目录补)
KeySteamSync.exe items [--json]
KeySteamSync.exe export --game-appid N --out 目录 [--zip] [--raw]
KeySteamSync.exe adopt --game-appid N --item ID # 把现成条目认领成某游戏的存档
KeySteamSync.exe delitem --item ID [--yes]      # 删条目(不可恢复)
KeySteamSync.exe retitle --game-appid N         # 把条目标题改成游戏名
KeySteamSync.exe history --game-appid N         # 看本地留过哪些版本
KeySteamSync.exe histrestore --game-appid N [--stamp 快照名]
KeySteamSync.exe names [--refresh] [--titles]   # 刷商店名(有中文用中文)
KeySteamSync.exe watch [--root 目录] [--debounce 60] [--cooldown 180] [--orphan-grace 90]

源码模式把 KeySteamSync.exe 换成 python main.py 即可。

面板参数:

python dashboard.py --watch --port 8765 --debounce 60 --cooldown 180 \
                    --interval 15 --orphan-grace 90
  • --debounce 安静期秒数
  • --interval 扫描间隔
  • --orphan-grace 卸载后确认秒数,0 = 关掉自动清理
  • --exit-on-close 面板关掉就退出(默认后台继续)

6. 数据放在哪

全部在 exe(或 main.py)同目录,拷走整个文件夹就搬家完成:

文件 内容
state.json 游戏 appid → 工坊 item、上次推送的指纹与 pushed_digest、卸载/回收标记
names.json / names_src.json 商店名与语言缓存
push.log 每次推送/校验/补档/清理的结果(面板活动记录读它)
logs/ watchdog.log、dashboard.log、tray.log、watch_state.json、pid 文件
history/<appid>/<时间戳>/ 推前快照(硬链接 + blobs/ + token 清单)

state.json 用 tmp + fsync + msvcrt 文件锁 + .bak 的原子写法,进程被强杀也不会写坏;每次只合并被改的那一条记录。


7. 安装

用打包好的 exe

  1. 从 Releases 下载 KeySteamSync.exe(约 9 MB,单文件)
  2. 拷到任意目录(建议单独一个文件夹,它会写 state.json 和 logs/)
  3. 双击。第一次会注册开机自启(登录时静默起看门狗,看到 steam.exe 就拉同步器)
  4. 面板自动打开:http://127.0.0.1:8765

从源码跑

git clone https://github.com/56rf786/KeySteamSync.git
cd KeySteamSync
python -m pip install -r requirements-dev.txt   # 只有跑测试需要,运行本身零依赖
python main.py                                   # 托盘 + 看门狗 + 面板

需要 Python 3.8+,全程只用标准库(ctypes / http.server / winreg),没有第三方运行时依赖。

前置条件

  • Windows 10/11
  • Steam 客户端(登录你自己的账号)
  • KeySteam 已经装好并且存档真的在被重定向到 D:\steam\456\...
  • 用 KeySteamSync.exe doctor 可以确认后两条

8. 打包

python -m PyInstaller KeySteamSync.spec --noconfirm --clean

产物在 dist/KeySteamSync.exe(onefile,无控制台)。版本号改 version_info.txt(四段)。

runtime.reap_stale_mei() 会在启动时清掉被强杀的实例留下的 %TEMP%\_MEIxxxx 解压目录——onefile 模式正常退出会自己删,被 taskkill /F 掉就会留,实测攒到过 79 个 / 1.6 GB。删除前先 os.rename 探测,正在运行的实例改不动名,所以不会误删活进程。


9. 已知限制

  • 新建条目、等 Steam 落地这两步给不了百分比,面板只能给流动条 + 计时。
  • 冲突检测只能发现"别人推过",不能自动合并;两边都改过要你自己选一边。
  • 依赖创意工坊条目本身可用:条目被删、被设为私有、账号被限制,同步就断。
  • 面板没有鉴权也没有 HTTPS,只绑 127.0.0.1,别往公网暴露。
  • 不处理 Steam Cloud 自己的存档,只处理 KeySteam 重定向过来的目录。

10. 测试

python -m pytest tests -q

61 个用例,覆盖:内容指纹与目录比对、原子写、冲突判定(含空基线不误判)、快照与还原、卸载清理的三道闸(还装着 / 没云端备份 / 有改动没推)与宽限窗口、fmt_span 文案。全部纯磁盘操作,不碰 Steam。


11. 目录结构

main.py           单文件入口,参数分派
ksws.py           核心:扫描 / 推送 / 拉取 / 校验 / 卸载清理 / 全部 CLI 子命令
dashboard.py      HTTP 面板 + 自动同步循环(前端内嵌在同一个文件里)
fstore.py         文件树:指纹、原子拷贝、本地历史版本
runtime.py        源码模式 vs exe 模式的路径差异、_MEI 清理
ks_watchdog.py    跟随 Steam 启停的看门狗
autostart.py      开机自启(注册表 RUN)
procs.py          Toolhelp 快照查进程(不 spawn 子进程)
tray.py           托盘图标(纯 ctypes)
hidewin.py        让子进程不弹黑窗
bootstrap.py      新机器体检 / 写配置
pack.py           打迁移包
tests/            单元测试

12. 发一版(自己维护时用)

# 1. 改版本号(四段,最后一段是构建号)
#    version_info.txt: filevers / prodvers / 字符串版本三处都要一致
# 2. 跑测试
python -m pytest tests -q
# 3. 打包
python -m PyInstaller KeySteamSync.spec --noconfirm --clean
# 4. 留一份回滚副本,再替换
cp KeySteamSync.exe KeySteamSync.exe.bak$(date +%s) 2>/dev/null
cp dist/KeySteamSync.exe KeySteamSync.exe
# 5. 打标签、发布,并把 exe 作为附件上传
git tag -a v1.1.6.3 -m "v1.1.6.3"
git push origin main --tags
#    然后在 GitHub 的 Releases 页面新建一条,附上 dist/KeySteamSync.exe
#    (tag 只记录源码,exe 不进 git,方便别人下载成品)

发完 Release 记得把正文里的 sha256 写上:certutil -hashfile dist\KeySteamSync.exe SHA256,别人下完能自己核对。

升级已经在跑的实例:先 KeySteamSync.exe stop(或任务管理器结束进程树),替换 exe,再双击启动。直接覆盖正在运行的 exe 会失败(文件被占用)。


13. 许可

MIT,见 LICENSE。

用创意工坊当存储是 Valve 允许的用法,但请自己注意:存档等于账号数据,别把别人的存档传到你的条目里,也别把条目设成公开分享他人的进度。

About

把 KeySteam 重定向出的 Steam 存档自动同步到 Steam 创意工坊,换机自动补档;带本地面板、进度条、卸载清理

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages