Avatar 引擎可以设置 zip 压缩文件作为引擎运行的资源目录,当指定资源目录为 zip 压缩包时,读取指定的文件也就自动转换为压缩包文件读取。而写文件则不以 zip 压缩包为目标写入,文件管理器在写文件时默认以 zip 文件所在目录作为当前目录进行写文件操作,这样当设置当前目录为 zip 文件时,读取和写入的位置是不一样的。
先来看看文件管理器部分相关方法
/**
* 设置资源目录
* @param directory 当前资源目录,可以设定为 ZIP 文件路径
* @note 当设定为 ZIP 文件时,GetDataDirectory 返回 ZIP 文件所在目录,另外可以设置解压密码(例如 C:\a.zip?password)
*/
void CFileManager::SetDataDirectory(const string& directory) {
m_strCurrentDirectory = directory;
// 销毁 ZIP 读取接口
if (m_pZipReader) {
delete m_pZipReader;
m_pZipReader = 0;
}
// directory 为 ZIP 文件
if (directory.find(".zip") != directory.npos) {
string filepath = directory;
string password = "";
size_t passIndex = directory.find_last_of('?');
if (passIndex != directory.npos) {
password = directory.substr(passIndex + 1);
filepath = directory.substr(0, passIndex);
}
m_pZipReader = new CZipReader(filepath, password);
m_strCurrentDirectory = GetDirectory(filepath);
if (m_strCurrentDirectory.length() > 0) {
m_strCurrentDirectory += "/";
}
}
// directory 为普通路径
if (!m_pZipReader && directory.length() > 0) {
char lastLetter = directory.at(directory.length() - 1);
if (lastLetter != '/' && lastLetter != '\\') {
m_strCurrentDirectory += "/";
}
}
}
文件读取
/**
* 读取文件
* @param filename 读取的文件名
* @param file 文件对象指针
* @return 读取成功返回 true
*/
bool CFileManager::ReadFile(const string& filename, CFile* file) {
unsigned int size = 0;
unsigned char* data = 0;
// 清除文件原内容
if (file->contents) {
delete[] file->contents;
file->contents = 0;
file->size = 0;
}
// 如果设定当前目录为压缩文件,则从压缩包中读取
if (m_pZipReader) {
if (!m_pZipReader->Open(filename)) return false;
size = m_pZipReader->Size();
data = new unsigned char[size];
m_pZipReader->Read(data, size);
m_pZipReader->Close();
} else {
// 计算完整路径
string fullpath = filename;
if (!IsFullPath(filename)) {
fullpath = m_strCurrentDirectory + filename;
}
// 读取文件内容
ifstream fin(fullpath.c_str(), ios::in | ios::ate | ios::binary);
if (!fin.is_open()) return false;
size = (unsigned int)fin.tellg();
data = new unsigned char[size];
fin.seekg(0, ios::beg);
fin.read((char*)data, size);
fin.close();
}
// 根据不同的文件类型进行解析
bool ret = false;
switch (file->type) {
case BIN: ret = ParseBinFile(file, data, size); break;
case TXT: ret = ParseTxtFile(file, data, size); break;
case BMP: ret = ParseBmpFile(file, data, size); break;
case TGA: ret = ParseTgaFile(file, data, size); break;
case PNG: ret = ParsePngFile(file, data, size); break;
case JPG: ret = ParseJpgFile(file, data, size); break;
case WAV: ret = ParseWavFile(file, data, size); break;
case MP3: ret = ParseMp3File(file, data, size); break;
}
delete[] data;
return ret;
}
文件写入
/**
* 写入文件
* @param filename 写入的文件名
* @param file 文件对象指针
* @return 写入成功返回 true
*/
bool CFileManager::WriteFile(const string& filename, CFile* file) {
unsigned int size = 0;
unsigned char* data = 0;
// 检查文件内容是否为空
if (!file->contents) {
return false;
}
// 根据不同的文件类型进行序列化
bool ret = false;
switch (file->type) {
case BIN: ret = SerializeBinFile(file, &data, &size); break;
case TXT: ret = SerializeTxtFile(file, &data, &size); break;
case BMP: ret = SerializeBmpFile(file, &data, &size); break;
case TGA: ret = SerializeTgaFile(file, &data, &size); break;
case PNG: ret = SerializePngFile(file, &data, &size); break;
case JPG: ret = SerializeJpgFile(file, &data, &size); break;
case WAV: ret = SerializeWavFile(file, &data, &size); break;
case MP3: ret = SerializeMp3File(file, &data, &size); break;
}
if (!ret) return false;
// 计算完整路径
string fullpath = filename;
if (!IsFullPath(filename)) {
fullpath = m_strCurrentDirectory + filename;
}
// 写入文件内容
ofstream fout(fullpath.c_str(), ios::out | ios::binary | ios::trunc);
if (fout.is_open()) {
fout.write((char*)data, size);
fout.close();
delete[] data;
return true;
}
delete[] data;
return false;
}
以上代码解释了 Avatar 引擎的文件管理器对文件的读取和写入的操作方式。
好了,现在我们回过来看看 zip 文件读取类(CZipReader)的定义,注意其中用到了 zlib 开源库。
//================================================
// Copyright (c) 2016 周仁锋. All rights reserved.
// ye_luo@qq.com
//================================================
#ifndef _CZIPREADER_H_
#define _CZIPREADER_H_
#include "thirdparty/zlib/zlib.h"
#include "thirdparty/zlib/unzip.h"
#include <string>
using namespace std;
/**
* ZIP 文件读取类
*/
class CZipReader {
public:
CZipReader(const string& path, const string& password);
~CZipReader();
public:
//! 打开压缩包中的指定文件
bool Open(const string& file);
//! 关闭上次打开的文件
void Close();
//! 获取当前打开的文件大小
unsigned int Size();
//! 读取当前打开的文件
unsigned int Read(unsigned char* buff, unsigned int size);
private:
//! 解压句柄
unzFile m_pZipFile;
//! 打开的文件信息
unz_file_info64 m_sFileInfo;
//! 压缩包解压密码
string m_strPassword;
};
#endif
CZipReader实现代码
//================================================
// Copyright (c) 2016 周仁锋. All rights reserved.
// ye_luo@qq.com
//================================================
#include "CZipReader.h"
#include "CLog.h"
#include "Configure.h"
#include "thirdparty/zlib/unzip.c"
#include "thirdparty/zlib/ioapi.c"
#include <string.h>
#ifdef AVATAR_WINDOWS
#pragma comment(lib, "thirdparty/zlib/zlib.lib")
#endif
/**
* 构造函数,指定压缩包路径和解压密码
*/
CZipReader::CZipReader(const string& path, const string& password) {
m_strPassword = password;
m_pZipFile = unzOpen64(path.c_str());
if (!m_pZipFile) {
CLog::Error("Open zip file '%s' error", path.c_str());
}
}
/**
* 析构函数
*/
CZipReader::~CZipReader() {
if (m_pZipFile) {
unzClose(m_pZipFile);
}
}
/**
* 打开压缩包中的指定文件
*/
bool CZipReader::Open(const string& file) {
memset(&m_sFileInfo, 0, sizeof(unz_file_info64));
if (!m_pZipFile) return false;
if (unzLocateFile(m_pZipFile, file.c_str(), 0) != UNZ_OK) return false;
if (unzGetCurrentFileInfo64(m_pZipFile, &m_sFileInfo, 0, 0, 0, 0, 0, 0) == UNZ_OK) {
const char* password = 0;
if (m_strPassword.length()) {
password = m_strPassword.c_str();
}
if (unzOpenCurrentFilePassword(m_pZipFile, password) == UNZ_OK) {
return true;
}
}
return false;
}
/**
* 关闭上次打开的文件
*/
void CZipReader::Close() {
memset(&m_sFileInfo, 0, sizeof(unz_file_info64));
if (m_pZipFile) {
unzCloseCurrentFile(m_pZipFile);
}
}
/**
* 获取当前打开的文件大小
*/
unsigned int CZipReader::Size() {
if (m_pZipFile) {
return (unsigned int)m_sFileInfo.uncompressed_size;
}
return 0;
}
/**
* 读取当前打开的文件
*/
unsigned int CZipReader::Read(unsigned char* buff, unsigned int size) {
if (m_pZipFile) {
return unzReadCurrentFile(m_pZipFile, buff, size);
}
return 0;
}
好了,到这里基本上完结了,但我在使用时发现了一个问题,在读取加密的文件时总是失败,通过查看 zlib 库的 unzip.c 源文件发现定义了 NOUNCRYPT 这个宏,注释掉就没问题了。