lgou2w / HoYo.Gacha

✨ 一个非官方的工具,用于管理和分析你的 miHoYo 抽卡记录。(原神 | 崩坏:星穹铁道 | 绝区零)An unofficial tool for managing and analyzing your miHoYo gacha records. (Genshin Impact | Honkai: Star Rail | Zenless Zone Zero)
Apache License 2.0
294 stars 14 forks source link

将获取抽卡记录独立成库 #54

Closed Aalivexy closed 2 months ago

Aalivexy commented 2 months ago

是否有计划将获取抽卡记录的功能独立成单独的crate?这样不仅可以减少和tauri的耦合,也能方便napi/pyo3绑定给其他程序使用

花了两天做了个原型: https://github.com/Aalivexy/hoyogacha

目前API设计还有待确定(随便糊了糊 错误处理),但已经能跑了,支持原神/崩铁/绝区零的国服/国际服(崩铁/绝区零国服经过测试),支持导出 UIGF v4.0

PS:使用的是简单的regex匹配data_2, 因为我快写完原型才看到这个仓库 PPS:还没遇到data_2最后一个链接过期的问题,可能是因为我多点了两下下一页?看代码还是没太搞懂......(因为regex匹配实在太简单了)

lgou2w commented 2 months ago

新版本 v1 会有计划将一些功能单独作为 lib 形式。(最近特别忙,v1 的开发进度已经滞后很长一段时间了

关于附录PS的疑问我有空会在这个 issue 详细解释。

lgou2w commented 2 months ago

Whenever there are not enough free blocks on a file to store more data, the file is grown by 1024 blocks until the maximum number of blocks is reached. At that moment, a new block-file of the same type is created, and the two files are linked together using the next_file member of the header. The type of the block-file is simply the size of the blocks that the file stores, so all files that store blocks of the same size are linked together. Keep in mind that even if there are multiple block-files chained together, the cache address points directly to the file that stores a given record. The chain is only used when looking for space to allocate a new record.^1

A block file is a file designed to store blocks of data of a given size. It is able to store data that spans from one to four consecutive "blocks", and it grows as needed to store up to approximately 65000 blocks. It has a fixed size header used for book keeping such as tracking free of blocks on the file. For example, a block-file for 1KB blocks will grow from 8KB when totally empty to about 64MB when completely full. At that point, data blocks of 1KB will be stored on a second block file that will store the next set of 65000 blocks. The first file contains the number of the second file, and the second file contains the number of a third file, created when the second file reaches its limit. It is important to remember that no matter how long the chain of files is, any given block can be located directly by its address, which contains the file number and starting block inside the file.^2

const int kBlockHeaderSize = 8192;  // Two pages: almost 64k entries
const int kMaxBlocks = (kBlockHeaderSize - 80) * 8;

简单点说就是每个数据块文件都有最大块数量限制。达到容量限制就会创建一个新的,变成数据块文件链。 但是这个条件挺苛刻的,再加上游戏版本更新的同时也会更新内置浏览器的版本号。 所以其实只读取 data_2 文件也确实足够了。

还没遇到data_2最后一个链接过期的问题,可能是因为我多点了两下下一页?

首先抽卡链接的默认有效期为 1 天。其次是这个数据块文件的数据量太少了。 不仅没有达到一个最大块数量限制。也没有触发 LRU 缓存驱逐策略删除旧的数据。 所以基本上新的抽卡链接数据总是会插入到文件的末尾(在非苛刻条件时)。

lgou2w commented 2 months ago

HoYo.Gacha 的方式就是实现了 Chromium Disk Cache 文件读取器。 不仅可以读取到所有历史的链接数据,还有数据结构的额外信息。 例如 链接的创建日期,这样就可以在不用发出 HTTP 请求的情况下得知这个抽卡链接是否已过期。 也可以在多个账号的情况下各拥有有效的抽卡链接时不需要再重新登录到另外一个账号重新生成新的抽卡链接。 因为可以读取到所有历史的链接数据。

https://github.com/lgou2w/HoYo.Gacha/blob/f1c51a3ccabc00cbe8e67ade0bc02f42bbe9847b/src-tauri/src/business/gacha_url.rs#L194-L282

Aalivexy commented 2 months ago

我明白了,所以这也是为什么我遇到的所有 data_2 文件都是 1,056,768 字节的原因,因为里面全是 NULL 根本没填满,不会出现缓存存到下一个块和触发 LRU...... 这导致实际上除非每天不停在游戏内查抽卡记录,实际上很难遇到将 data_2 填满的情况(而且它还会自动扩容?)

我原本以为实现 Cache 读取是为了获取最新的缓存链接,实际上更多的是为了读取不同账号的未过期的抽卡链接?(但想要得知这个链接对应的 UID,应该还是得向 API 发送请求啊)

Aalivexy commented 2 months ago

单纯的 URL 似乎并不带有明确的游戏类型和对应玩家账号 UID 的信息,我现在倾向于再自定义一个结构来将这些东西存到一起,否则从 URL 获取对应的抽卡记录依旧得需要提供 URL 对应的游戏类型,否则没办法得到 gacha_type/real_gacha_type

至于多个有效的链接,目前看来其实没什么用处,读取缓存肯定是想要最新的链接,但是最新的链接是针对不同的 UID 的,也许想要不从 API 获取链接对应的 UID 只能想办法从 authkey 里获取 UID 了?

Aalivexy commented 2 months ago

单纯的 URL 似乎并不带有明确的游戏类型和对应玩家账号 UID 的信息

指正,game_biz 参数带有游戏类型信息(

lgou2w commented 2 months ago

所以这也是为什么我遇到的所有 data_2 文件都是 1,056,768 字节的原因

是的。块文件 data_2 的初始化大小为 8192 字节的头 + 1024 个块。 这个文件的每个块所占的字节大小是 Block 1K,也就是 8192 + 1024 * 1024 = 1,056,768 字节

而且它还会自动扩容?

根据 Chromium 官方文档来看是会进行扩容的。 当这个 data_2 数据块文件即将达到上限 64MB 时。

实际上更多的是为了读取不同账号的未过期的抽卡链接?(但想要得知这个链接对应的 UID,应该还是得向 API 发送请求啊)

不止是可以读取多个抽卡链接。 硬盘缓存也会把 HTTP 响应头和响应内容也会存储起来。 也就是在游戏内翻页抽卡记录,不仅会存储链接,还会把这次请求响应的抽卡记录数据存储。 但是因为是 HTTPS 协议,那么响应内容自然是加密的(当然肯定能破解,不说详细。DMCA 警告

硬盘缓存图例: https://www.chromium.org/developers/design-documents/network-stack/disk-cache/#the-big-picture

也许想要不从 API 获取链接对应的 UID 只能想办法从 authkey 里获取 UID 了?

如果 authkey 被破解,也就说明整个 miHoYo 服务器账户相关的私钥就被破解了。理论不可能的

lgou2w commented 2 months ago

读取到抽卡链接后想要得知这个链接的 UID 所有者,论方便点的话就直接发送请求根据响应获取。 因为 mi 的抽卡数据服务器基本是 100% 高可用和稳定的,如果出现 500 错误那将会是严重的运营事故。

可以参考新版本 v1 的抽卡链接相关的代码逻辑: 当然这还差一层内存缓存,可以把链接创建日期和请求后得知 UID 的链接做键值对映射缓存。 并且你还需要考虑空的抽卡记录情况,因为只有记录不为空时才会有 UID 字段。

https://github.com/lgou2w/HoYo.Gacha/blob/f1c51a3ccabc00cbe8e67ade0bc02f42bbe9847b/src-tauri/src/business/gacha_url.rs#L299-L309

https://github.com/lgou2w/HoYo.Gacha/blob/f1c51a3ccabc00cbe8e67ade0bc02f42bbe9847b/src-tauri/src/business/gacha_url.rs#L330-L401

Aalivexy commented 2 months ago

内存缓存什么的似乎不是很有必要,毕竟有效期只有一天,多请求一下似乎也不是什么问题,现在这样就挺好的

感谢解答!

lgou2w commented 2 months ago

如还有其他问题或意见,欢迎讨论 ;)