
*仅用作网络留档和个人梳理记录
本文所有内容均来源于该国外论坛。
https://forums.nrvnqsr.com/showthread.php/9708-Mahoutsukai-no-Yoru-HD-PC-file-format-deciphering
notion版本链接:https://jewel-coal-0c7.notion.site/Steam-hfa-15a91dbca33180f6a20fcd17fc71ae72?pvs=4
b站就不全把代码贴上来了,不然太乱了。
关于hfa
使用该工具https://github.com/LinkOFF7/HunexFileArchiveTool直接进行提取。
HunexFileArchiveTool.exe --extract input.hfa
关于mzp(png)
导入该工具https://github.com/loicfrance/mahoyo_tools中的mzp.py,自行编写提取py
关于hw(ogg)
批处理将hw后缀改为ogg,导入ffmpeg识别输出即可
ren *.hw *.ogg
ffmpeg -i input.ogg output.ogg
关于cbg(raw)
方法同mzp,导入该工具https://github.com/loicfrance/mahoyo_tools中的cbg.py,自行编写提取py
关于ctd(txt)
方法一:使用https://github.com/loicfrance/mahoyo_tools中的lenzu.py,自行编写提取py
方法二:https://gist.github.com/Alyinghood/21bdb81d6f83114604082bd019482ae3编译后hook游戏PE后输出文件。如果抛异常`Size of image not known (was executable updated?)`注释掉相关检测再编译就好了。如果是steam官版可能需要自行脱壳,详见下面记录。
hunex_decompression.exe WoH.exe input.ctd output.ctd_
一切开始前,先确认GARbro彻底报废,无论是hfa还是第一层解压出的mzp,hw,cbg等,GARbro全都打不开。虽然和下面一点关系没有,但是这步真的很重要,别问我为什么。
紧跟一下时事:打开文件夹前,xp3的可能性很大,xp3加密也不是不行,换个封包方式也可以接受。打开后,woc哥们,hfa是啥啊。
拖进16进制编码器里先看看。发现,“太好了,是明文,我们有救了。”文件以HUNEXGGEFA10(HEX48 55 4E 45 58 47 47 45 46 41 31 30)打头,然后是四字节不知道啥东西,后面是文件中封装的文件名及后缀,最后还有4+4,8字节的东西。多看几个文件发现都是相同的格式,那么基本可以确定HUNEXGGEFA10应该是某种magic number。
那后面的是什么,找一个封装内容比较少的文件转换成十进制看一下。发现是小端序封装的文件数量。但是结尾的4+4还不知道是什么。不过既然已经找到了幻数,本身处理这类文件大部分就是依赖现成的工具,先将字符排列组合搜索一下看能不能搜到什么。得知hunex大概就是引擎名,把它带上,附加“file tool extract”等关键词。就找到了处理工具https://github.com/LinkOFF7/HunexFileArchiveTool/tree/main
下载release直接照着usage用就行了。至此第一步就搞定了,最后解决一下前文的4+4问题,看一下提取出来的文件属性,后4位是小端序的封装文件大小,那前四位猜也猜出来了,多半是文件偏移,不然data没地方找了。看下工具源码验证下猜想,以HUNEXGGEFA10作为magic number识别文件,读取FileCount,根据Offset定位到数据位置,size读出数据。顺便我们还知道了幻数全称是HUNEX Global Game Engine File Archive1.0

解决了hfa,但是提取出来的mzp文件也不是常规文件扩展啊,还是打不开。只能通过文件名img猜出大概是图片类型。一样的流程,拖进16进制编码器里看看。思路是一样的,用开头幻数去搜,能找到https://github.com/Hintay/PS-HuneX_Tools/tree/master这个工具。用里面的_extract_mzp.py试一下,结果提取出来一个1kb的bin文件和一个0kb的文件,很明显不对。大概压缩算法不一样。好在也不是完全没有收获,能通过mzp_format.md了解到一些mzp文件的格式。


之后又经过各种搜索法,就找到了开头提到的论坛和工具。https://github.com/loicfrance/mahoyo_tools。不过这只是一个tool库,所以还得自己写提取py,最后还是逃不过这一步。这里就长话短说了,`tile`中封入了图片的data数据,用`get_tile()`获取图片数据,再调用`img_write()`合成图片即可。缺少的一些依赖用 pip install安装下就好了,尝试运行报错ImportError: attempted relative import with no known parent packageimport导入错误,发现源码是相对导入改成绝对导入就好了。

