[YU-RIS] Whirlpool社的一些觀察
[YU-RIS引擎] Whirlpool社的一些觀察
對于原版的yuris引擎,只需要解包后對ybn進文本的導出導入,然后把SJIS范圍的檢查表改成GBK的即可
對于yuris來說CreateFont其實是默認依據系統判斷的,一般都可以不用改。
Whirlpool比較奇葩點,他們用的好像是改過的yuris引擎,ybn里不包含游戲的文本部分(這里說的是新版本啦)
他的文本直接就是純文本格式的,放在 sc.ypf 封包里,解包后是scenario文件夾里面放了一堆txt
看著似乎更方便漢化,其實不然
Whirlpool或者說是改過的yuris,其實有兩種顯示文本的方法
第一種
是調用Textout函數,在兼容DC上畫字,然后貼圖,它的Textout還比較奇葩,每次只畫一個字符,貌似是全部畫完后才貼圖。
第二種
是封包里有個字體文件夾,里面其實都是圖片,字符就在圖片上,把圖片上的字一個個切出來顯示到屏幕上,這種其實在Gal引擎上也有一部分是這樣實現的,一般Whirlpool官中就是這樣搞的。
第一種可以說說,第二種其實沒啥好講的,要漢化只能改圖片了,第一種看著就和正常yuris沒啥區別,確實,沒啥區別,但是Whirlpool搞了個很惡心的東西,就是字符的范圍校驗,之前的那個SJIS的表還在,但是Whirlpool額外還在腳本里寫了校驗的范圍。超出范圍的字符表現在游戲里就是直接不顯示了,甚至還會強行停止解析,就是當你的字符串一行里某一個位置開始的字符超出了SJIS的編碼范圍,就會從那個位置直接停止解析后面的字符。所以即使你改了SJIS那個校驗的表,也不能過范圍校驗,因為它后面還會校驗一次,特別的傻唄,而且這種校驗很難修改,因為不是直接寫的匯編,而是通過yuris的虛擬機實現的,所以需要解析op(操作碼),而yuris的腳本是編譯的,就和我們exe里的二進制數據一樣,只不過在win32下這些數據可以依據x86匯編解析,yuris那種要解析就得逆向yuris虛擬機了。
對此,我們有兩種對策
對策一:硬剛虛擬機
就是直接硬剛,yuris的虛擬機,找到對應的ybn文件(腳本),然后改掉。由于我水平低下,剛了一天也沒剛下來,虛擬機這種東西特別的惡心,很難調試。
一開始我對字符串進行了跟蹤,對所有讀取目標字符串的的操作都進行了跟蹤,但是跟到后面就不知道跑哪去了,這玩意會對字符串進行多次的復制和取字符串取單字節到處復制,非常傻唄,反正這個方法沒搞定。
嘗試摸索范圍,由于超過范圍后字符在游戲里會直接不顯示并停止解析下一個字符,我嘗試摸索出了范圍,和SJIS的范圍是一致的,也就是 0x81-0x9F,0xE0-0xFC 既然有這個范圍那么典型的來說,必然有cmp xx,0x81這種東西,而且0x81,0x9F,0xE0, 0xFC如果沒有各種位移異或應該就是直接躺在腳本里的某個位置的。了解了這個情況,開在腳本里摸索。一般的這四個值肯定不可能挨在一起,中間應該會隔有操作碼,ybn文件也太多了,搞不懂從哪個開始看,其實跟蹤虛擬機也是可以定位到相應腳本的,不過太麻煩了,于是想了個辦法。
首先正確提取并解密全部ybn,然后全部合并成一個文件,可以直接用bat批處理,挺快的
copy /b "%~dp0"\*.ybn "%~dp0"\new.ybn
然后搜索一下邊界那些常量,下面這段代碼是有問題的,會越界,反正就隨便搜一下,亂寫了。
//原理很簡單,先一個個字節搜索0x81,找到后從這個位置搜0x9F,找到后搜0xE0以此類推
//searchSize就是搜到0x81往下找0x9F的長度,因為0x81-0x9F這兩個之間的距離我們是不確定的
//searchSize盡量在20以內,大了沒意義,而且搜索結果很多。
void FindRange()
{
std::ofstream oFile("1.txt");
std::ifstream iFile("new.ybn", std::ios::binary);
size_t szFile = GetFileSize(iFile);
unsigned char* pBuffer = new unsigned char[szFile];
iFile.read((char*)pBuffer, szFile);
size_t searchSize = 20;
for (size_t i = 0; i < szFile; i++)
{
if (pBuffer[i] == 0x81)
{
for (size_t j = 0; j < searchSize; j++)
{
if (pBuffer[i + j] == 0x9F)
{
for (size_t x = 0; x < searchSize; x++)
{
if (pBuffer[i + j + x] == 0xE0)
{
for (size_t y = 0; y < searchSize; y++)
{
if (pBuffer[i + j + x + y] == 0xFC)
{
oFile << std::hex << (i + j + x + y) << std::endl;
i = i + j + x + y;
}
}
}
}
}
}
}
}
oFile.flush();
}
用改代碼對 pieces/渡り鳥のソムニウム 的ybn進行搜索,可以找到yst00054.ybn在file offset:0x30D0處,是符合的,對二進制數,進行一定的斷行,就真像是那么回事,不過由于沒有對yuris虛擬機進行仔細的逆向分析,這里面的操作碼都看不懂,只有0x81 0x9F這些是實實在在的。
unsigned AnsiChar data[56] = {
0xFC, 0x0A, 0x57, 0x02, 0x00,
0x81, 0x00, 0x5A, 0x00, 0x00, 0x48, 0x03, 0x00, 0x40, 0xFC, 0x0A,
0x57, 0x02, 0x00,
0x9F, 0x00, 0x53, 0x00, 0x00, 0x26, 0x00, 0x00, 0x48, 0x03, 0x00, 0x40, 0xFC, 0x0A,
0x57, 0x02, 0x00,
0xE0, 0x00, 0x5A, 0x00, 0x00, 0x48, 0x03, 0x00, 0x40, 0xFC, 0x0A,
0x57, 0x02, 0x00,
0xFC, 0x00,
0x53, 0x00, 0x00, 0x26
};
不管那么多,先把9F改成0xFE FC改成0xFE看看效果
把exe的SJIS校驗表改了,然后改一下文件讀取順序,變成可以免封包讀取txt文件,然后修改一下txt文件的內容。更改修改的ybn文件也放到目錄下ysbin文件夾里(ybn記得xor會去)


