#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 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; } pFileList->addFileNode(fullInfo); // Add file item to the list } return pFileList; } INPKFile* createNPKFile() { return new CNPKFile(); } void closeNPKFile(INPKFile* npkFile) { if (npkFile) { delete ((CNPKFile*)npkFile); npkFile = nullptr; } } }