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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

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

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


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




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


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

因此,SPK的文件結(jié)構(gòu)是這樣的~


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


using namespace Koishi; using namespace KoishiTitle; using namespace KoishiExpand; class SPKblock; typedef std::vector<SPKblock> SPKlist; class SPKblock{ public:unsigned long bzlib; //第一個(gè)雙字,若采用BZ壓縮則為1unsigned long len; //第二個(gè)雙字,數(shù)據(jù)塊長(zhǎng)度,不包含數(shù)據(jù)塊頭的48字節(jié)unsigned long bzlib_r; //第三個(gè)雙字,第一雙字反碼unsigned long len_r; //第四個(gè)雙字,第二雙字反碼unsigned char hash[32]; //哈希32字節(jié),再往后就是真實(shí)數(shù)據(jù)了unsigned long startPos; //在原SPK文件的起始位置unsigned long mainStartPos; //真實(shí)數(shù)據(jù)在原SPK文件的起始位置,配合len可以取出完整數(shù)據(jù) }; class SPKobject{ public://頭部unsigned long magic; //應(yīng)該是標(biāo)識(shí),均為0x1B111unsigned char nm[260]; //文件名unsigned long reserve1; //所有SPK文件內(nèi)此雙字均為0xC8unsigned long decompressed_len; //壓縮后文件大小unsigned char hash[32]; //哈希32字節(jié)unsigned long maxBlockSize; //最大數(shù)據(jù)塊中真實(shí)數(shù)據(jù)長(zhǎng)度,似乎都是0xE1000(900K)unsigned long indexCount; //數(shù)據(jù)塊數(shù)目std::vector<unsigned long> listLen; //數(shù)據(jù)塊的長(zhǎng)度//之后就是各種數(shù)據(jù)塊了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);}}//提取單個(gè)數(shù)據(jù)塊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; }




總結(jié)

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

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。