Solivault 的歌里藏了什么:一次 SSTV 解码实录
先给结论,细节在后面:
- Solivault(第九章 ARG 那个倒计时页)在倒计时临近时换了壳,开始播放一段 111 秒的歌,音频是加密下发的。
- 歌里按节拍藏着一条 SSTV(慢扫描电视)传输:每 0.43 秒一个 1200Hz 同步脉冲。
- 解码出来是一张 320×256 的角色立绘(黑发、黄绿瞳、白衣黑颈环),大概就是第九章的新曲绘/新角色。
- 倒放音频里没有第二张图,正放这张就是全部。
配套的完整解谜复盘在另一篇:Phigros 第九章《穹顶孤舟》网页解谜复盘。这篇只讲「声音」这一段。
一、页面变了:先问状态,再决定放不放歌
页面本体瘦成了几十行的引导脚本:
实测 state.php 返回:
{"sound":true,"open":true,"sound_enabled":true,"open_at":0,"close_at":1790931600,"now":1790412010}close_at 换算是 2026-10-02 17<00>00>,和原来的倒计时一秒不差。也就是说这首歌是倒计时前的预热,到点后应该还会再变一次。
二、音频是加密下发的
key.php?n=1返回一组会话材料:sid、spki(P-256 公钥)、nonce、tk;play.php?id=1、?id=2、?id=3分别返回{id, o, f, t}—— 看结构是被切成三段的密文(t很长,f是 32 字节的认证材料);key.php?n=2&sid=…&tk=…返回{iv, ct, p:[0,21,11,58,"…"]}—— 典型的 AES-GCM 参数加一段分片定位表。
裸 curl 这几个接口一律吃 773 访问被拒绝(要浏览器指纹+会话),但音频的解密是在浏览器里做的,所以只要让浏览器跑完,就能在音频进播放器之前把它截下来。
三、怎么把音频拿到手
用 Playwright 打开页面,并在页面初始化时 hook 掉 AudioContext.prototype.decodeAudioData——这是音频解密完、即将播放的那一瞬间:
// audio_probe.js 的核心await page.addInitScript(() => { const AC = window.AudioContext || window.webkitAudioContext; const orig = AC.prototype.decodeAudioData; AC.prototype.decodeAudioData = function (buf, ...rest) { const u8 = new Uint8Array(buf.slice(0)); // 解密后的原始音频字节 window.__cap.decode.push(u8); return orig.apply(this, [buf, ...rest]); };});
await browser.launch({ args: ['--autoplay-policy=no-user-gesture-required'] }); // 免交互自动播放await page.goto('https://wiki.pigeon-games.com/WERJETZTALLEINISTWIRDESLANGEBLEIBEN/');顺手把网络请求也全记了一遍(state.php / key.php / play.php 的响应体都落盘了)。拿到的音频头是 RIFF….WAVE:
| 项目 | 值 |
|---|---|
| 容器 | WAV(PCM) |
| 声道 | 1(单声道) |
| 采样率 | 44100 Hz |
| 位深 | 16 bit |
| 时长 | 111.356 秒 |
| 大小 | 9,821,608 字节 |
正放(160kbps mp3,便于在线听):
四、先把「是什么声音」量一遍
- BPM ≈ 139.7:能量包络自相关的最强峰(第二、第三候选是 69.8 与 105.5,都是它的分数倍/接近值),说明这是一段有稳定节拍的电子曲。
- 频谱质心 2.6
3.5kHz,频谱平坦度 0.120.21:谐波性明显,不是白噪声,也不是纯语音。 - 几乎无静音(非静音占比 99.2%),整段是连续的音乐。
- 每 2 秒的主导频率在 1.4k~3.1kHz 之间跳,且出现 1397.5 / 1565.5 / 2092.5 Hz 这类稳定的离散值(F6、G6、C7 附近),像合成器的高把位。
顺手过了遍语音识别(whisper base):它在 60 秒和 90 秒处听到「Sper, sper, sper…」(罗马尼亚语「我希望」)。但模型给的 no_speech_prob 是 0.83~0.91 —— 这种整齐重复的词,基本可以判定是被节拍切片带出来的幻觉,先不当线索用。
五、为什么怀疑是 SSTV
SSTV 的硬特征是「每行开头一个 1200Hz 同步脉冲」。于是我把音频按 4096 点做短时 FFT,分别取三个频带看周期性:
| 频带 | 周期性峰值 | 周期 |
|---|---|---|
| 1150–1250 Hz(同步脉冲) | 0.80 | 0.430 s |
| 1050–1350 Hz(VIS 头) | 0.73 | 0.430 s |
| 1500–2300 Hz(图像数据) | 0.90 | 0.139 s |
| 40–300 Hz(底鼓) | 0.60 | 0.139 s |
1200Hz 带的尖峰出现在 1.06 / 1.49 / 2.55 / 2.98 / 3.41 / 4.69 / 5.55 / 6.41 / 6.84 / 7.27 / 8.97 / 9.40 秒 —— 彼此间隔全是 0.43 秒的整数倍。
而 0.43 秒正好是这首歌一个节拍的长度(60 ÷ 139.7 = 0.4295),也正好是 Scottie S1 的行时长(428ms)。也就是说:有人把 SSTV 的行同步卡在节拍上,让整条图像传输听起来像鼓点。
六、解码
用 sstv(Rust 实现的 Python 包)直接解:
import sstv
for i, img in enumerate(sstv.decode_from_wav("solivault_song.wav")): img.save(f"sstv_img_{i}.png") print(i, img.size) # 0 (320, 256)结果:1 张图,320×256 —— 正是 Robot 36 模式的标准尺寸。

