Files
dnf-utils/libnpk/source/libNPK_NPKFile.cpp
T
2025-09-04 14:30:09 +08:00

334 lines
7.7 KiB
C++

#include "libNPK_StdAfx.h"
#include "libNPK_NPKFile.h"
#include "libNPK_Errors.h"
#include "libEncrypt_SHA256.h"
#include "libNPK_FileStream.h"
#include "libNPK_Utils.h"
#include "libNPK_FileList.h"
#include <assert.h>
namespace NPK
{
const char* CNPKFile::kNPKFileHeaderMagic = "NeoplePack_Bill\0";
const char* CNPKFile::kNPKFileNameXorCode = "puchikon@neople dungeon and fighter DNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNF";
CNPKFile::CNPKFile()
: m_hPakFile(NULL)
, m_nFileCounts(0)
{
}
CNPKFile::~CNPKFile()
{
if (m_hPakFile != NULL)
{
::CloseHandle(m_hPakFile);
m_hPakFile = NULL;
}
m_nFileCounts = 0;
m_fileList.clear();
m_hashFileList.clear();
}
bool CNPKFile::openPakFile(const wchar_t* wszNpkPathName)
{
assert(wszNpkPathName);
if (!wszNpkPathName || wszNpkPathName[0] == 0)
{
setLastError(NPK_ERR_PARAM);
return false;
}
//close current pak file if it is open
closePakFile();
wchar_t wszNpkBaseName[1024] = { 0 };
StringCchCopyW(wszNpkBaseName, 1024, wszNpkPathName);
PathRemoveFileSpecW(wszNpkBaseName);
m_strPackBaseName = convertWideStringToUtf8(wszNpkBaseName);
m_strPackFileName = convertWideStringToUtf8(::PathFindFileNameW(wszNpkPathName));
//open the pak file
m_hPakFile = ::CreateFileW(wszNpkPathName,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
0,
OPEN_EXISTING,
FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_SEQUENTIAL_SCAN,
0);
if (m_hPakFile == INVALID_HANDLE_VALUE)
{
setLastError(NPK_ERR_FILE_ACCESS, "File=%s, WinErr=%d",
convertWideStringToUtf8(wszNpkPathName).c_str(), ::GetLastError());
return false;
}
NPKFileHeader fileHeader;
SecureZeroMemory(&fileHeader, sizeof(fileHeader));
//Read NPK Header
DWORD dwReadBytes;
if (!ReadFile(m_hPakFile, &fileHeader, sizeof(fileHeader), &dwReadBytes, 0) ||
dwReadBytes != sizeof(fileHeader))
{
setLastError(NPK_ERR_FILE_READ, "WinErr=%d", ::GetLastError());
return false;
}
//Check NPK Magic Header
if(memcmp(fileHeader.szFileHeadMagic, kNPKFileHeaderMagic, kNPKFileHeaderMagicSize) != 0)
{
setLastError(NPK_ERR_INVALID_FILE_FORMAT, "File=%s, Magic=%s",
convertWideStringToUtf8(wszNpkPathName).c_str(), fileHeader.szFileHeadMagic);
return false;
}
//get file counts
m_nFileCounts = fileHeader.nFileCounts;
m_fileList.resize(m_nFileCounts);
// read file info
FileInfo fileInfo;
for (int32_t i = 0; i < m_nFileCounts; i++)
{
SecureZeroMemory(&fileInfo, sizeof(fileInfo));
if (!ReadFile(m_hPakFile, &fileInfo, sizeof(fileInfo), &dwReadBytes, 0) ||
dwReadBytes != sizeof(fileInfo))
{
setLastError(NPK_ERR_FILE_READ, "WinErr=%d", ::GetLastError());
return false;
}
//decode file name
for(int32_t j = 0; j < kNPKFileNameXorCodeSize; j++)
{
fileInfo.szFileName[j] ^= kNPKFileNameXorCode[j];
}
m_fileList[i].keyName = fileInfo.szFileName;
m_fileList[i].fileOffset = fileInfo.fileOffset;
m_fileList[i].fileSize = fileInfo.fileSize;
}
if(!checkPakFileHash())
{
// if hash check failed, close the pak file
closePakFile();
return false;
}
//build hash file list
buildHashFileList();
return true;
}
bool CNPKFile::checkPakFileHash()
{
if(m_hPakFile == NULL)
{
setLastError(NPK_ERR_INVALID_STATE, "NPK file is not opened");
return false;
}
//calculate hash
int32_t headSize = sizeof(NPKFileHeader) + sizeof(FileInfo) * m_nFileCounts;
int32_t hashSize = (int32_t)(headSize / 17) * 17; // align to 17 bytes
// allocate hash buffer
uint8_t* hashBuffer = new uint8_t[hashSize];
SecureZeroMemory(hashBuffer, hashSize);
// copy header and file info to hash buffer
DWORD dwReadBytes = 0;
if (0 != ::SetFilePointer(m_hPakFile, 0, 0, FILE_BEGIN) ||
!ReadFile(m_hPakFile, hashBuffer, hashSize, &dwReadBytes, 0) ||
dwReadBytes != hashSize)
{
delete[] hashBuffer; hashBuffer = nullptr;
setLastError(NPK_ERR_FILE_READ, "WinErr=%d", ::GetLastError());
return false;
}
// calculate hash
Sha256Digest calculatedHash;
sha256Calculate(hashBuffer, hashSize, calculatedHash);
delete[] hashBuffer; hashBuffer = nullptr;
//read hash data
if (headSize != SetFilePointer(m_hPakFile, headSize, 0, FILE_BEGIN))
{
setLastError(NPK_ERR_FILE_READ, "WinErr=%d", ::GetLastError());
return false;
}
Sha256Digest readedHashData;
if (!ReadFile(m_hPakFile, readedHashData.data(), readedHashData.max_size(), &dwReadBytes, 0) ||
dwReadBytes != readedHashData.max_size())
{
setLastError(NPK_ERR_FILE_READ, "WinErr=%d", ::GetLastError());
return false;
}
if (calculatedHash != readedHashData)
{
setLastError(NPK_ERR_INVALID_FILE_HASH);
return false;
}
return true;
}
void CNPKFile::buildHashFileList()
{
if (m_hPakFile == NULL)
{
setLastError(NPK_ERR_INVALID_STATE, "NPK file is not opened");
return;
}
m_hashFileList.clear();
for (int32_t i = 0; i < m_nFileCounts; i++)
{
const FileItemBaseInfo& baseInfo = m_fileList[i];
m_hashFileList[baseInfo.keyName] = i;
}
}
bool CNPKFile::closePakFile(void)
{
//Close handle
if (m_hPakFile)
{
CloseHandle(m_hPakFile); m_hPakFile = 0;
}
m_fileList.clear();
m_nFileCounts = 0;
return true;
}
bool CNPKFile::getFileItemBaseInfo(int32_t index, FileItemBaseInfo* baseInfo) const
{
if (index < 0 || index >= getFileCounts())
{
return false;
}
*baseInfo = m_fileList[index];
return true;
}
IFileStream* CNPKFile::openFileStream(const char* szFileName)
{
assert(szFileName && m_hPakFile);
if (!szFileName || szFileName[0] == 0)
{
setLastError(NPK_ERR_PARAM);
return nullptr;
}
if(m_hPakFile == NULL)
{
setLastError(NPK_ERR_INVALID_STATE, "NPK file is not opened");
return nullptr;
}
int32_t fileIndex = -1;
auto it = m_hashFileList.find(szFileName);
if(it==m_hashFileList.end())
{
setLastError(NPK_ERR_FILE_NOT_FOUND, "File=%s not found in NPK file", szFileName);
return nullptr;
}
else
{
fileIndex = it->second;
}
if (fileIndex < 0 || fileIndex >= getFileCounts())
{
setLastError(NPK_ERR_FILE_NOT_FOUND, "File=%s, FileIndex=%d, not found in NPK file", szFileName, fileIndex);
return nullptr;
}
FileItemBaseInfo& fileNode = m_fileList[fileIndex];
CFileStream* pFileStream = new CFileStream(this, m_hPakFile, fileNode);
return pFileStream;
}
void CNPKFile::closeFileStream(IFileStream* fileStream)
{
if (fileStream)
{
delete ((CFileStream*)fileStream);
}
}
IFileList* CNPKFile::generateFullInfoList(void)
{
if (m_hPakFile == NULL)
{
setLastError(NPK_ERR_INVALID_STATE, "NPK file is not opened");
return nullptr;
}
IFileList* pFileList = createEmptyFileList();
if (!pFileList)
{
setLastError(NPK_ERR_NEW_FAILED, "Failed to create empty file list");
return nullptr;
}
for (int32_t i = 0; i < m_nFileCounts; i++)
{
const FileItemBaseInfo& baseInfo = m_fileList[i];
FileItemFullInfo fullInfo;
fullInfo.keyName = baseInfo.keyName;
fullInfo.npkName = m_strPackFileName;
fullInfo.npkBase = m_strPackBaseName;
fullInfo.fileSize = baseInfo.fileSize;
fullInfo.fileOffset = baseInfo.fileOffset;
IFileStream* pFileStream = openFileStream(baseInfo.keyName.c_str());
if (pFileStream)
{
fullInfo.fileCRC = pFileStream->crc32();
closeFileStream(pFileStream);
pFileStream = nullptr;
}
else
{
fullInfo.fileCRC = 0;
}
// Add file item to the list
if (!((NPKFileList*)pFileList)->addFileNode(fullInfo, false))
{
//duplicate key name, error!
closeFileStream(pFileStream);
destoryFileList(pFileList);
setLastError(NPK_ERR_INVALID_FILE_FORMAT, "Duplicate keyname in NPK file");
return nullptr;
}
}
return pFileList;
}
INPKFile* createNPKFile()
{
return new CNPKFile();
}
void closeNPKFile(INPKFile* npkFile)
{
if (npkFile)
{
delete ((CNPKFile*)npkFile);
npkFile = nullptr;
}
}
}