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)

// test: packing-unpacking files
// @ edi ermawan , 20 Sept 2011
#include <iostream>
#include <vector>
#include <string>
#include "boost/filesystem.hpp"

#include <fstream>
#include "infdef.h"

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;
	}
}

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

Advertisement