-----------------------------------------------------------------------
!!警告!!
游戏资源所有权,归游戏开发商所有;
以下解包算法仅供学习交流,请勿用于商业与非法用途;
由此产生的一切后果,与博主(我)无关;
-----------------------------------------------------------------------
废话不多说,游戏的封包以 pack 为后缀名,内存布局很简单,首先是一个文件信息头:
1 struct PACK_FILE_HEADER { 2 char sig[ 4 ]; // KCAP 3 short file_number; // 文件数量 4 char u1[ 2 ]; 5 };
sig 为 4 字节大小的签名,该值永远为 KCAP 四个字母,随后是 2 字节大小的文件数量;
随后是一串连续的文件信息结构体,结构体定义如下:
1 struct PACK_FILE_INFO { 2 char file_name[ 64 ]; // 文件完整路径名 3 BYTE u1[ 8 ]; 4 int file_data_pos_in_file; // 文件数据在 pack 中的开始位置 5 int file_size; // 文件数据大小 6 BYTE u2[ 4 ]; 7 };
以上两个结构体,以 u 开头的成员结构体,目前还没摸索出具体作用,其余成员都可以从变量名中看出具体用法,就不多解释了;
1 #include <stdio.h> 2 #include <string.h> 3 #include <Windows.h> 4 5 struct PACK_FILE_HEADER { 6 char sig[ 4 ]; // KCAP 7 short file_number; // 文件数量 8 char u1[ 2 ]; 9 }; 10 11 struct PACK_FILE_INFO { 12 char file_name[ 64 ]; // 文件完整路径名 13 BYTE u1[ 8 ]; 14 int file_data_pos_in_file; // 文件数据在 pack 中的开始位置 15 int file_size; // 文件数据大小 16 BYTE u2[ 4 ]; 17 }; 18 19 void buildDirectoryPath( const char * _path ){ 20 unsigned int len = ::strlen( _path ); 21 char * build_path = new char[ len + 1 ]; 22 for( unsigned int i = 0; i < len; ++ i ){ 23 build_path[ i ] = _path[ i ]; 24 if( ‘\\‘ == build_path[ i ] ){ 25 build_path[ i + 1 ] = ‘\0‘; 26 CreateDirectoryA( build_path, nullptr ); 27 } 28 } 29 } 30 31 const char * getFileTitle( const char * _file_name ){ 32 static char title[ 256 ] = { 0 }; 33 memset( title, 0, sizeof( title ) ); 34 for( unsigned int i = 0; i < strlen( _file_name ); ++ i ){ 35 if( ‘.‘ == _file_name[ i ] ) 36 break; 37 title[ i ] = _file_name[ i ]; 38 } 39 return ( const char * ) title; 40 } 41 42 void extraFile( FILE * _pack_file, const char * _pack_name, PACK_FILE_INFO * _file_info ){ 43 FILE * file = nullptr; 44 BYTE * file_data = new BYTE[ _file_info->file_size ]; 45 char full_path[ 256 ] = { 0 }; 46 47 sprintf_s( full_path, 256, 48 "%s\\%s", 49 getFileTitle( _pack_name ), 50 _file_info->file_name ); 51 buildDirectoryPath( full_path ); 52 53 printf( "%s \n", full_path ); 54 fopen_s( & file, full_path, "wb" ); 55 fseek( _pack_file, _file_info->file_data_pos_in_file, SEEK_SET ); 56 fread( file_data, 1, _file_info->file_size, _pack_file ); 57 fwrite( file_data, 1, _file_info->file_size, file ); 58 delete [ ] file_data; 59 fclose( file ); 60 } 61 62 void extraPack( const char * _pack_name ){ 63 FILE * pack_file = nullptr; 64 PACK_FILE_HEADER header; 65 PACK_FILE_INFO * file_list = nullptr; 66 67 fopen_s( & pack_file, _pack_name, "rb" ); 68 69 // 读取文件头信息 70 fread( ( void * ) & header, 1, sizeof( PACK_FILE_HEADER ), pack_file ); 71 72 // 读取文件信息 73 file_list = new PACK_FILE_INFO[ header.file_number ]; 74 fread( ( void * ) file_list, 1, sizeof( PACK_FILE_INFO ) * header.file_number, pack_file ); 75 76 // 解开文件 77 for( short i = 0; i < header.file_number; ++ i ){ 78 extraFile( pack_file, _pack_name, & file_list[ i ] ); 79 } 80 81 delete [ ] file_list; 82 fclose( pack_file ); 83 } 84 85 int main( int argc, char * argv[ ] ){ 86 extraPack( "data2.Pack" ); 87 extraPack( "data.Pack" ); 88 return 0; 89 }
以上代码通过函数 extraPack( ) 分别解开 data2.Pack 与 data.Pack 两个资源包, extraFile( ) 函数负责从 pack 里读出文件数据,并创建完整目录结构与文件,随后把数据写入到新建的文件里,getFileTitle( ) 函数可以从一个带后缀名的文件名中提取不包含后缀名的文件标题部分,buildDirectoryPath( ) 函数从给定的完整文件路径创建完整的目录链;
代码就是这么多,下面展示提取出来的资源:
还有一些脚本之类的文本文件:
完整的可执行程序就不给出来了,资源可以自己下载游戏,程序可以自己新建个 Win32 Console 工程,把上面的代码粘贴进去,直接生成就行了;
PS: buildDirectoryPath( ) 函数忘记 delete 那个 new 出来的 build_path 变量了,结果导致程序运行后产生大量内存泄漏,大家手工自己加上去吧(笑)