Featured image of post 记录一下崩铁的 Fandom 和 BWiki 图片 URL 前字符串

记录一下崩铁的 Fandom 和 BWiki 图片 URL 前字符串

也算长了些知识

Fandom

我以前在 Honkai: Star Rail Wiki 找角色的素材图时注意到,每张图片的 URL 格式都很相似。 比如姬子跃迁立绘:https://static.wikia.nocookie.net/houkai-star-rail/images/8/8e/Character_Himeko_Splash_Art.png/revision/latest?cb=20230525090036,共同的前缀+1 位字符+2 位字符+文件名+版本控制参数。 所有图的文件名格式都很规范,比如 Character_Himeko_Icon.png 是姬子头像,Character_Himeko_Eidolon_6.png 是姬子 6 星魂。

我猜测 imgaes 和文件名之间的两处字符是为了优化缓存和负载均衡之类的,可能是用某种哈希算法根据文件名生成的,但是用于相关函数说不定在 js 里面。 我可不懂如何从网页前端扒拉 js 用,于是打算把所有角色各素材的 URL 中那两处特殊的字符单独存下来。 存了几个之后发现,1 位字符始终是 2 位字符的第 1 位,于是只存两位字符就行。

前段时间想,不管函数存在哪里,我猜用的还是成熟算法,自己设计算法容易不平衡、不稳定、有漏洞什么的,费力不讨好。 于是我尝试生成一下文件名的 MD5,没想到还真是这个。 比如 Character_Himeko_Splash_Art.png 的 MD5 开头就是 8e

于是我把之前好容易全存下来的数据全删了哈哈哈。

BWiki

后来也想用 星穹铁道WIKI 的素材,一看图片 URL 傻眼了。 比如姬子的半身立绘图:https://patchwiki.biligame.com/images/sr/0/08/3gv4qax2cdksleouchs6vr0y8jgdg6g.pngsr 后面的两处应该还是与 Fandom 类似的做法,但是文件名像加密了似的,而且文件名的算法还不一样。

想了一想,估计这种文件名的作用和前面两处字符也差不多,而且网页里也得有个原来的字符串的蛛丝马迹。 一不做二不休,开 F12 看两眼。 找到了疑似原文件名的参数 角色一览-姬子.png。 试一试算 MD5,开头是 08,好耶!

那文件名会是什么算法呢? 试了试 MD5、SHA-1、SHA-256、SHA-512 都不对,去掉后缀名用 角色一览-姬子 再试一次生成,也不对。 想用英文名试试,但是不知道 角色一览 在站里的英文对应什么,就从角色头像试试,但还都不对。

仔细观察一下,URL 文件名所有字母都有,但是算出来的都是 16 进制的字符串,那我转 36 进制试试。 依然不对。

那会不会是用文件本身生成的呢? 于是用 Dolphin 文件管理器自带的计算文件校验码的功能看了看,SHA-1 转 36 进制的前 10 位对上了!

那后面的部分会是什么呢? 用不同算法拼接的吗? 把之前步骤里的字符串对照一下,没找到共同子串。

要不问问 AI 叭,于是把现在的情况给 Deepsleep 描述了一下。 深度睡眠先生也说可能是拼接的,按它给的方案试了试都不行。 又刷新了几次,提到可能是进制转换时没用大整数导致的。 了解了一下什么是大整数,然后用代码生成了一下,真成了~

其实整个文件名都是用 SHA-1 生成的,只是之前转码用的不是大整数。 把整个字符串看成一个完整的 16 进制整数去转换,而不是按每位字符转换就可以了~(耶

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import hashlib

def sha1_to_base36(filepath: str):
    # 读取文件二进制
    with open(filepath, 'rb') as f:
        data = f.read()
    # 计算 SHA-1 十六进制(40位)
    hex_digest = hashlib.sha1(data).hexdigest()
    # 转换为大整数
    big_num = int(hex_digest, 16)
    # 转换为 36 进制
    chars = '0123456789abcdefghijklmnopqrstuvwxyz'
    if big_num == 0:
        return '0'*31
    result = []
    while big_num > 0:
        big_num, rem = divmod(big_num, 36)
        result.append(chars[rem])
    base36 = ''.join(reversed(result))
    # 前面补0至31位
    return base36.rjust(31, '0')

# 测试
filepath = 'path/to/file'
print(sha1_to_base36(filepath))
本文采用 CC BY-NC-SA 4.0 许可
当你有机会做出选择时,不要让自己后悔
使用 Hugo 构建
主题 StackJimmy 设计