FanLoveJX
29-08-16, 02:08 PM
Đầu tiên xin gửi lời chúc sức khỏe đến các anh em trong box VLTK.
Mình có ít kinh nghiệm về resources xin phép chia sẻ với anh em, bài viết này chủ yếu dành cho newbie mong các bạn đã biết đừng ném đá ạ, ai chưa biết thì cứ hỏi nhiệt tình, còn ai biết rồi thì đóng góp thêm với ạ.
Sau bài viết này bạn sẽ có cái nhìn rõ hơn cũng như có thể tự mình viết được tools nén và xả nén file pak/spr hoặc làm hẵn một cái Editor để edit file pak, spr(Tĩnh/Động) :D.
[Phần 1] Pak
Cấu trúc file pak
File pack của king soft là file nén chứa resources của game như file txt, ini, dat, jpg, bmp, spr .v.v.v(mình sẽ dùng tên gọi chung là res).
Cấu trúc file pak gồm 3 phần:
Header: chứa thông tin chung cả file pak(Số lượng res trong file pak, vị trí truy cập của Block Offsets, .v.v.v).
Block Datas: chứa dữ liệu của từng file(Mỗi res được đưa vào file Pak dưới dạng block và được quản lý thông qua id, và id này được hash theo path của res vì vậy các tool unpack không thể tách được tên file, mà chỉ đưa ra dạng chung chung là các số thứ tự).
Block Offsets: chứa thông tin bộ nhớ của từng block(vị trí truy cập trên file pak, độ dài của block tính bằng byte).
Chi tiết trực quan: (Mình chỉ giải thích một số thông tin cần thiết)
Phần Header chứa 32 byte
Signature: chứa 4 byte nhận dạng file pak có giá trị "PAK ".
Count: số lượng res được nén trong file pak.
Index: Vị trí offset của BlockHeader đầu tiên
Phần Block Data chứa toàn bộ dữ liệu của res
Phần Block Offset chứa thông tin của từng res, mỗi res sẽ lưu thông tin tại đây với độ dài 16 byte bao gồm các thông tin sau:
ID: là định danh của file được hash bằng path của res (vd: /spr/ui/ui3/series/0.spr -> 127598303, cái này mình ví dụ thôi chứ không chính xác) toàn bộ file pak quản lý thông qua ID này, chính là lý do cần phải có path/tên mới unpak được res, và không thể hash ngược lại thành path/tên nhé = . = .
Offset: là vị trí bắt đầu dữ liệu của block nằm trên phần Block Datas.
RealLength: là kích thước trước khi nén(mọi đơn vị mình sẽ tính bằng byte hết).
Length: Kích thước sau khi nén cũng chính là kích thước của block nằm trên phần Block Datas, vậy ở đây ta có thể tính được độ lớn tối đa của một res trong file pak là 2^24 = 16777216 byte = 16 mbyte
Method: quy định phương thức nén của từng block ở đây mình check được một số phương thức như sau
Method = 0: Không nén dữ liệu chỉ đọc và rãi dữ liệu lên block.
Method = 1: Nén toàn bộ block bằng ucl(ucl là thư viện nén dữ liệu mà kingsoft sử dụng).
Method = 16: Hôm qua mới detect được, chưa xem kỹ dữ liệu giãi mã có đúng không nhưng dùng ucl decompress ra thì được đầu ra khớp số liệu trên BlockHeader
Method = 32: tương tự nhưng dạng 1. thường sử dụng để nén cả file spr(file pak mới của vina hay dùng mã này).
Method = 17: thường dùng nén frame file spr.(chi tiết cách nén frame mình sẽ đề cập ở phần tiếp theo).
<b><font color=red>[Chỉ có thành viên mới xem link được. <a href="register.php"> Nhấp đây để đăng ký thành viên......</a>]</font></b> (<b><font color=red>[Chỉ có thành viên mới xem link được. <a href="register.php"> Nhấp đây để đăng ký thành viên......</a>]</font></b>)
Cách đọc dữ liệu từ file pak(IDE mình dùng VS2013, Source mình viết theo cách ExportDll, Form mình dùng .Net cho nhanh do ko rành code form trên c++)
Đọc thông tin header file pak(Cấu trúc Header và BlockHeader xem trên hình bên trên)
inline static unsigned long GetCompressSize(BLOCKHEADER* header) {
return (((unsigned long)header->Length[2]) << 16) | (((unsigned long)header->Length[1]) << 8) | (unsigned long)header->Length[0];
}
int CPak::Load(const char* path, PACKHEDER* *Header) {
CString csPath(path);
PACKHEDER* pHeader; //Khởi tạo biến nhớ đọc header file pak
PBYTE pTemp; //Khởi tạo con trỏ đọc header
PBYTE oTemp; //Khởi tạo con trỏ đọc blockHeader
CFileException e;
if (m_File.m_hFile != (HANDLE)0xffffffff) {
m_File.Close();
}
if (!m_File.Open(csPath, CFile::modeRead | CFile::shareDenyWrite, &e))
{
return 0;
}
m_HeaderBuffer = new BYTE[32];
pTemp = m_HeaderBuffer; // đưa con trỏ về vị trí bộ nhớ chứa 32 byte thông tin header
m_File.Read(pTemp, 32); // Đọc 32 byte chứa header từ fileStream lên con trỏ pTemp
pHeader = (PACKHEDER*)pTemp; // Chuyển bộ nhớ con trỏ pTemp lên pHeader
m_OffsetBuffer = new BYTE[pHeader->Count * 16]; // Khởi tạo bộ nhớ chưa toàn bộ thông tin các offset
m_File.Seek(pHeader->Index, 0); // di chuyển con trỏ fileStream tới vị trí đầu tiên của blockHeader
m_File.Read(m_OffsetBuffer, pHeader->Count * 16); // Đọc toàn bộ dữ liệu lên bộ nhớ chứa thông tin blockOffset
oTemp = m_OffsetBuffer; // Chuyển vị trí con trỏ oTemp lên bộ nhớ vừa đọc để đọc thông tin BlockHeader
//for (int i = 0; i < pHeader->Count; i++) { //UnCommend đoạn này để xem bảng index các file show ở cửa sổ OutPut VS
//BLOCKHEADER* bHeader;
//bHeader = (BLOCKHEADER*)oTemp;
//CString outPutString;
//outPutString.Format(L"ID: %02x\tMethod: %d\tLength: %lu\tCompress: %lu\tOffset: %d \n", bHeader->ID, bHeader->Method, bHeader->RealLength, GetCompressSize(bHeader), bHeader->Offset);
//OutputDebugString(outPutString);
//oTemp += 16;
//}
*Header = (PACKHEDER*)m_HeaderBuffer; //Đưa con trỏ trên tham số đầu vào của phương thức về bộ nhớ 32 byte của header, do mình sử dụng .net nên set thế này rồi dung Marshal để đọc dữ liệu từ c++ qua c#
m_Count = pHeader->Count;
return m_Count;
}
Tách dữ liệu block
Sau khi lấy được danh sách các blockHeader dùng thông tin từ block header để bóc tách dữ liệu của res:
các bạn khai báo một số cấu trúc và biến để tiến hành phân tích block data
struct PACKHEDER {
unsigned char Signature[4];
unsigned long Count;
unsigned long Index;
unsigned long Data;
unsigned long CRC32;
unsigned char Reserved[12];
};
struct BLOCKHEADER {
unsigned long ID;
unsigned long Offset;
unsigned long RealLength;
BYTE Length[3];
unsigned char Method;
inline void operator = (BLOCKHEADER a) {
ID = a.ID;
Offset = a.Offset;
RealLength = a.RealLength;
Length[0] = a.Length[0];
Length[1] = a.Length[1];
Length[2] = a.Length[2];
Method = a.Method;
}
};
struct COMPRESSINFO {
long Compress;
long Size;
};
typedef struct
{
BYTE Comment[4];
WORD Width;
WORD Height;
WORD CenterX;
WORD CenterY;
WORD Frames;
WORD Colors;
WORD Directions;
WORD Interval;
WORD Reserved[6];
} SPRHEAD;
typedef struct
{
DWORD Offset;
DWORD Length;
} SPROFFS;
typedef struct
{
WORD Width;
WORD Height;
WORD OffsetX;
WORD OffsetY;
BYTE Sprite[1];
} SPRFRAME;
typedef struct {
BYTE Blue;
BYTE Green;
BYTE Red;
BYTE Alpha;
} KPAL32;
typedef struct {
BYTE Red;
BYTE Green;
BYTE Blue;
} KPAL24;
typedef WORD KPAL16;
BYTE* m_Buffer;
BYTE* m_Palette;
KPAL24* m_pPal24;
KPAL16* m_pPal16;
SPROFFS* m_pOffset;
PBYTE m_pSprite;
int m_nWidth;
int m_nHeight;
int m_nCenterX;
int m_nCenterY;
ULONG m_nFrames;
int m_nColors;
ULONG m_nDirections;
int m_nInterval;
int m_nColorStyle;
int m_nLum;
ULONG m_size;
int m_nFrameInfo;
CString m_sprPath;
DWORD* m_buffer32;
Tiến hành đọc và phân tích thông tin từ BlockHeader sau đó giải mã block data tương ứng với từng Method
(Ở đây mình viết inout theo cách lấy block thứ n của file, sau đó đổ dữ liệu đã giải mã ra buffer, bên c# dùng Marshal để đọc dữ liệu. Marshal là thư viện bên c# dùng để thao tác với dữ liệu unsafe anh em muốn tìm hiểu rõ hơn thì gg thêm nhé) mình có chú thích một chút ở từng đoạn code các bạn chú ý
int CPak::ReadBlock(int block, DWORD* *data) {
if (block < 0 || block >= m_Count) {
return 0;
}
if (m_ExtractBuffer != NULL) {
delete m_ExtractBuffer;
}
//Init File Mem Poiter
PBYTE oTemp = m_OffsetBuffer;
//Jump to Block Header Info Offset
oTemp += block * 16;
//Get Block Info from Mem
BLOCKHEADER* Header = (BLOCKHEADER*)oTemp;
if (Header->ID <= 0 || Header->Length <= 0 || Header->Offset <= 0) {
m_ExtractBuffer = new BYTE[1];
return 0;
}
//Caculate Compress Size of The Block
int size = GetCompressSize(Header);
//Init Block data Buffer
m_BlockBuffer = new BYTE[size];
//Seek File Mem To The Block Offset
m_File.Seek(Header->Offset, 0);
//Read Block Mem To Buffer
m_File.Read(m_BlockBuffer, size);
//Method = 1: Compress Image, ini, txt, ...etc
if (Header->Method == 0) {
//Get UnCompress Length
size = Header->RealLength;
//Return Mem Handle
*data = (DWORD*)m_BlockBuffer;
}
else if (Header->Method == 1) {
//Get UnCompress Length
size = Header->RealLength;
unsigned int dest_length = Header->RealLength;
//Init Buffer
m_ExtractBuffer = new BYTE[Header->RealLength];
unsigned int CompressSize = GetCompressSize(Header);
//Call Decompress Source Buffer
ucl_nrv2b_decompress_8(m_BlockBuffer, CompressSize, m_ExtractBuffer, &dest_length, 0);
//Release Buffer
delete m_BlockBuffer;
//Return Mem Handle
*data = (DWORD*)m_ExtractBuffer;
}
else if (Header->Method == 32) { // Phương thức nén mới(pak mới của vina), thực chất chỉ đổi id method, cách nén thì như Method 1
//Get UnCompress Length
size = Header->RealLength;
unsigned int dest_length = 0;
//Init Buffer
m_ExtractBuffer = new BYTE[Header->RealLength];
unsigned int CompressSize = GetCompressSize(Header);
//Call Decompress Source Buffer
ucl_nrv2b_decompress_8(m_BlockBuffer, CompressSize, m_ExtractBuffer, &dest_length, 0);
//Release Buffer
delete m_BlockBuffer;
//Return Mem Handle
*data = (DWORD*)m_ExtractBuffer;
}
//Method = 17: Compress Spr Image
else {
// Update sau phần 2;
}
return size;
}
- Các Method 0,1, 32 thì tương đối đơn giản chỉ nén toàn bộ block rồi ghi vào BlockData, Method 17 thì phức tạp hơn chỉ dùng cho file Spr và nén từng Frame
Mình xin tạm dừng phần này tại đây để mình đi chi tiết về cấu trúc SPR file, khi các bạn đã rõ về cấu trúc Spr thì phần nén frame này sẽ dễ hiểu hơn.
[Phần 2] Spr
Cấu trúc file Spr
File Spr(SpriteSheet) là loại file chứa nội dung hình ảnh trong game(Giao diện, NPC, Item .v.v.v)
Cấu trúc gồm:
SprHeader: chứa thông tin chung của file spr.
SprOffset: chứa thông tin vị trí và chiều dài dữ liệu từng frame.
SprFrame: chứa thông tin frame(chiều dài, rộng, độ lệch x, y.
SprColor: chứa bảng màu của file spr.
Chi tiết trực quan:
SprHeader chứa 32 byte thông tin file spr gồm các trường sau
Signature: chứa 4byte định danh file spr có giá trị là "SPR "
Width: chiều rộng spr
Height: chiều cao spr
CenterX: độ lệch X
CenterY: độ lệch Y
Frames: số frame của spr
Colors: số màu trên spr
Directions: số góc trên spr(mặc định có 1 hoặc 8 góc) chia đều frame cho các góc
Interval: tốc độ chạy frame mặc định
SprOffset chứa vị trí và chiều dài(byte) các frame gồm các thông tin sau
Offset: vị trí bộ nhớ của frame trên file pak
Length: chiều dài của frame
Array KPAL24 chứa bộ mã màu cho spr(về xử lý ảnh mình không đào sâu lắm nên mình không giải thích nhiều về phần này): số lượng màu được khai báo tại SprHeader->Colors
SprFrame: Định nghĩa chi tiết thông số của frame
Width: chiều rộng frame.
Height: chiều cao frame.
OriginX: độ lệch x.
OriginY: độ lệch y.
Data: được tính từ vị trí bắt đầu frame trừ đi độ lớn của thông tin frame kéo dài tới frame tiếp theo.
<b><font color=red>[Chỉ có thành viên mới xem link được. <a href="register.php"> Nhấp đây để đăng ký thành viên......</a>]</font></b> (<b><font color=red>[Chỉ có thành viên mới xem link được. <a href="register.php"> Nhấp đây để đăng ký thành viên......</a>]</font></b>)
Giải mã FrameSpr sang định dạng ARGB(Ảnh có nền trong suốt)
Toàn bộ ảnh spr trong game đều mã hóa dạng chiều sâu 24 bit tức là dùng KPAL24 để đánh bảng index màu cho spr
Từ bảng màu này qua một vài phép toán dịch bit và tách lấy màu từ bảng màu KPAL24 sẽ lấy được ảnh với format argb
Mình sẽ chỉ gới thiệu về giải mã từ 24 bit sang ARGB 32 bit, các bạn nào muốn lấy ảnh thấp hơn thì cách làm cũng tương tự
Khai báo biến
BYTE* m_Buffer;
BYTE* m_Palette;
KPAL24* m_pPal24;
KPAL16* m_pPal16;
SPROFFS* m_pOffset;
PBYTE m_pSprite;
int m_nWidth;
int m_nHeight;
int m_nCenterX;
int m_nCenterY;
ULONG m_nFrames;
int m_nColors;
ULONG m_nDirections;
int m_nInterval;
int m_nColorStyle;
int m_nLum;
ULONG m_size;
int m_nFrameInfo;
CString m_sprPath;
DWORD* m_buffer32;
Đọc thông tin file SPR
int CSpr::LoadSpr(const char* path)
{
CStringW szWide(path);
m_sprPath = path;
CFile File;
SPRHEAD* pHeader;
PBYTE pTemp;
CFileException e;
if (!File.Open(szWide, CFile::modeRead | CFile::shareDenyWrite, &e))
{
return 0;
}
m_sprPath = path;
m_size = File.GetLength();
m_Buffer = new BYTE[m_size];
pTemp = m_Buffer;
File.Read(pTemp, File.GetLength());
// check file header setup sprite member
pHeader = (SPRHEAD*)pTemp;
// get sprite info
m_nWidth = pHeader->Width;
m_nHeight = pHeader->Height;
m_nCenterX = pHeader->CenterX;
m_nCenterY = pHeader->CenterY;
m_nFrames = pHeader->Frames;
m_nColors = pHeader->Colors;
m_nDirections = pHeader->Directions;
m_nInterval = pHeader->Interval;
// setup palette pointer
pTemp += sizeof(SPRHEAD);
m_pPal24 = (KPAL24*)pTemp;
// setup offset pointer
pTemp += m_nColors * sizeof(KPAL24);
m_pOffset = (SPROFFS*)pTemp;
// setup sprite pointer
pTemp += m_nFrames * sizeof(SPROFFS);
m_pSprite = (LPBYTE)pTemp; // Ïà¶ÔÆ«ÒÆ
// make color table
m_Palette = new BYTE[m_nColors * sizeof(KPAL16)];
//caculate palete color
Make4444Palette();
return m_size;
}
Đọc thông tin frame và dịch sang mảng byte b-r-g-a, Những hàm còn thiếu ở đây đã có sẵn trong source Utility\Source\Image
int CSpr::GetSprFrame(DWORD* *data, int nFrame) {
if (nFrame < 0 || nFrame >= m_nFrames)
return 2;
//extract image size
ULONG width,height;
GetSize(nFrame,width,height);
WORD* pDest = new WORD[width*height];
ZeroMemory(pDest,sizeof(WORD)*width*height);
stImageRender render;
render.ptDes.x = 0;
render.ptDes.y = 0;
render.buffer = pDest;
render.nPitch = width * 2;
render.nFrame = nFrame;
RenderToA4R4G4B4(render);
if (!m_buffer32) {
delete m_buffer32;
}
m_buffer32 = new DWORD[width*height];
char* p32 = (char*)m_buffer32;
WORD* p16 = pDest;
BYTE a,r,g,b;
for (int i=0; i<width*height; i++)
{
GetColorA4R4G4B4Format(*p16++,a,r,g,b);
*p32++ = b;
*p32++ = g;
*p32++ = r;
*p32++ = a;
}
*data = (DWORD*)m_buffer32;
delete pDest;
return width*height*4;
}
HRESULT CSpr::RenderToA4R4G4B4(stImageRender& render)
{
if (render.nFrame < 0 || render.nFrame >= m_nFrames)
return FALSE;
SPRFRAME* pFrame = (SPRFRAME*)(GetFrame(render.nFrame));
int height = pFrame->Height;
int width = pFrame->Width;
long nNextLine = render.nPitch - width * 2;
PBYTE pSrc = pFrame->Sprite;
PBYTE pPalette = (PBYTE)GetPalette();
WORD* pDest = (WORD*)render.buffer;
pDest += ( render.ptDes.y * render.nPitch / 2 + render.ptDes.x ) ;
__asm
{
mov edi, pDest
mov esi, pSrc
loc_DrawSprite_0100:
mov edx, width
loc_DrawSprite_0101:
movzx eax, byte ptr[esi]
inc esi
movzx ebx, byte ptr[esi]
inc esi
or ebx, ebx
jnz loc_DrawSprite_0102
push eax
mov ecx, eax
loc_FillZeroAlpha:
mov [edi], 0
inc edi
inc edi
dec ecx
jnz loc_FillZeroAlpha
pop eax
sub edx, eax
jg loc_DrawSprite_0101
add edi, nNextLine
dec height
jnz loc_DrawSprite_0100
jmp loc_DrawSprite_exit
loc_DrawSprite_0102:
push eax
push edx
and bx, 0x00f0
shl bx, 8
push ebx
mov ecx, eax
loc_DrawSprite_0103:
mov ebx, pPalette
movzx eax, byte ptr[esi]
inc esi
mov dx, [ebx + eax * 2]
pop ebx
push ebx
or dx, bx
mov [edi], dx
inc edi
inc edi
dec ecx
jnz loc_DrawSprite_0103
pop ebx
pop edx
pop eax
sub edx, eax
jg loc_DrawSprite_0101
add edi, nNextLine
dec height
jnz loc_DrawSprite_0100
jmp loc_DrawSprite_exit
loc_DrawSprite_exit:
}
return S_OK;
}
Sắp tới share cho anh em PakEditor dùng chơi, unpack được file pak mới, có preview cho Spr và hỗ trợ luôn file Spr mới
500đ ảnh
<b><font color=red>[Chỉ có thành viên mới xem link được. <a href="register.php"> Nhấp đây để đăng ký thành viên......</a>]</font></b> (<b><font color=red>[Chỉ có thành viên mới xem link được. <a href="register.php"> Nhấp đây để đăng ký thành viên......</a>]</font></b>)
Mình có ít kinh nghiệm về resources xin phép chia sẻ với anh em, bài viết này chủ yếu dành cho newbie mong các bạn đã biết đừng ném đá ạ, ai chưa biết thì cứ hỏi nhiệt tình, còn ai biết rồi thì đóng góp thêm với ạ.
Sau bài viết này bạn sẽ có cái nhìn rõ hơn cũng như có thể tự mình viết được tools nén và xả nén file pak/spr hoặc làm hẵn một cái Editor để edit file pak, spr(Tĩnh/Động) :D.
[Phần 1] Pak
Cấu trúc file pak
File pack của king soft là file nén chứa resources của game như file txt, ini, dat, jpg, bmp, spr .v.v.v(mình sẽ dùng tên gọi chung là res).
Cấu trúc file pak gồm 3 phần:
Header: chứa thông tin chung cả file pak(Số lượng res trong file pak, vị trí truy cập của Block Offsets, .v.v.v).
Block Datas: chứa dữ liệu của từng file(Mỗi res được đưa vào file Pak dưới dạng block và được quản lý thông qua id, và id này được hash theo path của res vì vậy các tool unpack không thể tách được tên file, mà chỉ đưa ra dạng chung chung là các số thứ tự).
Block Offsets: chứa thông tin bộ nhớ của từng block(vị trí truy cập trên file pak, độ dài của block tính bằng byte).
Chi tiết trực quan: (Mình chỉ giải thích một số thông tin cần thiết)
Phần Header chứa 32 byte
Signature: chứa 4 byte nhận dạng file pak có giá trị "PAK ".
Count: số lượng res được nén trong file pak.
Index: Vị trí offset của BlockHeader đầu tiên
Phần Block Data chứa toàn bộ dữ liệu của res
Phần Block Offset chứa thông tin của từng res, mỗi res sẽ lưu thông tin tại đây với độ dài 16 byte bao gồm các thông tin sau:
ID: là định danh của file được hash bằng path của res (vd: /spr/ui/ui3/series/0.spr -> 127598303, cái này mình ví dụ thôi chứ không chính xác) toàn bộ file pak quản lý thông qua ID này, chính là lý do cần phải có path/tên mới unpak được res, và không thể hash ngược lại thành path/tên nhé = . = .
Offset: là vị trí bắt đầu dữ liệu của block nằm trên phần Block Datas.
RealLength: là kích thước trước khi nén(mọi đơn vị mình sẽ tính bằng byte hết).
Length: Kích thước sau khi nén cũng chính là kích thước của block nằm trên phần Block Datas, vậy ở đây ta có thể tính được độ lớn tối đa của một res trong file pak là 2^24 = 16777216 byte = 16 mbyte
Method: quy định phương thức nén của từng block ở đây mình check được một số phương thức như sau
Method = 0: Không nén dữ liệu chỉ đọc và rãi dữ liệu lên block.
Method = 1: Nén toàn bộ block bằng ucl(ucl là thư viện nén dữ liệu mà kingsoft sử dụng).
Method = 16: Hôm qua mới detect được, chưa xem kỹ dữ liệu giãi mã có đúng không nhưng dùng ucl decompress ra thì được đầu ra khớp số liệu trên BlockHeader
Method = 32: tương tự nhưng dạng 1. thường sử dụng để nén cả file spr(file pak mới của vina hay dùng mã này).
Method = 17: thường dùng nén frame file spr.(chi tiết cách nén frame mình sẽ đề cập ở phần tiếp theo).
<b><font color=red>[Chỉ có thành viên mới xem link được. <a href="register.php"> Nhấp đây để đăng ký thành viên......</a>]</font></b> (<b><font color=red>[Chỉ có thành viên mới xem link được. <a href="register.php"> Nhấp đây để đăng ký thành viên......</a>]</font></b>)
Cách đọc dữ liệu từ file pak(IDE mình dùng VS2013, Source mình viết theo cách ExportDll, Form mình dùng .Net cho nhanh do ko rành code form trên c++)
Đọc thông tin header file pak(Cấu trúc Header và BlockHeader xem trên hình bên trên)
inline static unsigned long GetCompressSize(BLOCKHEADER* header) {
return (((unsigned long)header->Length[2]) << 16) | (((unsigned long)header->Length[1]) << 8) | (unsigned long)header->Length[0];
}
int CPak::Load(const char* path, PACKHEDER* *Header) {
CString csPath(path);
PACKHEDER* pHeader; //Khởi tạo biến nhớ đọc header file pak
PBYTE pTemp; //Khởi tạo con trỏ đọc header
PBYTE oTemp; //Khởi tạo con trỏ đọc blockHeader
CFileException e;
if (m_File.m_hFile != (HANDLE)0xffffffff) {
m_File.Close();
}
if (!m_File.Open(csPath, CFile::modeRead | CFile::shareDenyWrite, &e))
{
return 0;
}
m_HeaderBuffer = new BYTE[32];
pTemp = m_HeaderBuffer; // đưa con trỏ về vị trí bộ nhớ chứa 32 byte thông tin header
m_File.Read(pTemp, 32); // Đọc 32 byte chứa header từ fileStream lên con trỏ pTemp
pHeader = (PACKHEDER*)pTemp; // Chuyển bộ nhớ con trỏ pTemp lên pHeader
m_OffsetBuffer = new BYTE[pHeader->Count * 16]; // Khởi tạo bộ nhớ chưa toàn bộ thông tin các offset
m_File.Seek(pHeader->Index, 0); // di chuyển con trỏ fileStream tới vị trí đầu tiên của blockHeader
m_File.Read(m_OffsetBuffer, pHeader->Count * 16); // Đọc toàn bộ dữ liệu lên bộ nhớ chứa thông tin blockOffset
oTemp = m_OffsetBuffer; // Chuyển vị trí con trỏ oTemp lên bộ nhớ vừa đọc để đọc thông tin BlockHeader
//for (int i = 0; i < pHeader->Count; i++) { //UnCommend đoạn này để xem bảng index các file show ở cửa sổ OutPut VS
//BLOCKHEADER* bHeader;
//bHeader = (BLOCKHEADER*)oTemp;
//CString outPutString;
//outPutString.Format(L"ID: %02x\tMethod: %d\tLength: %lu\tCompress: %lu\tOffset: %d \n", bHeader->ID, bHeader->Method, bHeader->RealLength, GetCompressSize(bHeader), bHeader->Offset);
//OutputDebugString(outPutString);
//oTemp += 16;
//}
*Header = (PACKHEDER*)m_HeaderBuffer; //Đưa con trỏ trên tham số đầu vào của phương thức về bộ nhớ 32 byte của header, do mình sử dụng .net nên set thế này rồi dung Marshal để đọc dữ liệu từ c++ qua c#
m_Count = pHeader->Count;
return m_Count;
}
Tách dữ liệu block
Sau khi lấy được danh sách các blockHeader dùng thông tin từ block header để bóc tách dữ liệu của res:
các bạn khai báo một số cấu trúc và biến để tiến hành phân tích block data
struct PACKHEDER {
unsigned char Signature[4];
unsigned long Count;
unsigned long Index;
unsigned long Data;
unsigned long CRC32;
unsigned char Reserved[12];
};
struct BLOCKHEADER {
unsigned long ID;
unsigned long Offset;
unsigned long RealLength;
BYTE Length[3];
unsigned char Method;
inline void operator = (BLOCKHEADER a) {
ID = a.ID;
Offset = a.Offset;
RealLength = a.RealLength;
Length[0] = a.Length[0];
Length[1] = a.Length[1];
Length[2] = a.Length[2];
Method = a.Method;
}
};
struct COMPRESSINFO {
long Compress;
long Size;
};
typedef struct
{
BYTE Comment[4];
WORD Width;
WORD Height;
WORD CenterX;
WORD CenterY;
WORD Frames;
WORD Colors;
WORD Directions;
WORD Interval;
WORD Reserved[6];
} SPRHEAD;
typedef struct
{
DWORD Offset;
DWORD Length;
} SPROFFS;
typedef struct
{
WORD Width;
WORD Height;
WORD OffsetX;
WORD OffsetY;
BYTE Sprite[1];
} SPRFRAME;
typedef struct {
BYTE Blue;
BYTE Green;
BYTE Red;
BYTE Alpha;
} KPAL32;
typedef struct {
BYTE Red;
BYTE Green;
BYTE Blue;
} KPAL24;
typedef WORD KPAL16;
BYTE* m_Buffer;
BYTE* m_Palette;
KPAL24* m_pPal24;
KPAL16* m_pPal16;
SPROFFS* m_pOffset;
PBYTE m_pSprite;
int m_nWidth;
int m_nHeight;
int m_nCenterX;
int m_nCenterY;
ULONG m_nFrames;
int m_nColors;
ULONG m_nDirections;
int m_nInterval;
int m_nColorStyle;
int m_nLum;
ULONG m_size;
int m_nFrameInfo;
CString m_sprPath;
DWORD* m_buffer32;
Tiến hành đọc và phân tích thông tin từ BlockHeader sau đó giải mã block data tương ứng với từng Method
(Ở đây mình viết inout theo cách lấy block thứ n của file, sau đó đổ dữ liệu đã giải mã ra buffer, bên c# dùng Marshal để đọc dữ liệu. Marshal là thư viện bên c# dùng để thao tác với dữ liệu unsafe anh em muốn tìm hiểu rõ hơn thì gg thêm nhé) mình có chú thích một chút ở từng đoạn code các bạn chú ý
int CPak::ReadBlock(int block, DWORD* *data) {
if (block < 0 || block >= m_Count) {
return 0;
}
if (m_ExtractBuffer != NULL) {
delete m_ExtractBuffer;
}
//Init File Mem Poiter
PBYTE oTemp = m_OffsetBuffer;
//Jump to Block Header Info Offset
oTemp += block * 16;
//Get Block Info from Mem
BLOCKHEADER* Header = (BLOCKHEADER*)oTemp;
if (Header->ID <= 0 || Header->Length <= 0 || Header->Offset <= 0) {
m_ExtractBuffer = new BYTE[1];
return 0;
}
//Caculate Compress Size of The Block
int size = GetCompressSize(Header);
//Init Block data Buffer
m_BlockBuffer = new BYTE[size];
//Seek File Mem To The Block Offset
m_File.Seek(Header->Offset, 0);
//Read Block Mem To Buffer
m_File.Read(m_BlockBuffer, size);
//Method = 1: Compress Image, ini, txt, ...etc
if (Header->Method == 0) {
//Get UnCompress Length
size = Header->RealLength;
//Return Mem Handle
*data = (DWORD*)m_BlockBuffer;
}
else if (Header->Method == 1) {
//Get UnCompress Length
size = Header->RealLength;
unsigned int dest_length = Header->RealLength;
//Init Buffer
m_ExtractBuffer = new BYTE[Header->RealLength];
unsigned int CompressSize = GetCompressSize(Header);
//Call Decompress Source Buffer
ucl_nrv2b_decompress_8(m_BlockBuffer, CompressSize, m_ExtractBuffer, &dest_length, 0);
//Release Buffer
delete m_BlockBuffer;
//Return Mem Handle
*data = (DWORD*)m_ExtractBuffer;
}
else if (Header->Method == 32) { // Phương thức nén mới(pak mới của vina), thực chất chỉ đổi id method, cách nén thì như Method 1
//Get UnCompress Length
size = Header->RealLength;
unsigned int dest_length = 0;
//Init Buffer
m_ExtractBuffer = new BYTE[Header->RealLength];
unsigned int CompressSize = GetCompressSize(Header);
//Call Decompress Source Buffer
ucl_nrv2b_decompress_8(m_BlockBuffer, CompressSize, m_ExtractBuffer, &dest_length, 0);
//Release Buffer
delete m_BlockBuffer;
//Return Mem Handle
*data = (DWORD*)m_ExtractBuffer;
}
//Method = 17: Compress Spr Image
else {
// Update sau phần 2;
}
return size;
}
- Các Method 0,1, 32 thì tương đối đơn giản chỉ nén toàn bộ block rồi ghi vào BlockData, Method 17 thì phức tạp hơn chỉ dùng cho file Spr và nén từng Frame
Mình xin tạm dừng phần này tại đây để mình đi chi tiết về cấu trúc SPR file, khi các bạn đã rõ về cấu trúc Spr thì phần nén frame này sẽ dễ hiểu hơn.
[Phần 2] Spr
Cấu trúc file Spr
File Spr(SpriteSheet) là loại file chứa nội dung hình ảnh trong game(Giao diện, NPC, Item .v.v.v)
Cấu trúc gồm:
SprHeader: chứa thông tin chung của file spr.
SprOffset: chứa thông tin vị trí và chiều dài dữ liệu từng frame.
SprFrame: chứa thông tin frame(chiều dài, rộng, độ lệch x, y.
SprColor: chứa bảng màu của file spr.
Chi tiết trực quan:
SprHeader chứa 32 byte thông tin file spr gồm các trường sau
Signature: chứa 4byte định danh file spr có giá trị là "SPR "
Width: chiều rộng spr
Height: chiều cao spr
CenterX: độ lệch X
CenterY: độ lệch Y
Frames: số frame của spr
Colors: số màu trên spr
Directions: số góc trên spr(mặc định có 1 hoặc 8 góc) chia đều frame cho các góc
Interval: tốc độ chạy frame mặc định
SprOffset chứa vị trí và chiều dài(byte) các frame gồm các thông tin sau
Offset: vị trí bộ nhớ của frame trên file pak
Length: chiều dài của frame
Array KPAL24 chứa bộ mã màu cho spr(về xử lý ảnh mình không đào sâu lắm nên mình không giải thích nhiều về phần này): số lượng màu được khai báo tại SprHeader->Colors
SprFrame: Định nghĩa chi tiết thông số của frame
Width: chiều rộng frame.
Height: chiều cao frame.
OriginX: độ lệch x.
OriginY: độ lệch y.
Data: được tính từ vị trí bắt đầu frame trừ đi độ lớn của thông tin frame kéo dài tới frame tiếp theo.
<b><font color=red>[Chỉ có thành viên mới xem link được. <a href="register.php"> Nhấp đây để đăng ký thành viên......</a>]</font></b> (<b><font color=red>[Chỉ có thành viên mới xem link được. <a href="register.php"> Nhấp đây để đăng ký thành viên......</a>]</font></b>)
Giải mã FrameSpr sang định dạng ARGB(Ảnh có nền trong suốt)
Toàn bộ ảnh spr trong game đều mã hóa dạng chiều sâu 24 bit tức là dùng KPAL24 để đánh bảng index màu cho spr
Từ bảng màu này qua một vài phép toán dịch bit và tách lấy màu từ bảng màu KPAL24 sẽ lấy được ảnh với format argb
Mình sẽ chỉ gới thiệu về giải mã từ 24 bit sang ARGB 32 bit, các bạn nào muốn lấy ảnh thấp hơn thì cách làm cũng tương tự
Khai báo biến
BYTE* m_Buffer;
BYTE* m_Palette;
KPAL24* m_pPal24;
KPAL16* m_pPal16;
SPROFFS* m_pOffset;
PBYTE m_pSprite;
int m_nWidth;
int m_nHeight;
int m_nCenterX;
int m_nCenterY;
ULONG m_nFrames;
int m_nColors;
ULONG m_nDirections;
int m_nInterval;
int m_nColorStyle;
int m_nLum;
ULONG m_size;
int m_nFrameInfo;
CString m_sprPath;
DWORD* m_buffer32;
Đọc thông tin file SPR
int CSpr::LoadSpr(const char* path)
{
CStringW szWide(path);
m_sprPath = path;
CFile File;
SPRHEAD* pHeader;
PBYTE pTemp;
CFileException e;
if (!File.Open(szWide, CFile::modeRead | CFile::shareDenyWrite, &e))
{
return 0;
}
m_sprPath = path;
m_size = File.GetLength();
m_Buffer = new BYTE[m_size];
pTemp = m_Buffer;
File.Read(pTemp, File.GetLength());
// check file header setup sprite member
pHeader = (SPRHEAD*)pTemp;
// get sprite info
m_nWidth = pHeader->Width;
m_nHeight = pHeader->Height;
m_nCenterX = pHeader->CenterX;
m_nCenterY = pHeader->CenterY;
m_nFrames = pHeader->Frames;
m_nColors = pHeader->Colors;
m_nDirections = pHeader->Directions;
m_nInterval = pHeader->Interval;
// setup palette pointer
pTemp += sizeof(SPRHEAD);
m_pPal24 = (KPAL24*)pTemp;
// setup offset pointer
pTemp += m_nColors * sizeof(KPAL24);
m_pOffset = (SPROFFS*)pTemp;
// setup sprite pointer
pTemp += m_nFrames * sizeof(SPROFFS);
m_pSprite = (LPBYTE)pTemp; // Ïà¶ÔÆ«ÒÆ
// make color table
m_Palette = new BYTE[m_nColors * sizeof(KPAL16)];
//caculate palete color
Make4444Palette();
return m_size;
}
Đọc thông tin frame và dịch sang mảng byte b-r-g-a, Những hàm còn thiếu ở đây đã có sẵn trong source Utility\Source\Image
int CSpr::GetSprFrame(DWORD* *data, int nFrame) {
if (nFrame < 0 || nFrame >= m_nFrames)
return 2;
//extract image size
ULONG width,height;
GetSize(nFrame,width,height);
WORD* pDest = new WORD[width*height];
ZeroMemory(pDest,sizeof(WORD)*width*height);
stImageRender render;
render.ptDes.x = 0;
render.ptDes.y = 0;
render.buffer = pDest;
render.nPitch = width * 2;
render.nFrame = nFrame;
RenderToA4R4G4B4(render);
if (!m_buffer32) {
delete m_buffer32;
}
m_buffer32 = new DWORD[width*height];
char* p32 = (char*)m_buffer32;
WORD* p16 = pDest;
BYTE a,r,g,b;
for (int i=0; i<width*height; i++)
{
GetColorA4R4G4B4Format(*p16++,a,r,g,b);
*p32++ = b;
*p32++ = g;
*p32++ = r;
*p32++ = a;
}
*data = (DWORD*)m_buffer32;
delete pDest;
return width*height*4;
}
HRESULT CSpr::RenderToA4R4G4B4(stImageRender& render)
{
if (render.nFrame < 0 || render.nFrame >= m_nFrames)
return FALSE;
SPRFRAME* pFrame = (SPRFRAME*)(GetFrame(render.nFrame));
int height = pFrame->Height;
int width = pFrame->Width;
long nNextLine = render.nPitch - width * 2;
PBYTE pSrc = pFrame->Sprite;
PBYTE pPalette = (PBYTE)GetPalette();
WORD* pDest = (WORD*)render.buffer;
pDest += ( render.ptDes.y * render.nPitch / 2 + render.ptDes.x ) ;
__asm
{
mov edi, pDest
mov esi, pSrc
loc_DrawSprite_0100:
mov edx, width
loc_DrawSprite_0101:
movzx eax, byte ptr[esi]
inc esi
movzx ebx, byte ptr[esi]
inc esi
or ebx, ebx
jnz loc_DrawSprite_0102
push eax
mov ecx, eax
loc_FillZeroAlpha:
mov [edi], 0
inc edi
inc edi
dec ecx
jnz loc_FillZeroAlpha
pop eax
sub edx, eax
jg loc_DrawSprite_0101
add edi, nNextLine
dec height
jnz loc_DrawSprite_0100
jmp loc_DrawSprite_exit
loc_DrawSprite_0102:
push eax
push edx
and bx, 0x00f0
shl bx, 8
push ebx
mov ecx, eax
loc_DrawSprite_0103:
mov ebx, pPalette
movzx eax, byte ptr[esi]
inc esi
mov dx, [ebx + eax * 2]
pop ebx
push ebx
or dx, bx
mov [edi], dx
inc edi
inc edi
dec ecx
jnz loc_DrawSprite_0103
pop ebx
pop edx
pop eax
sub edx, eax
jg loc_DrawSprite_0101
add edi, nNextLine
dec height
jnz loc_DrawSprite_0100
jmp loc_DrawSprite_exit
loc_DrawSprite_exit:
}
return S_OK;
}
Sắp tới share cho anh em PakEditor dùng chơi, unpack được file pak mới, có preview cho Spr và hỗ trợ luôn file Spr mới
500đ ảnh
<b><font color=red>[Chỉ có thành viên mới xem link được. <a href="register.php"> Nhấp đây để đăng ký thành viên......</a>]</font></b> (<b><font color=red>[Chỉ có thành viên mới xem link được. <a href="register.php"> Nhấp đây để đăng ký thành viên......</a>]</font></b>)