#pragma once // EterPack — reader/writer for this m2dev fork's single-file asset pack // (PackLib/Pack.cpp). NOT the classic Metin2 .eix/.epk format. // // Layout: // [Header: u64 entry_num, u64 data_begin, u8 nonce[24]] (40 bytes) // [Entry x entry_num] each XChaCha20-encrypted with Header.nonce + PACK_KEY // Entry: char name[NAME_FIELD], u64 offset, u64 file_size, // u64 compressed_size, u8 encryption, u8 nonce[24] // [data blob @ data_begin] // per file @ data_begin+offset, compressed_size bytes: // encryption==0 -> zstd frame -> zstd-decompress to file_size // encryption==1 -> XChaCha20(entry.nonce)-> zstd-decompress to file_size // // NAME_FIELD is FILENAME_MAX+1 on the packer's platform (MSVC PackMaker = 261). // The reader derives it from (data_begin - 40) / entry_num, falling back to the // known platform values, so it stays compatible with real packs. #include #include #include #include namespace mtpack { inline constexpr int PACK_NONCE_SIZE = 24; inline constexpr int PACK_KEY_SIZE = 32; // PackLib/config.h PACK_KEY — the fork ships this hardcoded. inline constexpr uint8_t PACK_KEY[PACK_KEY_SIZE] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10 }; // MSVC PackMaker.exe: FILENAME_MAX == 260, so the name field is 261 bytes. inline constexpr int PACK_NAME_FIELD_DEFAULT = 261; struct Entry { std::string name; uint64_t offset = 0; uint64_t file_size = 0; uint64_t compressed_size = 0; uint8_t encryption = 0; uint8_t nonce[PACK_NONCE_SIZE] = {}; }; class EterPack { public: // Load the index (mmaps the file; keeps it open for read()). bool open(const std::string &path, std::string *err = nullptr); void close(); bool has(const std::string &name) const { return m_by_name.count(norm(name)) != 0; } std::vector names() const; size_t count() const { return m_index.size(); } // Decompress (+decrypt) one file into `out`. bool read(const std::string &name, std::vector &out, std::string *err = nullptr) const; int name_field() const { return m_name_field; } private: static std::string norm(std::string s); // '\\'->'/', lowercase std::vector m_file; // whole pack in memory (simple; mmap later) uint64_t m_data_begin = 0; std::vector m_index; std::unordered_map m_by_name; int m_name_field = PACK_NAME_FIELD_DEFAULT; }; // Build a pack from (name, bytes) pairs. `encrypt` -> per-file encryption==1. // Uses PACK_NAME_FIELD_DEFAULT so real clients / PackLib can read it back. struct InputFile { std::string name; std::vector data; }; bool write_pack(const std::string &out_path, const std::vector &files, bool encrypt, std::string *err = nullptr); } // namespace mtpack