日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

关于DNF的多媒体包NPK文件的那些事儿(10) - SPK文件

發布時間:2023/12/9 编程问答 48 豆豆
生活随笔 收集整理的這篇文章主要介紹了 关于DNF的多媒体包NPK文件的那些事儿(10) - SPK文件 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
因為EX收到了解析韓服、日服、美服的DNF客戶端補丁的需求,所以對SPK文件進行了一些探索,似乎基本搞定了(除了NX的意圖)于是就寫了這篇文章。
從官網下載的文件都是SPK格式,SPK格式是對客戶端文件進行一種部分壓縮,是基于BZ2壓縮算法生成的。
接到需求后,首先在CSDN上就發現了這篇文章: https://blog.csdn.net/u010275932/article/details/76082249
嘛,雖然說只是做參考,這篇文章上也列出了一些作者的一個示意圖:


當然看的我確實有點一臉ZZ……首先很多地方都不知道,而且分隔符竟然是那么復雜的一個東西……雖然說最后靠著split分離出來,也能達到目的
但是一般來說文件不太可能出現這種花里胡哨的東西,于是就著手深入研究了一下。
首先文件頭問題到不大(唯一奇怪的是長度表很多數據塊的長度都是0xE1030)




然后文件頭后,發現了分隔符01 00 00 00,這種東西看起來不像是文件頭的,因此我猜想這個分隔符不是TAIL,而且01 00 00 00接下來的16字節很有意思:
首先,第三個雙字和第四個雙字分別是第一個雙字和第二個雙字按位取反的結果,第二個字節是CSDN這篇文章中提到以“BZh91AY&SY ”為開頭的數據開始的字節長度,
因此試了下,發現BZ解壓函數返回0(說明這段數據滿足解壓要求是真實數據),因此猜想第二字節是真實數據長度。
至于“BZh91AY&SY??”和這16個字節中間還有32個字節自然而然就聯想到哈希了,只是不知道怎么生成的……
于是猜想01 00 00 00并不是TAIL,而是HEAD……
然后順著長度查找下去,查到了該真實數據的尾部,這里當然是第二個數據塊出現了,結果出現了文章中提到的另一個分隔符,特長,16字節:
00 00 00 00 00 10 0E 00 FF FF FF FF FF EF F1 FF


后面也沒有"BZh91AY&SY"的字樣了,再往后查也是這個,只不過又多了32字節;
但是文章中發現以這個分隔符為開頭總共數據文件也是48字節,因此估計這個分隔符后面的32字節也是一段哈希,再往后就是真實數據了。
兩個數據塊都是48字節的頭帶一段真實數據,就讓我不得不懷疑他們有沒有關系。
突然發現,這個分隔符也是滿足 第三雙字是第一雙字取反,第四雙字是第二雙字取反 ,又發現第二雙字不就是等于文件頭內長度表不就是0XE1030嘛,就是第二字節加上0x30(十進制的48)!
也就是說這16字節和10 00 00 00開頭的唯一不同,就是第一個雙字!之所以這16個字節引人注目,無非是因為這些數據的長度都是0XE1030的緣故,這也解釋了為啥長度表中那么多0XE1030
因為不見"BZh91AY&SY",于是大膽猜想, 00 00 00 00開頭的數據塊不采用BZ壓縮……
最后,果不其然~針對每個數據塊進行或不進行BZ壓縮最后組成的數據,正是該SPK所對應的NPK文件!
這樣文件結構差不多就摸清了~就差不同就是哈希值不知道咋算的了~
這個靠自己猜想~很容易推斷出,SPK頭部的哈希值是生成后文件的SHA256值,每個數據塊頭部的哈希值是每個數據塊真實數據的SHA256值~
于是就把數據段截取出來用SHA256計算器一算,果然匹配~got IT!

因此,SPK的文件結構是這樣的~


附一下尚未整理到庫里的源碼~(C++ WIN32控制臺)


using namespace Koishi; using namespace KoishiTitle; using namespace KoishiExpand; class SPKblock; typedef std::vector<SPKblock> SPKlist; class SPKblock{ public:unsigned long bzlib; //第一個雙字,若采用BZ壓縮則為1unsigned long len; //第二個雙字,數據塊長度,不包含數據塊頭的48字節unsigned long bzlib_r; //第三個雙字,第一雙字反碼unsigned long len_r; //第四個雙字,第二雙字反碼unsigned char hash[32]; //哈希32字節,再往后就是真實數據了unsigned long startPos; //在原SPK文件的起始位置unsigned long mainStartPos; //真實數據在原SPK文件的起始位置,配合len可以取出完整數據 }; class SPKobject{ public://頭部unsigned long magic; //應該是標識,均為0x1B111unsigned char nm[260]; //文件名unsigned long reserve1; //所有SPK文件內此雙字均為0xC8unsigned long decompressed_len; //壓縮后文件大小unsigned char hash[32]; //哈希32字節unsigned long maxBlockSize; //最大數據塊中真實數據長度,似乎都是0xE1000(900K)unsigned long indexCount; //數據塊數目std::vector<unsigned long> listLen; //數據塊的長度//之后就是各種數據塊了SPKlist list;stream data;void load(str fileName){int i,j;unsigned long dw;data.loadFile(fileName);data.read(magic);for(i=0;i<260;i++){data.read(nm[i]);}data.read(reserve1);data.read(decompressed_len);for(i=0;i<32;i++){data.read(hash[i]);}data.read(maxBlockSize);data.read(indexCount);listLen.clear();for(i=0;i<indexCount;i++){data.read(dw);listLen.push_back(dw);}for(i=0;i<indexCount;i++){SPKblock sb;sb.startPos = data.ptPos();data.read(sb.bzlib);data.read(sb.len);data.read(sb.bzlib_r);data.read(sb.len_r);for(j=0;j<32;j++){data.read(sb.hash[j]);}sb.mainStartPos = data.ptPos();data.ptMove(sb.len);list.push_back(sb);}}//提取單個數據塊void extract(int pos, stream &s){data.ptMoveTo(list[pos].mainStartPos);data.readStream(s, list[pos].len);}int extractTrueStream(stream &s){s.allocate(decompressed_len*2);s.clear();stream s1,s2;for(int i = 0;i<indexCount;i++){extract(i, s1);if(list[i].bzlib){if(s1.BZdecompress(s2)<0){return 0;}s.pushStream(s2,s2.getLen());}else{s.pushStream(s1,s1.getLen());}}return 1;} }; int _tmain(int argc, _TCHAR* argv[]) {SPKobject so;//so.load("sprite_interface2_awakening2.NPK.spk");so.load("sprite_interface.NPK.spk");stream s;int isOK = so.extractTrueStream(s);s.makeFile("sprite_interface.NPK");return 0; }




總結

以上是生活随笔為你收集整理的关于DNF的多媒体包NPK文件的那些事儿(10) - SPK文件的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。