Packing app resources
Jika kita memperhatikan secara seksama semua program-program atau game yang tidak trivial (yang serius) selalu memproteksi resources/sumber daya yang digunakan. Resources disini maksudnya file-file yang berguna untuk game/program, misalnya library, gambar, sound, config file, text file, dan lainnya. Tujuannya jelas, agar tidak mudah diubah/dimodifikasi oleh user, atau setidaknya kalau pun user bisa mengubah , itu tidak dilakukan secara mudah. (ya, tidak ada yang 100 % aman di dunia komputer ). Dari apa yang saya lihat di folder data dari program dan games yang ter-install di komputer, ada macam-macam teknik untuk memproteksinya, ada yang hanya mengubah ekstensi file, (misalnya file : gambar.jpg menjadi blablabla.dll ), melakukan enkripsi dan mengkompress file, dan ada juga yang menjadikan seluruh resources menjadi 1 file besar, misalnya data.pak . Bagaimana kalau ingin membuat proteksi semacam itu? . Saya ingin mencobanya disini, tidak perlu menulis program dari scratch, cukup menggunakan library yang sudah ada. Zlib (library kompres-dekompresi yang popular se-antero internet :-D ) dan C++ Boost adalah pilihan yang menarik. Dengan meng-kompresi data, kita tidak hanya membuat data tersebut tidak mudah di hack tapi juga membuat ukuran menjadi kecil a.k.a menghemat space.
Saya akan membuatnya sederhana. Ada 2 hal yang jelas : [1] program yang melakukan packing, dan [2] data yang terpacking bisa dibaca ulang oleh program yang akan menggunakan data tersebut. Daripada menjadikan beberapa file menjadi 1 file besar, saya lebih memillih mem-packing beberapa file dari sebuah folder (dan sub folder didalam-nya) kedalam 1 folder besar. Untuk mempermudah pembacaan dan peng-organisasian, dari segi pembaca data yang sudah di pack , “data” seolah-olah masih berada di folder sebelum dipack ( tidak membaca dari 1 folder ) . Untuk itu saya akan menyimpan informasi file-file tersebut kedalam 1 file berformat biner, yang nantinya bisa dibaca ulang.
Hmm.. so, here we go, berikut program test-nya: ( klik untuk ekspand source)
[sourcecode collapse=“true” language=“cpp”]
// test: packing-unpacking files
// @ edi ermawan , 20 Sept 2011
#include
#include
using std::fstream; using namespace boost::filesystem;
namespace xediconst { const std::string FileNameIdent =“data”; const std::string FileNameIdentExt =".pak"; const int max_path_str = 200; }
using namespace xediconst;
class FileInfo { public: FileInfo() {
}
FileInfo(const std::string& str): mFileID(mLastFileID++)
{
const char *pC = str.data();
int length = str.size();
strncpy( mFileName, pC, length );
mFileName[length]='\0';
}
~FileInfo()
{
};
void setFileInfo(const int id,const std::string& str)
{
mFileID=id;
const char *pC = str.data();
int length = str.size();
strncpy( mFileName, pC, length );
mFileName[length]='\0';
};
int getFileID()
{
return mFileID;
};
std::string getFileName()
{
return mFileName;
};
public: static int mLastFileID; private: int mFileID; char mFileName[xediconst::max_path_str]; };
int FileInfo::mLastFileID=0; //use boost bool GetAllFiles(const path& dir_path,std::vector<FileInfo*>& list) { if ( !exists( dir_path ) ) return false; directory_iterator end_itr; for ( directory_iterator itr( dir_path ); itr != end_itr; ++itr ) { if ( is_directory(itr->status()) ) { GetAllFiles( itr->path(),list); } else { list.push_back(new FileInfo(itr->path().string())); } } return true; }
void SaveFile(std::vector<FileInfo*>& listFiles,const std::string& nameFile) { std::string nameFile2=nameFile+"\"+FileNameIdent+FileNameIdentExt; fstream fileOut(nameFile2.c_str(),std::ios::out|std::ios::binary);
std::cout<<" Saving... "<<nameFile<<std::endl;
if(!fileOut)
{
std::cout<<"Error saving file."<<std::endl;
return;
}
int i=0;
for(std::vector<FileInfo*>::iterator it=listFiles.begin();
it!=listFiles.end();it++)
{
fileOut.seekp( (i++) * sizeof(FileInfo) );
const char* c=reinterpret_cast<const char*>(*it);
fileOut.write(c,sizeof(FileInfo));
}
fileOut.close();
}
void ReadFiles(std::vector<FileInfo*>& listFiles,const std::string& nameFile) { std::string nameFile2=nameFile+"\"+FileNameIdent+FileNameIdentExt; fstream fileIn(nameFile2.c_str(),std::ios::in | std::ios::binary);
std::cout<<"Reading... "<<nameFile<<std::endl;
if(!fileIn)
{
std::cout<<"Error reading file."<<std::endl;
return;
}
FileInfo* temp=new FileInfo();
fileIn.read(reinterpret_cast<char*>(temp),sizeof(FileInfo));
while(fileIn && !fileIn.eof())
{
listFiles.push_back(temp);
temp=new FileInfo();
fileIn.read(reinterpret_cast<char*>(temp),sizeof(FileInfo));
}
fileIn.close();
} //compress-decompress : use zlib void CompressFiles(std::vector<FileInfo*>& listFiles,const std::string& nameFile) { FILE *file_in; FILE *file_out;
for(std::vector<FileInfo*>::iterator it=listFiles.begin();
it!=listFiles.end();it++)
{
std::string nameIn=(*it)->getFileName();
char c[30];
itoa((*it)->getFileID(),c,10);
std::string nameOut=nameFile+"\\"+FileNameIdent+c+FileNameIdentExt;
std::cout<<"File: "<<nameIn<<" Compressed to :"<<nameOut<<std::endl;
file_in=fopen(nameIn.c_str(),"r+b");
file_out=fopen(nameOut.c_str(),"w+b");
int ret = def(file_in, file_out, Z_DEFAULT_COMPRESSION);
if (ret != Z_OK)
{
zerr(ret);
}
fclose(file_in);
fclose(file_out);
}
}
void DecompressFiles(std::vector<FileInfo*>& listFiles,const std::string& nameFile) { FILE *file_in; FILE *file_out;
for(std::vector<FileInfo*>::iterator it=listFiles.begin();
it!=listFiles.end();it++)
{
std::string nameOut=(*it)->getFileName();
char c[30];
itoa((*it)->getFileID(),c,10);
std::string nameIn=nameFile+"\\"+FileNameIdent+c+FileNameIdentExt;
std::cout<<"File: "<<nameIn<<" Extracted to :"<<nameOut<<std::endl;
file_in=fopen(nameIn.c_str(),"r+b");
file_out=fopen(nameOut.c_str(),"w+b");
int ret = inf(file_in, file_out);
if (ret != Z_OK)
{
zerr(ret);
}
fclose(file_in);
fclose(file_out);
}
} int main(int argc,char** argv) { if(argc<3) { std::cout«“Usage: xedicompress -P RelativePathSrc RelativePathDest”«std::endl; std::cout«“Usage: xedicompress -UP RelativePathDest”«std::endl; std::cout«“Relative path from this app."«std::endl; return 0; }
if (strcmp(argv[1], "-UP") == 0)
{
std::cout<<" Unpacking Files . . ."<<std::endl;
///path presult;
std::vector<FileInfo*>listFiles_2;
//read list of file from data.pak
ReadFiles(listFiles_2,argv[2]);
//after get list of files, then extract them.
DecompressFiles(listFiles_2,argv[2]);
}
else if (strcmp(argv[1], "-P") == 0)
{
std::cout<<" Packing Files . . ."<<std::endl;
path presult;
std::vector<FileInfo*>listFiles;
//get all files from specified path, save it into vector
GetAllFiles(argv[2],listFiles);
//save list of files in data.pak, as future reference for extracting
SaveFile(listFiles,argv[3]);
//compress all files
CompressFiles(listFiles,argv[3]);
}
else
{
std::cout<<"Usage: xedicompress -P RelativePathSrc RelativePathDest"<<std::endl;
std::cout<<"Usage: xedicompress -UP RelativePathDest"<<std::endl;
std::cout<<"Relative path from this app."<<std::endl;
return 0;
}
} [/sourcecode]
format penggunaan: untuk Packing : xedicompress -P RelativePathSrc RelativePathDest untuk Unpacking : xedicompress -UP RelativePathDest contoh: xedicompress -P ..\test ..\debug\data akan mengkompress seluruh file didalam folder test (dan subfolder di dalam test) ke folder \debug\data. Relative path terhadap folder dimana aplikasi berada. xedicompress -UP ..\debug\data Akan mendekompress seluruh file di debug\data ke folder asalnya.
#just_for_fun :-)
boost ver 1.42 zlib ver 1.2.5