提取成功,但是为什么会有25张一模一样。是一份mzp里有多张图片,代码写错了导致data读错变成了一样的吗?比对一下文件数就能直接排除这种可能,这份hfa有645份mzp文件,那就是645x25 16125张,哥们,这辈子没见过有这么多cg的,这还只是一份hfa。所以确实是有25个tile的index,但它们的内容是一样的。验证一下,16字节作为一个block分割文件数据,循环遍历记个数。ok,初见端倪,到16编码器里搜一下,完整的应该是4D 5A 58 30 20 04 02 00 1B 48 45 50 00 20 04 02 00 00 00 30 25 06 00 12 03 10 00 12 03 00 02 12,32字节搜下25个结果。


最后,让它只读一次tile,再读个文件夹就好啦。还可以,就是读的有点慢,不管了反正自己也不用。

其实到这里,整个游戏的文件封装已经有个基本认识了,hw也是一样。oggs与vorbis很明显能知道这是音频格式。

论坛大佬给的线索是“To unpack the .hw files to a playable audio format, use oggsplit, which will output playable Ogg Vorbis files.”用oggsplit处理就行,首先想到的是去翻官方库https://github.com/xiph/vorbis-tools
一看,我勒个上古代码啊,打开都不想打开。总之先找个在线格式网站试下吧,但是网站识别不了hw文件,这个简单改个后缀不就好了,然后,就成功了。 剩下就是解决文件数的问题了,几千个文件总不可能全传上去。这个也简单,换ffmpeg秒了。最后思路用批处理把hw改成ogg,写py调用ffmpeg识别输出就好了。

吐槽:虽然这部分看起来很轻松,但自己当初执着于oggsplit,以为是什么专门工具,花了很长时间去找。外加对ogg格式不了解,根本没想过直接转化,最后也是万策尽才试一试。好奇原理之后恶补了一下ogg的相关知识。详见CSDN这篇文章https://blog.csdn.net/qq_60914456/article/details/125965469
提取过mzp之后这个就简单多了。也不需要分析什么文件了,工具已经都有了https://github.com/loicfrance/mahoyo_tools。直接照搬mzp文件的思路就好了。就是提取的更慢了,算了,能用就行。

ctd有两种方法,一种就是和前面一样,写py提取。另一种是用hook去dump出来。我两种都试了,就都说一下。
第一种:ctd用的是lenzu.py,因为在编码器里能看到LenZuCompressor的幻数。一样的思路,就是函数传的参不太一样。看下核心代码,要传一个文件,文件大小,none。然后写的时候别直接用它这里src: BytesReader的BytesReader()函数,会抛一个异常,Protocols cannot be instantiated,换成 BytesIO就好了。去翻这个函数定义的地方,头文件import跳过去。然后发现utils文件夹下的io.py命名和io库重命名,不过这不是核心问题,但是会有红线,强迫症还是改一下,别忘了改别的文件的。

第二种:比较复杂。https://gist.github.com/Alyinghood/21bdb81d6f83114604082bd019482ae3 c语言得先编译。同时要用到https://github.com/TsudaKageyu/minhook。手动下了导入,或者用vcpkg都可以,配置下minhook路径。唯一要注意的是minhook的lib和附加lib和编译环境的x64或者x86都要一致。
git clone https://github.com/microsoft/vcpkg.git
cd vcpkg
.\bootstrap-vcpkg.bat
.\vcpkg install minhook
tip1:如果你是steam官版可能得要对游戏PE脱个壳,论坛大佬给出的方法是用pe-sieve。参考命令如下,第二部分要启动游戏进程,或者用od,x64啥的自己脱,通用的什么steam crack应该也可以。第三方流别人已经处理过了就不需要这步了。
Invoke-WebRequest -Uri https://github.com/hasherezade/pe-sieve/releases/download/v0.3.8/pe-sieve64.zip -OutFile pe-sieve64.zip
Expand-Archive pe-sieve64.zip -DestinationPath pe-sieve64 -Force
cd pe-sieve64
$procid = get-process WoH | select -expand id
.\pe-sieve.exe /imp 3 /shellc 3 /pid $procid
explorer process_$procid
tip2:运行如果抛异常Size of image not known (was executable updated?)去源代码里把下面这几行注释掉再编译生成就好了。别问为什么,反正作者是这样说的。
/*
if (pinh->OptionalHeader.SizeOfImage != 0x621db1)
{
printf("Size of image not known (was executable updated?): %x\n", pinh->OptionalHeader.SizeOfImage);
return 1;
}
*/
最后用法,编译生成的PE 游戏脱壳PE ctd文件 输出文件。

最后还剩下一些ccit,chs都是些字体文件,没啥兴趣就不研究了。