CGSS 核心反向过程实录 | 一、摸清目录结构

文章目录
  1. 1. 一、声明
  2. 2. 二、一切的起因
  3. 3. 三、目标分析
  4. 4. 四、文件粗略分析
  5. 5. 五、初步验证猜想

索引

2017年5月追记:

我探索这些的目的,是以一个玩家的身份,让 CGSS 变得更好玩。私下里我类比过普罗米修斯,将天火——谱面的制作和玩的能力从“天上”分一点出来。其实,修改 CGSS 去作弊是很简单的,无论是客户端还是 MITM。但是我不会去作弊,也不希望这系列文章的读者们将文章内容用在歪门邪道上,不希望去作弊、破坏平衡性。玩过 LLSIFSB69GF(note)Arcaea 之后,我仍然认为 CGSS 的综合指标(游戏性、操作体验、曲目等等)是行业内翘楚,Cy这次比较用心。那么作弊还有什么意思嘛!对了,我不氪金,不冲分,只是闲暇时间来两局,玩得舒心。

当然,まゆさまが見ています。

(2月21日)


一、声明

The Idolmaster Cinderella Girls Starlight Stage 软件和相关媒体的著作权为 Bandai Namco Entertainment Inc. 所有。本系列文章仅从兴趣出发,研究数据反向过程。在此不会给出完整的反向过程、反向结果和源代码,只给出思路和部分非关键数据。

我相信大牛即使是没有看过我写的这些玩意儿也能上的。

二、一切的起因

我2月15日时开始玩了CGSS,由于加入的第一天正好是一个送 star jewel 的活动的第一天,所以记得比较清楚。

然而,在音游上,我是个手残,至少初始状态下是个手残。开始的一周,在队伍的life值(可以认为是HP)还不高、没“奶妈”(恢复life)卡等因素的共同作用下,打 Regular 难度,终盘都半死不活。你或许会问,多练习不就好了?的确如此,不过运营商怎么赚钱呢?很简单,限制你的练习次数。游戏中有一个叫做体力槽(スタミナゲージ,stamina gauge)的东西,每玩一首歌(“打歌”)就要消耗一定的体力。这个体力是会慢慢涨回去的,5分钟一点;然而一首歌的消耗随难度不同在11点到20点之间。体力槽上限我现在见过的是81点。所以各位可以计算一下,平均多久才能打一首歌。体力可以氪金,或者通过掉落频率较少的物品来补充。——看到这里,想必之前不知道的同学也理解了收钱的方法了吧,和普通网游是差不多的。

但是!我等是有技术的,而不是萌豚,能用技术轰下的阵地就不会用钱砸。于是问题就很明显了,我们要根据已有的数据模拟出一个练手的东西出来。(不要想去伪造 HTTP 请求,破解是有底线的,不说错误的数据肯定会被检测到,就是刷出个全SSR又有什么意思呢?卖?那就和苟活的人没区别了。)

Q: 有没有会嗤笑这个的成品呢?

A: 当然,这些人会:1、对 CGSS 无感的;2、有时间肝(花大量时间打游戏)的;3、有钱氪(课金,中文叫充值)的;4、手巧的。以上几项我一个都不符合,也没什么“本命”(非常喜欢某个角色)一说,所以,既然是数据,我们就用数据的手段来一决雌雄吧!

(注:在修订此文章的3月16日,经过一个月的练习,我的技术已经达到了能几乎FC17星的曲目、23星存活没问题、24-25星试一次的水平。除去队伍强化(从最初一张R其他N,到现在全队至少是R)的因素,技术确实是提高了。离大触还很远,不过比自己进步了许多。看来这和练钢琴是差不多的。)

三、目标分析

CGSS 的玩法属于音游,大致由这些元素构成:

  1. 歌曲播放;
  2. 跟着节奏点击操作的质量判断;
  3. 评分;
  4. 显示;
  5. 社交(好友、任务);
  6. 扭蛋(抽卡);
  7. 卡片管理;
  8. Communication(类似 gal game 的东西)。