上图:SSTV 解码结果。横向的噪纹是 SSTV 带宽窄、逐行扫描带来的固有失真,不是原图瑕疵。
图上是一位黑发、黄绿色眼睛、白色裙装、黑色颈环、头上有白色蝴蝶结/头纱的角色。
倒计时归零、End 页那句 slogan 在游戏内解锁之后可以确认:这段旋律对应的是 くるぶっこちゃん 的《Message》 —— 也就是第九章这一轮更新里的新曲,上面这张立绘就是它的曲绘。
也就是说页面那段”会响的歌”并不是随手找的 BGM,而是先一步放出了即将解锁的新曲。
七、顺手核对一下:key.php 里的封面图
key.php?n=1 的响应里还有一个 au 字段(https://c9.gaoice.run/Solivault-<hash>.png)。它的哈希每次会话都不同,直接 curl 会吃 403 —— 但它是页面自己会加载的资源,让浏览器正常打开页面就能拿到(1920×1080)。
把它和第一阶段从 start.png 里拆出来的两张图做像素级比对:
| 对比 | 灰度相关系数 | dHash 汉明距离 |
|---|---|---|
au 封面 vs part1.png | 1.000 | 0/992(完全一致) |
au 封面 vs part2.png | 0.980 | 175/992(82.4%) |
结论:这张”封面”就是第一阶段那张黑洞图本身,只是换了会话签名的文件名又发了一次。所以这次更新里唯一的新图,就是上面的 SSTV 角色图。
八、倒放呢?
第九章的线索里反复出现「Backward」,所以倒放也查了。
倒放(160kbps mp3):
结论:倒放音频里没有第二张 SSTV 图(解码器返回 0 张),正放那张就是全部。倒放频谱也放这儿备查:

上图:倒放音频的频谱(正放那张见文末产物清单)。
九、这次用到的产物都在这里
| 文件 | 说明 | 下载 / 在线听 |
|---|---|---|
phi9-solivault-song.mp3 | 正放,111 秒,160kbps | 在线听 |
phi9-solivault-song-reversed.mp3 | 倒放版,同上 | 在线听 |
phi9-sstv-robot36.png | SSTV 解出的 320×256 图 | 见正文第六节 |
phi9-spec-forward.jpg | 正放频谱图(2048×1024,log 频率轴) | 见下节插图 |
phi9-spec-reversed.jpg | 倒放频谱图 | 见上节 |
solivault_song.wav | 原始解密音频(9.8MB,44.1kHz/16bit/单声道) | — |
正放频谱(黄色亮带就是歌曲的能量分布;SSTV 那条传输就藏在 1.5~2.3kHz 的细纹里):

十、下一步看什么
state.php 的 close_at 指向 2026-10-02 17<00>00>。
复查时发现页面已经改回原版:主页恢复成 start.png 两图拆分 + 倒计时的老代码(实测又能切出 583504 字节的第二张图),state.php 与 key.php 接口已撤(404 / 错误页),song.html 现在返回的是 wiki 回退首页。也就是说本文抓取的音频与 SSTV 图是限时窗口期的内容,文内的试听与图片都来自那份存档。
倒计时归零后,主页面没有立刻换页:主页面和 nosong.html 的 HTML 一字未改,反倒是终点页在 17<01>01>~17<02>02> 之间换了一版——撤掉源码混淆、版本号从 2.2.0 跳到 2.4.0,并放出一段只在浏览器控制台里打印的制作人员名单。
播歌用的那套加密接口(state.php / key.php / play.php)没有回来,所以本文的音频仍是那段窗口期唯一的存档。
归零前后完整的实测时间窗与那封控制台致谢,写在收官那篇里。
① 需要国内 IP + 真浏览器;② 页面要允许自动播放(--autoplay-policy=no-user-gesture-required);③ 音频只在 decodeAudioData 那一刻是明文,hook 它;④ SSTV 用 sstv.decode_from_wav(),一行搞定。
来源:页面本身(state.php / song.html / key.php / play.php 的实测)与我们的脚本产出(audio_probe.js、audio_probe2.js、SSTV 解码脚本)。第九章 ARG 的整体脉络与致谢见另一篇复盘。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!



