galgame解包(封包)記錄

起因
前兩天下了個(gè)叫《幼なじみお嬢様とHでヒミツな同棲生活》的黃油,瞥了一眼看到pac文件。下意識(shí)以為用的是戲畫那個(gè)引擎,還在想戲畫不是都亡了,這社和戲畫什么關(guān)系,怎么還能用它的引擎。結(jié)果發(fā)現(xiàn)用的其實(shí)是水晶社(CRYSTALiA)同款引擎(是同一母公司,我下游戲的時(shí)候沒太注意,后面才發(fā)現(xiàn))。以前完全沒摸過這個(gè)引擎,所以就想稍微看看。弄了一下發(fā)現(xiàn)異常簡(jiǎn)單,發(fā)個(gè)專欄來水水()
不是很清楚這個(gè)引擎叫啥,我看https://morkt.github.io/GARbro/supported.html上寫著Unison Shift(就是《あなたに戀する戀愛ルセット》那個(gè)社),于是就叫它unison了,下文若提及unison,介是指此引擎
本人逆的樣本是水晶社的red_cherish以及它的csv.pac(主要是這游戲就在我桌面,弄起來方便),接下來的內(nèi)容介以此為例,不同版本可能存在細(xì)微差異
解包
這個(gè)引擎的解包非常簡(jiǎn)單,瞪眼法看出結(jié)構(gòu)即可
用010Editor打開csv.pac
前4個(gè)字節(jié)一定是文件頭無誤,0x8-0xC應(yīng)該是封包內(nèi)的文件數(shù)。再下面則是一堆意義不明的數(shù)據(jù)。

打開其它封包看一下,會(huì)發(fā)現(xiàn)所有的封包都會(huì)從0x804這個(gè)地方開始顯示字符串

說明從這個(gè)地方開始就會(huì)是文件的目錄(至于前面那部分初步推測(cè)是什么校驗(yàn),先不管它),兩個(gè)字符串開頭固定間距為0x28字節(jié)。從0x20開始還會(huì)有一些數(shù)據(jù),基本可以猜到目錄的一項(xiàng)就是0x28字節(jié),前0x20字節(jié)就是文件名的部分。但是后面兩個(gè)4字節(jié)的部分作用還不明朗,而且一般來說作為目錄信息好像有點(diǎn)少。
繼續(xù)往下看,可以看到大量明文,說明這個(gè)封包的數(shù)據(jù)應(yīng)該是沒有壓縮和加密的

那上方目錄部分,剩余兩個(gè)字節(jié)基本可以確定一個(gè)是數(shù)據(jù)偏移,一個(gè)是數(shù)據(jù)大小。再稍微觀察以下,發(fā)現(xiàn)前一個(gè)4字節(jié)有時(shí)候會(huì)特別小,很多時(shí)候會(huì)在目錄前面。那么它大概率是數(shù)據(jù)的尺寸。后面一個(gè)4字節(jié)則是數(shù)據(jù)偏移
現(xiàn)在可以推斷游戲目錄的結(jié)構(gòu)如下
char fileName[0x20];
int dataSize;
int dataOffset;
照這個(gè)結(jié)構(gòu)寫個(gè)解包測(cè)試一下,成功提取
封包
解包完成后,反向?qū)憘€(gè)封包出來。0x804前面未知的那部分直接填0。寫出來發(fā)現(xiàn),成品對(duì)比原封包,在EOF標(biāo)志少了4字節(jié)

基本可以判斷這個(gè)地方應(yīng)該是個(gè)類似crc32之類的校驗(yàn),有沒有都沒關(guān)系。先不要管它

測(cè)試一下發(fā)現(xiàn)替換原封包后,游戲沒有顯示按鈕。說明我們的封包是有問題的。
嘗試輕微改動(dòng)一下原封包0x804以前的部分,發(fā)現(xiàn)按鈕正常顯示,但如果全部替換為0,則同樣無法顯示按鈕。說明前面這部分涉及到封包內(nèi)文件的讀取(也可以嘗試改動(dòng)一下EOF標(biāo)志前的4字節(jié),會(huì)發(fā)現(xiàn)沒影響)
到這里,我已經(jīng)沒有什么能用瞪眼法觀察出來的信息了(當(dāng)然如果你是大佬,或許上面那部分也可以直接看出來),需要上x64dbg了。
用x64dbg打開游戲,createFileA下個(gè)斷點(diǎn),很容易就能找到一個(gè)游戲經(jīng)常光顧的函數(shù),在pal.dll里

也可以用ida打開pal.dll看看