CGSS 有点特殊的地方是有一个 3D Live 的选项,这是通过 Unity 实现的,我们没必要还原这个,按下不表。另外,显示部分可以做得简单一些,因为我们做的是一个模拟程序,而不是对游戏的精确还原。再说了,完全反向所需的精力十分巨大,产出远不如投入(还会被告),根本没必要。除去其他和我们的目的不相关的元素,我们需要实现的有:

  1. 歌曲播放;
  2. 点击判断;
  3. 分数计算;
  4. 综合显示。

有了明确的目标之后,就开始逐个击破。

判断和显示是要自己写的,而文件是要读取的。在玩的时候,已经下载的歌曲是不会再下载的;因此,先解决歌曲文件和 beatmap(谱面)的位置和读取问题。

四、文件粗略分析

首先将 Android 上的数据拷贝一份。数据在哪里呢?熟悉 Android 开发的同学们应该熟悉 Environment.getExternalFilesDir() 这个 API,映射到的是 /mnt/sdcard/Android/data/{app_id}/files,是各种外部数据的存放位置。我是在浏览文件管理器的时候偶然发现的。看总大小(500M,CGSS 的安装包<50M),应该就是外部数据了。

复制之后结构应该是这样的:

F:\TEMP\root>tree
卷 Software 的文件夹 PATH 列表
卷序列号为 XXXX-XXXX
F:.
└─Android
└─data
└─jp.co.bandainamcoent.BNEI0242
├─cache
│ ├─tmp
│ └─UnityShaderCache
└─files
├─a
├─b
├─c
├─l
├─manifest
├─r
├─s
└─v

注意到一个很特殊的目录 manifest,其他的目录名字都是不明所以的单字母,只有这个非常显眼。打开之后可以发现,目录内有且只有一个文件:

F:\TEMP\root\Android\data\jp.co.bandainamcoent.BNEI0242\files>dir manifest
驱动器 F 中的卷是 Software
卷的序列号是 XXXX-XXXX

F:\TEMP\root\Android\data\jp.co.bandainamcoent.BNEI0242\files\manifest 的目录

2016/02/21 08:42 <DIR> .
2016/02/21 08:42 <DIR> ..
2016/02/16 17:17 1,269,760 cceff7e658530cf7c8bf96901ef77d7e85dd436b

这个文件是一个什么东西?试试用 Notepad++ 打开:

Notepad++ 下看到的文件前端可读部分

有点经验的同学应该立刻能反应过来:这是 SQLite 3 的数据库格式!而且,从常识上来说,SQLite 是嵌入式数据库的首选,在移动平台上有着广泛的应用,所以出现在这里是可信的。

于是我们就找一个 SQLite 数据库的浏览工具吧。这里我用的是 SQLiteStudio

翻阅记录,加上一点直觉和对数据的敏感性,能大致判断记录项的意义。然而,最有趣的地方还是在这里:

song_*.acb 有着明显的提示

这说明:

  1. 这个数据库很可能是货真价实的索引(因为仍在假设阶段);
  2. 程序中用的很可能是明文,经过加工后成了现在看到的东西。

五、初步验证猜想

在玩的时候,有些歌曲是第一次运行时就下载了的。在玩的时候注意到这些情况:

  • 歌曲的“开始”变成了“下载”,同时试听也没有了。点击“下载”则需要在“数据下载中界面”等待大概半分钟,然后试听就有了,歌曲也能正常玩了。
  • 在开始一首歌的时候,有时候会有数据下载,但是一般都是十秒内结束。
  • 有少见的情况,有试听,但是开始打歌的时候要等待比较长的时间;不过这都发生在上次歌曲下载没有完成我就强制退出(有时界面会无响应)的时候。
  • 一般的打歌过程中,如果不换卡或者显示模式(3D/2D)是不会出现额外的下载过程的;而且明显这些文件是有缓存的。
  • CGSS 的“通信中”和“数据下载中”是两个不同的状态。