可以看到漢(0xBABA)這個字出來了,但是后面的字符都沒了,漢這個字的GBK已經是超過SJIS原來的邊界了,不過至于為什么這里也會出問題,你可能會說是判斷了第二個字節,其實不是,或第二個字,其實也不是,這里只有第一個字解除了限制可以出來,只要是GBK里的都行,但是后面第二個字符只有寫SJIS范圍內的才能出來,否則就像圖上那樣。可能還有地方限制了范圍,但是沒找到。
Notes.txt 后面搜到了這個東西,有人取逆了虛擬機和大部分文件結構,照著解析了一下腳本
48030040 FB0A pushscalarvar FB0A
4201005F pushint8 0x5F
3D0000 equal
48030040 FF0A pushscalarvar FF0A
420100 5E pushint8 0x5E
48030040 FB0A pushscalarvar FB0A
420100 2E pushint8 0x2E
3D0000 equal
48030040 FF0A pushscalarvar FF0A
420100 5F pushint8 0x5F
48030040 FC0A pushscalarvar FC0A
570200 8100 pushint16 0x8100
5A0000 ge
48030040 FC0A pushscalarvar FC0A
570200 9F00 pushint16 0x9F00
530000 le
260000 logand
48030040 FC0A pushscalarvar FC0A
570200 E000 pushint16 0xE000
5A0000 ge
48030040 FC0A pushscalarvar FC0A
570200 FC00 pushint16 0xFC00
530000 le
260000 logand
7C0000 logor
48030040 FF0A pushscalarvar FF0A
420100 60 pushint8 0x60
48030040 FB0A pushscalarvar FB0A
420100 22 pushint8 0x22
3D0000 equal
48030040 FF0A pushscalarvar
420100 16 pushint8 0x16
48030040 FB0A pushscalarvar
420100 20 pushint8 0x20
3D0000 equal
沒看出什么名堂,由于最近又一堆考試和課,這里就沒繼續研究下去了,做一個記錄,就此打住。
對策二:映射碼表
之前提到Textout那個函數是一個個字符輸出的,其實可以搞一個映射,原理很簡單,我們在SJIS范圍內重新給漢字編號,然后Hook Textout這個函數,比如傳入這個函數的是 0x8130 我們就查一下自己的表看看這個是不是要映射,比如表里記錄了 0x8130 -> 0xBABA 那么我們就把BABA覆蓋進去就好了。當然還有一種是直接改字體的也行。這個后面有空我會寫一個放上來。
對策三:直接看答案
之前有些大佬逆過了,給出了關鍵點,直接搜索以下特征碼即可。
C1 E0 08 ?? ?? ?? ?? ?? ?? 99 E9
00455B23 | E9 0DF9FFFF | jmp pieces.new.455435
00455B28 | C1E0 08 | shl eax,8
00455B2B | 0FB64A 01 | movzx ecx,byte ptr ds:[edx+1]
00455B2F | 03C1 | add eax,ecx
00455B31 | 99 | cdq
00455B32 | E9 40F9FFFF | jmp pieces.new.455477
改成
00455B23 | E9 0DF9FFFF | jmp pieces_crack1121.new.455435
00455B28 | 66:B8 009F | mov ax,9F00
00455B2C | 90 | nop
00455B2D | 90 | nop
00455B2E | 90 | nop
00455B2F | 90 | nop
00455B30 | 90 | nop
00455B31 | 99 | cdq
00455B32 | E9 40F9FFFF | jmp pieces_crack1121.new.455477
ax里面的數值只要是SJIS范圍內的都行啦,不限于0x9F00,寫eax也行啦。
最后JMP會去的地方往上看也能看到SJIS那個編碼檢查的表,所以改這個也還要改SJIS的檢查表。

浙公網安備 33010602011771號