打包的定义
什么是打包?打包这个词很形象,就是把零碎的文件进行统一重封装,统一管理,比如我们常见的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;
}