因此可以判断,在点击“下载”的时候,是下载音频文件,而且包含试听和本体。第一次打歌的时候下载数据的包含谱面和一些其他素材。然后从时间上加以印证:贴吧上有人说过设置中切换音频质量,低质量一首大概 2M,高质量一首大概 3.5M,“不要问是怎么知道的”(其实就是因为ta用了流量,然后看到了账单嘛)。地址我没记下来,不过我记得应该是在偶像大师灰姑娘女孩星光舞台吧里的。这个数据很关键,综合 MP3 的一般大小,假设歌曲采用 MP3 CBR,那么低质量码率大概是 96~128 Kbps,高质量大概是 192~224 Kbps,歌曲时间在2~2.5分钟——这和我的耳朵&手机时间告诉我的相吻合。因此也可以判断,不管对方采用什么样的音频编码,其压缩率应该和 MP3 接近。

虽然歌曲的标题不知道,但是可以注意到歌曲的数量。这个数量怎么得到呢?上图中有一个 song_*,数据库中还有一些 name 值为 musicscores_* 的记录。不过由于这个“score”在当前语境中有多种可能意思:曲谱(即使不太可能)、分数、谱面(我一开始假设谱面是分开存放的,这个文件和音乐强相关,有可能是谱面),暂时还不知道设计者采用这个词的时候是想着哪一种。这里又遇到了一个挫折:(2月16日版的)数据库中的这两条记录数量都超过了发行的歌曲数量,剔除了明显重复的之后也是。(现在再想一想,有可能是预告呢,呵呵。)

但是,峰回路转,不在这么明显的地方验证数量,我们换一个地方。经过一番过滤,我发现了一个突破口:

曲目列表(1),歌名已经很明显了

续上,曲目列表(2)

续上,曲目列表(3)

上面的文件,直接出现了歌名!对灰姑娘曲目有点了解的人,应该能一眼发现熟悉的歌曲。例如,3d_cutt_apple 明显是《アップルパイ・プリンセス》(Applepie Princess,十时爱梨角色CD曲),3d_cutt_twilight 明显是《Twilight Sky》(多田李衣菜角色CD曲)。另外,我们还知道,solo 卡(带有“ソロ”标记的卡)是可以打《お願い!シンデレラ》的 solo 版的——所以 3d_cutt_oneshin 还带一个 solo 的“姐妹”。同时文件名还显示,这些文件用于 Unity3D,很可能是显示的资源包。因此可以做出一个合理假设,这些记录和歌曲相关,而且和歌曲版本也有关系(估计是动作?好像某地提到了“cutter”,但是不知道意思),而且看起来一个文件对应一首(分版本的)歌。我查了一下 CGSS 的歌曲列表(2月21日快照),一共54首(《お願い!シンデレラ》不算 solo 版),刚好和相应记录数量吻合。因此可以判断,不管歌曲有没有下载下来,这个数据库存储的都是完整的、最新的资源列表。

有了以上的关键提示,我们能确定要解析的目标的大概质量和体量(文件大小、内容量)了。但是现在遇到一个问题:文件名全都是莫名其妙的随机串,并不是直白的文件名。

看看,数据库中不是有一个 hash 列吗?有没有可能代码中有个函数用的是这个散列值呢?于是我写了一个小程序,进行了散列值-文件名的交叉比对,结果是我猜错了。(当然,BNEI 的程序员应该不会弄一个这么明显的东西出来。)不过为什么找不到呢?我突然发现,二者长度不相等,数据库中的值是128位的(32×4),文件名是160位的(40×4)。熟悉的同学也应该一眼就看出来了——这不就是 MD5 和 SHA-1 吗?但是知道了又能怎么样,这只是信息摘要,还不知道原始信息是什么呢。(读者可以看着这些值试试,很可能在这里就得出了我后来才得出的结论。)那么二者的功能就很明显了——MD5 用来校验文件完整性,SHA-1 来散列文件名。


走到这一步,对 CGSS 的数据目录结构有了个大体的认知,不过还是没有实际的用处。接下来,就应该推进到文件内容了。

分享到 评论