打包的定义
什么是打包?打包这个词很形象,就是把零碎的文件进行统一重封装,统一管理,比如我们常见的RAR文件,ZIP文件都是很常见的包裹格式
打包的意义
- 比如RAR包,我们虽然能有工具解压,但是我们却基本上没有相关的SDK来做二次开发
- ZIP包虽然有SDK来读取,但是对于通用的文件格式,我们无法做到保护资源的需求
- 如果只是为了文件管理的方便,无所谓别人解开资源的话,直接用现成的ZIP开发的SDK即可
打包的方式
- 分类打包
比如图片资源打一个包,声音资源打一个包 - 全部打包
把所有资源一起打包
打包的一般准则和规范
- 原始文件的标识,这个标识可以使原始文件名+路径名,或者也可以是转换后的数据如ID等,先从最简单的说起,使用原始文件名+路径名
- 原始文件的大小,把文件打进包裹之后,我们要知道这个原始文件有多大
- 原始文件的数据打包在包裹的什么位置
打包程序的实现框架
- 包裹文件的定义
- 添加CreatPackage(创建空白包裹)函数、AddFileToPackage(添加一个文件到包裹)函数
- CreatePackage(创建空白包裹)函数的实现
- AddFileToPackage(添加一个文件到包裹)函数的实现
package.h
#pragma once #include <cstdio> #include <vector> /* * 根据分析得知,我们需要将多个文件打包一个文件中,这样我们这个文件相对于整个打包的文件的一些信息 * 当前只定义部分信息 */ struct PackageItem { char fileName[256]; //文件名 size_t filesize; //文件的大小 size_t pos; //文件在整个打包文件的偏移位置 }; /* * 定义一个打包文件 * 首先,打包文件最终依旧是一个文件,在这个类中,我们希望一个对象维护一个打包文件, * 这样就需要这个对象持有一个文件指针,又因为一个打包文件不止一个文件,这样就需要用一个容器来存放多个文件 */ class Package { public: Package() :m_fpPackage(nullptr) { } ~Package(); public: //创建一个包裹 bool CreatePackage(const char* szFileName); //向一个包裹中添加一个文件 bool AddFileToPackage(const char* szFileName); private: FILE* m_fpPackage; std::vector<PackageItem> m_packageItems; };
package.cpp
#include "stdafx.h" #include "Package.h" Package::~Package() { fclose(m_fpPackage); m_fpPackage = nullptr; } bool Package::CreatePackage(const char *szFileName) { //首先判断包裹是否已经被打开,如果已经打开就直接返回 if (m_fpPackage) return false; //判断文件名 if (!szFileName) return false; //到这里,说明当前对象还没有创建文件,开始创建文件,由于打包的文件是二进制的,这个地方使用"wb"的方式打开 m_fpPackage = fopen(szFileName, "wb"); if (!m_fpPackage) { fprintf(stderr, "fopen %s error : %s\n", szFileName, strerror(errno)); return false; } return true; } bool Package::AddFileToPackage(const char * szFileName) { //由于这个地方要添加一个文件到包裹中,先以只读的方式打开文件"rb" FILE* fp = fopen(szFileName,"rb"); if (!fp) { fprintf(stderr, "fopen %s error : %s\n", szFileName, strerror(errno)); return false; } //获取要添加的文件大小 fseek(fp, 0, SEEK_END); size_t szFileSize = ftell(fp); //由于我们将文件指针移动到了文件的末尾,此时文件被没有写入到包裹中,需要再将文件指针移动到文件的头 rewind(fp); //fseek(fp, 0, SEEK_SET); //定义一个文件对象,把文件的信息放进去 //由于PackageItem是没有虚函数的类,可以使用memset,如果有虚函数,不能使用memset,会影响虚函数表函数指针的挂接 //关于文件相对于整个包裹的位置,只需要通过获取包裹的文件指针位置就可以获取到 PackageItem item; memset(&item, 0, sizeof(item)); strncpy(item.fileName, szFileName, strlen(szFileName)); item.filesize = szFileSize; item.pos = ftell(m_fpPackage); //将文件信息写入包裹中 size_t nWrite; if ((nWrite = fwrite(&item, 1, sizeof(item), m_fpPackage)) != sizeof(item)) { fprintf(stderr, "文件头部 %s 信息写入失败!\n", szFileName); return false; } //写入文件信息 //由于是按照指定的大小写入文件,需要循环写入文件 size_t nRead; char buff[65536]; while (true) { nRead = fread(buff, 1, sizeof(buff), fp); fwrite(buff, 1, nRead, m_fpPackage); if (nRead < sizeof(buff)) break; } //由于我们使用的是文件流,文件流在内存中也是一份缓存,需要将缓存写入到硬件中 fflush(m_fpPackage); fclose(fp); return true; }
解包程序的实现框架
- 新建一个CPackageLoader类(这个类负责解出Package1打包的文件)
- 在CPackageLaoder类中添加几个接口
- 实现打开包裹文件的OpenPackage函数
- 实现得到包裹里面有多少个PackageItemq结构的GetPackageItemCount函数
- 实现得到打包文件的信息的GetPackageItem函数
- 实现导出包裹文件并保存到szTargetName的ExportPackageItem函数
- 测试Package2解包程序
PackageLoader.h
#pragma once #include <vector> #include <cstdio> struct PackageItem { char fileName[256]; //文件名 size_t filesize; //文件的大小 size_t pos; //文件在整个打包文件的偏移位置 }; class PackageLoader { public: PackageLoader() :m_fpPackage(nullptr) { } ~PackageLoader() { fclose(m_fpPackage); m_fpPackage = nullptr; } public: //打开一个包裹 bool OpenPackage(const char* szFileName); //获取一个包裹中的文件数 const size_t GetPackageItemCount()const; const PackageItem* GetPackageItems(size_t index)const; //导出包裹中的文件 bool ExportPackageItems(const PackageItem *p, const char* szFileName); private: FILE* m_fpPackage; std::vector<PackageItem> m_packageItems; };
PackageLoader.cpp
#include "stdafx.h" #include "PackageLoader.h" bool PackageLoader::OpenPackage(const char * szFileName) { if (m_fpPackage) return false; m_fpPackage = fopen(szFileName, "rb"); if (!m_fpPackage) { fprintf(stderr, "fopen %s error %s\n", szFileName, strerror(errno)); return false; } while (1) { PackageItem item; memset(&item, 0, sizeof(PackageItem)); if (fread(&item, 1, sizeof(PackageItem), m_fpPackage) != sizeof(PackageItem)) { break; } m_packageItems.push_back(item); fseek(m_fpPackage, item.filesize, SEEK_CUR); } return true; } const size_t PackageLoader::GetPackageItemCount()const { return m_packageItems.size(); } const PackageItem * PackageLoader::GetPackageItems(size_t index)const { if (index < 0 && index >= m_packageItems.size()) return nullptr; return &m_packageItems[index]; } bool PackageLoader::ExportPackageItems(const PackageItem * pItem, const char * szFileName) { FILE* fp = fopen(szFileName, "wb"); if (!fp) { fprintf(stderr, "fopen %s error %s\n", szFileName, strerror(errno)); return false; } fseek(m_fpPackage, pItem->pos + sizeof(*pItem), SEEK_SET); char szBuffer[65536]; int leftSize = pItem->filesize; while (1) { int readSize = leftSize; if (readSize > sizeof(szBuffer)) { readSize = sizeof(szBuffer); } int nReadBytes = fread(szBuffer, 1, readSize, m_fpPackage); assert(nReadBytes == readSize); fwrite(szBuffer, 1, nReadBytes, fp); leftSize -= nReadBytes; if (leftSize == 0) { break; } } fclose(fp); return true; }