前一段时间写了两篇关于游戏封包格式反汇编的文章(虽然有一篇并没有分析出结果OTL),而且分析完了也没写出能提取资源的程序。于是这次我就由解包到翻译到封包到修改程序完整地写一篇教程。这次的游戏选择的是DC2PC,大小是7.62GB(- -|||)
一、资源分析
一般对于汉化游戏而言,脚本是汉化者最重要的部分(大多数时候还要提取出部分图片进行修改,部分有过场动画的游戏还要提取出动画嵌入字幕再封回去,至于声音的话……难道有人打算将日语的配音换成普通话么)。一般脚本文件要么是多个大小不超过1MB的文件放在特定的文件夹,要么就是单个大小不超过20M的单个文件,还有种情况就是多种资源封装在一起组成一个很大的资源文件。对于本例的游戏而言,脚本文件放在\DC2PC\Advdata\MES\目录下面,有很多个大小在20KB以内的文件。
至于图片等资源文件,为求简洁,这里就不分析了。
进入\DC2PC\Advdata\MES\下面,用WinHex随便打开一个文件,看到如下图所示:
可以看出脚本文件里面能看到明文的要载入的资源位置,而后面有一段乱码,在日文编码中,一般日文的高位是0x80,可见,这段乱码是经过加密的。由此猜测,这段就是游戏中的对白。至于具体的加密方式……好吧,CIRCUS的游戏加密的方式一般都是每字节减0x20,这个也不例外。但本着学术研究的精神,为严谨起见,下面我还是会用OD进行调试的。
在这个文件夹发现有个start.mes的文件,打开看看发现没有像前面那样的乱码。如无意外,这个应该是控制游戏启动的脚本。既然这样,我们就应该在开始游戏的时候来进行分析了。
二、提取文本
到了这一步,就轮到od登场了,首先载入游戏,然后F9运行,然后在游戏界面点下开始游戏的用时在od的命令行输入bp CreateFileA,接着od就将打开文件的操作断了下来。一直按F9到打开的是mes后缀名的文件为止。而目前要打开的是fst_1216_a1_cmn.mes这个文件。接着对ReadFile下断,断下后单步运行到函数返回,回到下面的地方:
#include<windows.h>
#include<iostream>
#include<string.h>
using namespace std;
int main()
{
HANDLE hfile=CreateFile
("c:\\fst_1216_a1_cmn.mes",GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,NULL);
if(hfile==INVALID_HANDLE_VALUE)
{
cout<<"Can not open file!"<<endl;
return 0;
}
DWORD size=GetFileSize(hfile,NULL);
DWORD len;
char *buff=new char [size];
if(!ReadFile(hfile,(LPVOID)buff,size,&len,NULL))
{
cout<<"Can not read file!"<<endl;
CloseHandle(hfile);
return 0;
}
CloseHandle(hfile);
char temp[128]={0};
int j=0;
hfile=CreateFile("d:\\a1_cmn.txt",GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_ARCHIVE,NULL);
if(hfile==INVALID_HANDLE_VALUE)
{
cout<<"Can not create file!"<<endl;
return 0;
}
for(int i=0;i<size;i++)
{
j=0;
if(buff[i]==0x4c)
{
while(buff[i+j])buff[i+j++]+=0x20;
memcpy(temp,&buff[i],j);
i+=j;
temp[j]='\r';
temp[++j]='\n';
WriteFile(hfile,(LPVOID)temp,j,&len,NULL);
}
}
delete [] buff;
delete [] temp;
CloseHandle(hfile);
return 0;
}
#include<windows.h>
#include<iostream>
#include<string.h>
using namespace std;
int main()
{
HANDLE hfile=CreateFile("c:\\fst_1216_a1_cmn.mes",GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,NULL);
if(hfile==INVALID_HANDLE_VALUE)
{
cout<<"Can not open file!"<<endl;
return 0;
}
DWORD size=GetFileSize(hfile,NULL);
DWORD len;
BYTE *buff=new BYTE [size];
if(!ReadFile(hfile,(LPVOID)buff,size,&len,NULL))
{
cout<<"Can not read file!"<<endl;
CloseHandle(hfile);
return 0;
}
CloseHandle(hfile);
int j=0,nu=0;
hfile=CreateFile("d:\\a1_cmn.txt",GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_ARCHIVE,NULL);
if(hfile==INVALID_HANDLE_VALUE)
{
cout<<"Can not create file!"<<endl;
return 0;
}
DWORD textsize=GetFileSize(hfile,NULL);
BYTE *temp=new BYTE [textsize];
if(!ReadFile(hfile,(LPVOID)temp,textsize,&len,NULL))
{
cout<<"Can not read file!"<<endl;
CloseHandle(hfile);
return 0;
}
CloseHandle(hfile);
hfile=CreateFile("d:\\fst_1216_a1_cmn.mes",GENERIC_WRITE|GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_ARCHIVE,NULL);
if(hfile==INVALID_HANDLE_VALUE)
{
cout<<"Can not create new script file!"<<endl;
return 0;
}
for(int i=0;i<size;i++)
{
if(buff[i]>=0x4a&&buff[i]<0x4e)
{
while(buff[i++]){};
while(temp[j]!=0x0d)
{
temp[j]-=0x20;
WriteFile(hfile,(LPVOID)&temp[j++],1,&len,NULL);
}
WriteFile(hfile,(LPVOID)&nu,1,&len,NULL);
WriteFile(hfile,(LPVOID)&buff[i],1,&len,NULL);
j++;
}
else
{
WriteFile(hfile,(LPVOID)&buff[i],1,&len,NULL);
}
}
delete [] temp;
delete [] buff;
SetFilePointer(hfile,0,NULL,FILE_BEGIN);
size=GetFileSize(hfile,NULL);
buff=new BYTE [size];
if(!ReadFile(hfile,(LPVOID)buff,size,&len,NULL))
{
cout<<"Can not read file!"<<endl;
CloseHandle(hfile);
return 0;
}
DWORD *offsetarray=new DWORD [*(DWORD*)&buff[0]];
DWORD sizeofheader=(*(DWORD*)&buff[0])*4+4;
for(j=0,i=0;i<size;i++)
{
if(buff[i]>=0x4a&&buff[i]<0x4e)
{
offsetarray[j]=i-sizeofheader;
while(buff[i++]);
j++;
}
}
SetFilePointer(hfile,4,NULL,FILE_BEGIN);
WriteFile(hfile,(LPVOID)offsetarray,sizeofheader-4,&len,NULL);
delete [] offsetarray;
CloseHandle(hfile);
return 0;
}
0041A0BF |> \6A 04 push 4 ; 读取四个字节
0041A0C1 |. 68 4CA0AF00 push 00AFA04C ; 缓冲区
0041A0C6 |. 56 push esi
0041A0C7 |. E8 64080100 call 0042A930 ; 读取文件
0041A0CC |. 8B15 4CA0AF00 mov edx, dword ptr [AFA04C]
0041A0D2 |. 8D0495 000000>lea eax, dword ptr [edx*4] ; 文件首双字*4
0041A0D9 |. 50 push eax ; 读取字节数
0041A0DA |. 68 2431B000 push 00B03124 ; 缓冲区
0041A0DF |. 56 push esi ; 未知
0041A0E0 |. E8 4B080100 call 0042A930 ; 读取文件
0041A0E5 |. 68 A0860100 push 186A0 ; 读取字节数
0041A0EA |. 68 B8D3AA00 push 00AAD3B8 ; 缓冲区
0041A0EF |. 56 push esi
0041A0F0 |. E8 3B080100 call 0042A930 ; 读取文件
0041C575 |. 892D 30CEB000 |mov dword ptr [B0CE30], ebp
0041C57B |. 0FBEB0 B8D3AA>|movsx esi, byte ptr [eax+AAD3B8]
0041C582 |. 83FE 29 |cmp esi, 29
0041C585 |. 897424 28 |mov dword ptr [esp+28], esi
0041C589 |. 8915 2CCEB000 |mov dword ptr [B0CE2C], edx
0041C58F |. 7D 32 |jge short 0041C5C3
0041C591 |. 8A90 B9D3AA00 |mov dl, byte ptr [eax+AAD3B9]
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)