#include "libNPK_StdAfx.h" #include "libNPK_NPKFile.h" #include "libNPK_Errors.h" #include "libEncrypt_SHA256.h" #include "libNPK_FileStream.h" #include 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() { } bool CNPKFile::openPakFile(const char* szPackFileName) { assert(szPackFileName); if (!szPackFileName || szPackFileName[0] == 0) { setLastError(NPK_ERR_PARAM); return false; } //close current pak file if it is open closePakFile(); m_strPackFileName = szPackFileName; //open the pak file m_hPakFile = ::CreateFile(szPackFileName, 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", szPackFileName, ::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", szPackFileName, 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].fileName = 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 FileNode& fileNode = m_fileList[i]; m_hashFileList[fileNode.fileName] = 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::getFileNode(int32_t index, INPKFile::FileNode& fileNode) const { if (index < 0 || index >= getFileCount()) { return false; } fileNode = 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 >= getFileCount()) { setLastError(NPK_ERR_FILE_NOT_FOUND, "File=%s, FileIndex=%d, not found in NPK file", szFileName, fileIndex); return nullptr; } FileNode& fileNode = m_fileList[fileIndex]; CFileStream* pFileStream = new CFileStream(this, m_hPakFile, fileNode); return pFileStream; } void CNPKFile::closeFileStream(IFileStream* fileStream) { if (fileStream) { delete ((CFileStream*)fileStream); } } INPKFile* createNPKFile() { return new CNPKFile(); } void closeNPKFile(INPKFile* npkFile) { if (npkFile) { delete ((CNPKFile*)npkFile); npkFile = nullptr; } } }