發(fā)現(xiàn)游戲會(huì)先嘗試以文件夾的方式create文件,失敗后再從封包找。不用做任何處理,游戲就可以免封包,非常方便。知道這點(diǎn)的話,也沒必要研究什么封包了。不過我的目的不是漢化,就是想看看這個(gè)封包具體長(zhǎng)什么樣,所以還是要繼續(xù)看下去。
這個(gè)函數(shù)是游戲用來獲取需要的文件的。最關(guān)鍵的一個(gè)參數(shù)是需要文件的文件名,因?yàn)橛螒驎?huì)取這個(gè)文件的首字母<<3作為一個(gè)偏移,去讀取封包0xC-0x804這個(gè)部分,然后找到這個(gè)偏移的前一個(gè)4字節(jié)再作為一個(gè)偏移跳轉(zhuǎn)到封包的目錄中。
稍微觀察一下,會(huì)發(fā)現(xiàn)0x804-0xC=0x7F8(十進(jìn)制:2040),0x7F8/0x8=0xFF。一個(gè)char的范圍,0x8代表其中每一項(xiàng)是8字節(jié)。而且游戲通過首字母偏移后,會(huì)讀取它的前一個(gè)4字節(jié)跳到封包目錄中某一項(xiàng)的fileName的位置
所以0xC-0x804這部分是用來做一個(gè)索引的,把這部分填0的話游戲會(huì)索引不到文件。替換封包之后游戲標(biāo)題界面的按鈕也就無法顯示。
游戲的行為就是:獲取文件名首字母->跳轉(zhuǎn)到索引區(qū)->讀取其前一個(gè)4字節(jié),跳轉(zhuǎn)到封包目錄
接下來只要找出每項(xiàng)兩個(gè)4字節(jié)的關(guān)系即可,最簡(jiǎn)單的方式就是跟斷點(diǎn),以csv.pac中的SOUND.CSV為例,V=0x53,(0x53<<3)+0xC=0x2A4

這是索引區(qū)中的某一項(xiàng),游戲讀取時(shí)還會(huì)在0x2A4上+0x4,讀取0x2A8這個(gè)位置上的值,然后判斷這個(gè)值是否為0,為0則會(huì)退出函數(shù)。這個(gè)值是什么意思?看下從這個(gè)封包導(dǎo)出的文件,以S開頭的文件有4個(gè),這個(gè)值的意義也不言而喻。不然可以跟一下,會(huì)發(fā)現(xiàn)函數(shù)后面會(huì)通過這個(gè)值來決定循環(huán)次數(shù)
現(xiàn)在就知道了索引區(qū)中的一項(xiàng)后4字節(jié)代表了封包中以某個(gè)字符開頭的文件數(shù)。前4字節(jié)的含義仍然未知,不過繼續(xù)看看索引區(qū)后面的部分,每項(xiàng)前4個(gè)字節(jié)的最大值為0x12??梢圆碌竭@個(gè)值大概是什么意思。
x64dbg跟一下,可以推倒出游戲跳轉(zhuǎn)的大概計(jì)算方式:(0x8*0x28)+0x804=0x944??吹轿募倪@個(gè)部分。

發(fā)現(xiàn)該項(xiàng)包括后面3個(gè)項(xiàng)都是以S開頭的文件,那么索引區(qū)每一項(xiàng)的前4字節(jié)代表以某字符開頭的文件在目錄中的最小索引。后面只要一個(gè)個(gè)對(duì)比就能從封包找出需要的文件
即索引區(qū)每一項(xiàng)結(jié)構(gòu)為
int??firstIndexInUnsionEntity; //封包目錄中以該字母開頭的文件的最小index
int?InitialsUsageCount;?//該字符作為文件首字符的計(jì)數(shù)
不過游戲計(jì)算目錄位置的方式其實(shí)要稍微有所優(yōu)化

如果不放心可以用其它文件的首字母驗(yàn)證一下
總結(jié)出結(jié)構(gòu)后修改一下封包程序,再次嘗試

成功顯示按鈕,同時(shí)說明EOF標(biāo)志的前4個(gè)字節(jié)對(duì)游戲讀取文件并無明顯影響,應(yīng)該就是個(gè)crc32之類的東西。
到這里整個(gè)引擎的封包結(jié)構(gòu)已基本明了,不過后面拿著數(shù)據(jù)算了算,發(fā)現(xiàn)這個(gè)4字節(jié)值應(yīng)該不是crc32。所以去跟了下游戲的FileChecker。
發(fā)現(xiàn)這個(gè)值的確是個(gè)校驗(yàn),只不過方式比較抽象。FileChecker會(huì)計(jì)算封包除最后8字節(jié)外的所有字節(jié)的和(就是除了這個(gè)值和EOF標(biāo)志),保存在該位置。所以這個(gè)值的確是個(gè)checkSum沒錯(cuò)。不過這個(gè)驗(yàn)證方式導(dǎo)致FileChecker在驗(yàn)證一些較大封包時(shí)效率感人。
到這里整個(gè)封包的結(jié)構(gòu)完全明了,大致如下

看設(shè)計(jì),這引擎應(yīng)該是個(gè)老古董了,不免封包的話,讀取文件的效率會(huì)很低。
后面我又找了下文本文件,發(fā)現(xiàn)也是大片明文,應(yīng)該也不需要花什么功夫。不過我暫時(shí)應(yīng)該是沒什么興趣了。