From 1b0cf51c38b79931e91498e2c4253a2df858db94 Mon Sep 17 00:00:00 2001 From: Juno Kim Date: Mon, 31 Aug 2026 11:20:16 +0900 Subject: [PATCH] make class works. need to be tested --- MyZip.cpp | 207 ++++++++++++++++++++++++++++++---------------------- MyZip.h | 33 ++++++--- ZipDefine.h | 10 +-- main.cpp | 49 ++++++++----- 4 files changed, 180 insertions(+), 119 deletions(-) diff --git a/MyZip.cpp b/MyZip.cpp index 7ff2b85..bd42530 100644 --- a/MyZip.cpp +++ b/MyZip.cpp @@ -1,107 +1,86 @@ +#include +#include +#include #include "MyZip.h" - // Phase A(AddFile) / Phase B(Finalize) 구조: // - AddFile() : 파일 하나 읽어서 [LocalFileHeader][파일명][데이터]를 fileOut에 이어 쓰고, // 이 파일의 CentralDirectory 정보는 vecEntries에 누적만 해둠 (아직 안 씀). // - Finalize() : 전체 순회가 끝난 뒤 딱 한 번 호출. 누적된 CentralDirectory들을 순서대로 쓰고, // EOCD 작성 후 스트림을 닫음. -bool MyZip::AddFileToZip(const std::string& strFilePath) -{ - LocalFileHeader LFHeader{}; - // - strFilePath 파일을 열어 크기 계산 + CRC 계산 - // - LocalFileHeader 채워서 [헤더][파일명][데이터] 순서로 fileOut에 씀 - // - 이 파일의 CentralDirectory를 채워서 vecEntries에 push_back - // - 이 시점에는 CentralDirectory/EOCD를 파일에 쓰지 않음 (Finalize 몫) - // strFilePath : 디스크 상의 실제 파일 경로 (FileReader가 넘겨주는 경로) - // 3. LocalFileHeader 채우기 (signature/crc32/size/lengthOfFileName 등) - // 4. fileOut에 [LocalFileHeader][파일명][데이터] 쓰기 - // 5. 이 파일의 CentralDirectory 채워서 vecEntries.push_back({header, fileName}) +// Phase A +// - strFilePath 파일을 열어 크기 계산 + CRC 계산 +// - LocalFileHeader 채워서 [헤더][파일명][데이터] 순서로 fileOut에 씀 +// - 이 파일의 CentralDirectory를 채워서 vecEntries에 push_back +// - 이 시점에는 CentralDirectory/EOCD를 파일에 쓰지 않음 (Finalize 몫) +// strFilePath : 디스크 상의 실제 파일 경로 (FileReader가 넘겨주는 경로) +// 3. LocalFileHeader 채우기 (signature/crc32/size/lengthOfFileName 등) +// 4. fileOut에 [LocalFileHeader][파일명][데이터] 쓰기 +// 5. 이 파일의 CentralDirectory 채워서 vecEntries.push_back({header, fileName}) +bool MyZip::AddFileToZip(const std::string &strFilePath) +{ std::ifstream fileIn(strFilePath, std::ios::binary); - if (fileIn.is_open()) + if (!fileIn.is_open()) { return false; } // 파일 크기 계산 - fileIn.seekg(0, std::ios::end); // seekg (offset, 목표점) + fileIn.seekg(0, std::ios::end); // seekg (offset, 목표점) std::streamsize fileInSize = fileIn.tellg(); - fileIn.seekg(0, std::ios::beg); // beg == begin + fileIn.seekg(0, std::ios::beg); // beg == begin // 메모리 할당 std::vector fileInBuffer(fileInSize); // TODO : Buffering 으로 구현. 현재는 파일 통짜로 읽음. - auto* pInBuffer = reinterpret_cast(fileInBuffer.data()); + char *pInBuffer = reinterpret_cast(fileInBuffer.data()); - // Get CRC value + // 파일 읽기 + if (!fileIn.read(pInBuffer, fileInSize)) + { + return false; + } + + // Gathering data uint32_t myCRC = getCRC32(pInBuffer, fileInSize); - - // ZIP 포맷 채워넣기 - // Put PK Signature - LFHeader.signature = PK_SIGNATURE_LF_HEADER; - - // TOOD : Temp Format 채우기. - LFHeader.versionNeededToExtract = 0; // Temp - LFHeader.generalBitFlag = 0; // Temp - - // Put compressed method (uncompressed) - LFHeader.compressionMethod = METHOD_UNCOMPRESSED; - - // lastMode[Time|Date] => Not Used. - - // Put CRC value - LFHeader.crc32 = myCRC; - CentDir.crc32 = myCRC; - - // Put File size - LFHeader.sizeOfUncompressed = (uint32_t)fileInSize; - LFHeader.sizeOfCompressed = LFHeader.sizeOfUncompressed; - CentDir.sizeOfUncompressed = (uint32_t)fileInSize; - CentDir.sizeOfCompressed = CentDir.sizeOfUncompressed; - - // Put File name length uint16_t fileNameLeng = static_cast(strFilePath.length()); - LFHeader.lengthOfFileName = fileNameLeng; - CentDir.lengthOfFileName = fileNameLeng; + //////////////////////////////////////////////////////////////////////////////////////// + LocalFileHeader LFHeader{}; + CentralDirectory CentDir{}; + + // LocalFile Header + FillLocalFileHeader(LFHeader, myCRC, static_cast(fileInSize), fileNameLeng); + // CentralDirectory Header + CentralDirectory fiiledCDH = GetFilledCentDirHeader(CentDir, myCRC, static_cast(fileInSize), fileNameLeng); + // LocalFile Header 의 시작위치 + fiiledCDH.offsetLocalFileHeader = static_cast(fileOut.tellp()); - // Extra Field 는 사용하지 않음 - // LFHeader.lengthOfExtraField = NOT_USED; - // CentDir.lengthOfExtraField = NOT_USED; + // 우선 CDH 는 vector 에 저장 (나중에 파일 쓸 때 마저 채움.) + vecEntries.push_back({fiiledCDH, strFilePath}); - // TODO : CentDir Temp format 채우기. - CentDir.lengthOfFileComment = 0; - CentDir.diskNumStart = 0; // Temp - CentDir.attributeInternal = 0; // Temp - CentDir.attributeExternal = 0; // Temp - // CentDir.offsetLocalFileHeader -> 실제 파일 Write 중에 작성. - - // TODO : EOCDR Temp format 채우기. - EOCDR.diskNumber = 0; // Temp - EOCDR.diskNumber_CentralDirStart = 0; // Temp - EOCDR.numOfCentralDir = 1; - EOCDR.numOfEntries = 1; - EOCDR.sizeOfCentralDir = sizeof(CentDir) + CentDir.lengthOfFileName; - // EOCDR.offsetCentralDir -> 실제 파일 Write 중에 작성. - EOCDR.lengthOfCommentLength = 0; + // 1. Local File Header, file name (var), extra field (var) + fileOut.write(reinterpret_cast(&LFHeader), sizeof(LFHeader)); + fileOut.write(strFilePath.c_str(), LFHeader.lengthOfFileName); + // fileOut.write(NOT_USED, LFHeader.lengthOfExtraField); + // 2. File Data + fileOut.write(pInBuffer, fileInSize); // TODO : 성공여부 처리 어떻게 할 건지? - return false; + return true; } +// Phase B // - vecEntries를 순서대로 순회하며 [CentralDirectory][파일명] 씀 // - EoCDRecord 채우기 (numOfEntries = vecEntries.size() 등 누적값 반영) // - EoCDRecord 쓰기 -> fileOut.close() -// Zip arhcive 파일 쓰기 -// EOCD 채우기 +// TODO : 1. vecEntries 순서대로 [CentralDirectory][파일명] 쓰기 +// 2. EoCDRecord 채우기 (entries 수, central dir 시작 오프셋/크기 등) +// 3. EoCDRecord 쓰기 +// 4. fileOut.close() bool MyZip::FinalizeZip() { bool iRet = false; - // TODO : 1. vecEntries 순서대로 [CentralDirectory][파일명] 쓰기 - // 2. EoCDRecord 채우기 (entries 수, central dir 시작 오프셋/크기 등) - // 3. EoCDRecord 쓰기 - // 4. fileOut.close() - std::ofstream fileOut(OUTPUT_FILE_NAME, std::ios::binary); iRet = fileOut.is_open(); if (!iRet) { @@ -109,31 +88,87 @@ bool MyZip::FinalizeZip() } else { - // 1. Local File Header, file name (var), extra field (var) - CentDir.offsetLocalFileHeader = static_cast(fileOut.tellp()); // TODO : sizeof 의 값이 스펙과 정확히 일치한다는 보장 필요 (현재는 compiler 에 종속적) // : offsetof((LocalFileHeader, crc32) 으로 멤버별 static_assert 로 변경할 지 고민. // : 아니면 필드 단위 직접쓰기로 struct 분해하기? - fileOut.write(reinterpret_cast(&LFHeader), sizeof(LFHeader)); - fileOut.write(INPUT_FILE_NAME, LFHeader.lengthOfFileName); - //fileOut.write(NOT_USED, LFHeader.LFHeader.lengthOfExtraField); - // 2. File Data - fileOut.write(pInBuffer, fileInSize); + // TODO : EOCDR Temp format 채우기. + this->EOCDR.diskNumber = 0; // Temp + this->EOCDR.diskNumber_CentralDirStart = 0; // Temp + this->EOCDR.numOfCentralDir = static_cast(vecEntries.size()); + this->EOCDR.numOfEntries = static_cast(vecEntries.size()); + this->EOCDR.lengthOfCommentLength = 0; + for (const auto &p : vecEntries) + { + this->EOCDR.sizeOfCentralDir += (sizeof(p.header) + p.header.lengthOfFileName); + } + this->EOCDR.offsetCentralDir = static_cast(fileOut.tellp()); - // 3. CentralDirectory, file name (var) - EOCDR.offsetCentralDir = static_cast(fileOut.tellp()); - // TODO : sizeof 의 값이 스펙과 정확히 일치한다는 보장 필요 (현재는 compiler 에 종속적) - fileOut.write(reinterpret_cast(&CentDir), sizeof(CentDir)); - fileOut.write(INPUT_FILE_NAME, CentDir.lengthOfFileName); + // Write to file (archiving) + for (const auto &p : vecEntries) + { + // CentralDirectory, file name (var) + fileOut.write(reinterpret_cast(&p.header), sizeof(p.header)); + fileOut.write(p.fileName.c_str(), p.header.lengthOfFileName); + } - // 4. End of CentralDirectory Record - // TODO : sizeof 의 값이 스펙과 정확히 일치한다는 보장 필요 (현재는 compiler 에 종속적) - fileOut.write(reinterpret_cast(&EOCDR), sizeof(EOCDR)); + // End of CentralDirectory Record + fileOut.write(reinterpret_cast(&this->EOCDR), sizeof(this->EOCDR)); fileOut.close(); } // TODO : 성공여부 처리 어떻게 할 건지? - return false; + return iRet; +} + +void MyZip::FillLocalFileHeader(LocalFileHeader &targetLF, const uint32_t targetCRC, const uint32_t targetSize, const uint16_t targetNameLeng) +{ + // ZIP 포맷 채워넣기 + // Put PK Signature + targetLF.signature = PK_SIGNATURE_LF_HEADER; + + // TOOD : Temp Format 채우기. + targetLF.versionNeededToExtract = 0; // Temp + targetLF.generalBitFlag = 0; // Temp + + // Put compressed method (uncompressed) + targetLF.compressionMethod = METHOD_UNCOMPRESSED; + + // lastMode[Time|Date] => Not Used. + + // Put CRC value + targetLF.crc32 = targetCRC; + + // Put File size + targetLF.sizeOfUncompressed = targetSize; + targetLF.sizeOfCompressed = targetLF.sizeOfUncompressed; + + // Put File name length + targetLF.lengthOfFileName = targetNameLeng; + + // Extra Field 는 사용하지 않음 + // LFHeader.lengthOfExtraField = NOT_USED; +} + +CentralDirectory MyZip::GetFilledCentDirHeader(CentralDirectory &targetCDH, const uint32_t targetCRC, const uint32_t targetSize, const uint16_t targetNameLeng) +{ + targetCDH.crc32 = targetCRC; + targetCDH.sizeOfUncompressed = targetSize; + targetCDH.sizeOfCompressed = targetCDH.sizeOfUncompressed; + + // Put File name length + targetCDH.lengthOfFileName = targetNameLeng; + + // Extra Field 는 사용하지 않음 + // targetCDH.lengthOfExtraField = NOT_USED; + + // TODO : targetCDH Temp format 채우기. + targetCDH.lengthOfFileComment = 0; + targetCDH.diskNumStart = 0; // Temp + targetCDH.attributeInternal = 0; // Temp + targetCDH.attributeExternal = 0; // Temp + // targetCDH.offsetLocalFileHeader -> 실제 파일 Write 중에 작성. + + return targetCDH; } diff --git a/MyZip.h b/MyZip.h index b2fae68..112ab8a 100644 --- a/MyZip.h +++ b/MyZip.h @@ -8,6 +8,8 @@ #include "CRC.h" #define OUTPUT_FILE_NAME "MyZip.zip" +#define INPUT_FILE_NAME "Target_File.txt" +#define INPUT_DIR_NAME "Target_File" class MyZip { @@ -16,30 +18,43 @@ private: std::string strOutputPath; // Zip Format struct - CentralDirectory CentDir{}; EoCDRecord EOCDR{}; struct ArchivedEntry { CentralDirectory header; std::string fileName; + + // TODO : 아래 기본값 어디서 채울 지 고민 (GetFiiled or 여기서) + ArchivedEntry(CentralDirectory cdh, std::string name) : header(cdh), fileName(name) + { + header.signature = PK_SIGNATURE_CENT_DIR; + header.versionMadeBy = 0; // Temp + header.versionNeededToExtract = 0; // Temp + header.generalBitFlag = 0; // Temp + header.compressionMethod = METHOD_UNCOMPRESSED; + } }; std::vector vecEntries; public: - MyZip(std::string strfilePath) - : strOutputPath(strfilePath) + MyZip() : strOutputPath(std::string(OUTPUT_FILE_NAME)) + { + fileOut = std::ofstream(strOutputPath, std::ios::binary); + EOCDR.signature = PK_SIGNATURE_EO_CDR; + } + MyZip(const std::string& strfilePath) : strOutputPath(strfilePath) { fileOut = std::ofstream(strOutputPath, std::ios::binary); - CentDir.signature = PK_SIGNATURE_CENT_DIR; EOCDR.signature = PK_SIGNATURE_EO_CDR; - - CentDir.versionMadeBy = 0; // Temp - CentDir.versionNeededToExtract = 0; // Temp - CentDir.generalBitFlag = 0; // Temp - CentDir.compressionMethod = METHOD_UNCOMPRESSED; } + // Zip 에 파일 추가 bool AddFileToZip(const std::string &strFilePath); + // Zip 마무리 bool FinalizeZip(); + + // Header Functions + void FillLocalFileHeader(LocalFileHeader &targetLF, const uint32_t targetCRC, const uint32_t targetSize, const uint16_t targetNameLeng); + CentralDirectory GetFilledCentDirHeader(CentralDirectory &targetCDH, const uint32_t targetCRC, const uint32_t targetSize, const uint16_t targetNameLeng); }; diff --git a/ZipDefine.h b/ZipDefine.h index 639df64..d707c58 100644 --- a/ZipDefine.h +++ b/ZipDefine.h @@ -2,10 +2,10 @@ #include -const static uint32_t PK_SIGNATURE_LF_HEADER = 0x04034b50; // PK + 0x0403 -const static uint32_t PK_SIGNATURE_CENT_DIR = 0x02014b50; // PK + 0x0201 -const static uint32_t PK_SIGNATURE_EO_CDR = 0x06054b50; // PK + 0x0605 -const static uint16_t METHOD_UNCOMPRESSED = 0; +constexpr uint32_t PK_SIGNATURE_LF_HEADER = 0x04034b50; // PK + 0x0403 +constexpr uint32_t PK_SIGNATURE_CENT_DIR = 0x02014b50; // PK + 0x0201 +constexpr uint32_t PK_SIGNATURE_EO_CDR = 0x06054b50; // PK + 0x0605 +constexpr uint16_t METHOD_UNCOMPRESSED = 0; /** Overall .ZIP file format: @@ -186,6 +186,6 @@ constexpr int LOCAL_FILE_HEADER_BYTE = 30; }; // 정적 메모리 사이즈 검사 - static_assert(sizeof(EoCDRecord) == EOCD_BYTE); + static_assert(sizeof(EoCDRecord) == EOCD_BYTE, "EoCDRecord Size is mismatch"); #pragma pack(pop) diff --git a/main.cpp b/main.cpp index 21f3368..4e8b2b4 100644 --- a/main.cpp +++ b/main.cpp @@ -14,19 +14,22 @@ static uint32_t g_recursive_counter = 0; -bool ZipArchiver(std::string strInputPath) +bool ZipArchiver(std::string strInputPath, bool bIsFinal = false) { - MyZip myZIP(strInputPath); + MyZip zip; + bool bRet = false; - myZIP.AddFileToZip(); + bRet = zip.AddFileToZip(strInputPath); + if(bIsFinal) + bRet = zip.FinalizeZip(); - return false; + return bRet; } bool FileReader(std::string strInputPath) { using namespace std; - bool iRet = true; + bool bRet = true; // path class obj 생성. filesystem::path pathObj(strInputPath); @@ -37,7 +40,8 @@ bool FileReader(std::string strInputPath) string strDbg = "[ERROR] File not found : " + pathObj.string(); strDbg += "\r\n"; OutputDebugStringA(strDbg.c_str()); - iRet = false; + + bRet = false; } enum class eFILETYPE { @@ -50,12 +54,14 @@ bool FileReader(std::string strInputPath) else if (filesystem::is_regular_file(pathObj)) eFileDist = eFILETYPE::isFile; else eFileDist = eFILETYPE::ETC; + const filesystem::path& rootPath = pathObj; switch (eFileDist) { // case 1 : input file case eFILETYPE::isFile: { - iRet = ZipArchiver(pathObj.string()); + bool bIsFinal = g_recursive_counter > 0 ? false : true; + bRet = ZipArchiver(pathObj.string(), bIsFinal ); break; } @@ -68,37 +74,37 @@ bool FileReader(std::string strInputPath) { // get entry one by one const filesystem::directory_entry& currentEntry = *itrDir; - const filesystem::path& currentPath = currentEntry.path(); + const filesystem::path& currentAbsPath = currentEntry.path(); if (filesystem::is_directory(currentEntry)) { + // 있으면 좋을거같아서 만든 변수 "g_recursive_counter" g_recursive_counter++; // TODO : export to debugging log function - string strDbg = "[INFO] Current Entry (Dir)\t: " + currentPath.string(); + string strDbg = "[INFO] Current Entry (Dir)\t: " + currentAbsPath.string(); strDbg += "\t Counter : " + to_string(g_recursive_counter); strDbg += "\r\n"; OutputDebugStringA(strDbg.c_str()); // recursive traversal - iRet = FileReader(currentPath.string()); + bRet = FileReader(currentAbsPath.string()); g_recursive_counter--; } else if (filesystem::is_regular_file(currentEntry)) { // TODO : export to debugging log function - string strDbg = "[INFO] Current Entry (File)\t: " + currentPath.string(); + string strDbg = "[INFO] Current Entry (File)\t: " + currentAbsPath.string(); strDbg += "\t Counter : " + to_string(g_recursive_counter); strDbg += "\r\n"; - OutputDebugStringA(strDbg.c_str()); // archiving file - iRet = ZipArchiver(currentPath.string()); + auto relativePath = filesystem::relative(currentAbsPath, rootPath); + bRet = ZipArchiver(relativePath.string()); } else { - // TODO : 근데 애초에 제 3의 Type 이 있나? (여기 의미 있는건가?) OutputDebugStringA("[ERROR] Wrong Type of File"); - iRet = false; + bRet = false; } // step forward entry @@ -114,24 +120,29 @@ bool FileReader(std::string strInputPath) string strErr = "[ERROR] Something went Wrong..!!"; OutputDebugStringA(strErr.c_str()); - iRet = false; + bRet = false; break; } default: break; }; + if(bRet) + { + bRet = ZipArchiver(strInputPath, true); + } + // TODO : 실패 상황 별 다른 코드 부여 + 코드 별 처리 어떻게 할 건지? - return iRet; + return bRet; } -int main() +int main(int argc, char* argv[]) { std::printf("==================== Hello Zip! ====================\r\n"); int iRet = 0; // Main Zip archive method - if (!FileReader(INPUT_FILE_NAME)) + if (!FileReader(std::string(INPUT_FILE_NAME))) { iRet = -1; std::printf("[ ERROR ] Somthing went Wrong... \r\n");