diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6c6e181 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.10) + +project(PVFTools) + + +set(CMAKE_CXX_STANDARD 17) + + +file(GLOB SRC + src/*.cpp + src/*.h +) + +add_library(PVFTools STATIC ${SRC}) + + +target_include_directories(PVFTools PUBLIC + src + ../libiconv/include + ../../Engine/Code/Maple/lib/utf8/include + ../../Engine/Code/Maple/lib/charset-detect + ../../Engine/Code/Maple/lib/zlib/src +) diff --git a/src/BufferReader.h b/src/BufferReader.h new file mode 100644 index 0000000..8dd7b2e --- /dev/null +++ b/src/BufferReader.h @@ -0,0 +1,43 @@ + +#pragma once +#include +#include + +class BufferReader +{ +public: + BufferReader(const uint8_t* buffer, int32_t len) : buffer(buffer), len(len) {} + template + inline T read() + { + size_t count = sizeof(T) / sizeof(int8_t); + T all = 0; + for (size_t i = 0; i < count; i++) + { + T val = static_cast(buffer[offset]); + all += val << (8 * i); + offset++; + } + return static_cast(all); + } + + template<> + inline float read() + { + int32_t v = read(); + return *reinterpret_cast(&v); + } + inline auto readAsciiString(int32_t len) -> std::string { + std::string str = { buffer + offset ,buffer + offset + len }; + offset += len; + return str; + } + + inline auto getOffset() const { return offset; } + inline auto setOffset(int32_t off) { offset = off; } + +private: + const uint8_t* buffer; + int32_t len; + int32_t offset = 0; +}; diff --git a/src/ImgFile.cpp b/src/ImgFile.cpp new file mode 100644 index 0000000..f914fa2 --- /dev/null +++ b/src/ImgFile.cpp @@ -0,0 +1,281 @@ +#include "ImgFile.h" +#include "NpkFile.h" +#include +#include + +const char* FileNameFlag = "puchikon@neople dungeon and fighter DNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNF"; +const char* ImgFlag = "Neople Img File"; +const char* ImgFlag2 = "Neople Image File"; + + +constexpr uint8_t table4[0x10] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF +}; + +constexpr uint8_t table5[0x20] = { + 0x00, 0x08, 0x10, 0x19, 0x21, 0x29, 0x31, 0x3A, 0x42, 0x4A, 0x52, + 0x5A, 0x63, 0x6B, 0x73, 0x7B, 0x84, 0x8C, 0x94, 0x9C, 0xA5, 0xAD, + 0xB5, 0xBD, 0xC5, 0xCE, 0xD6, 0xDE, 0xE6, 0xEF, 0xF7, 0xFF +}; + +//2^6 = 64 +constexpr uint8_t table6[0x40] = { + 0x00, 0x04, 0x08, 0x0C, 0x10, 0x14, 0x18, 0x1C, 0x20, 0x24, 0x28, 0x2D, 0x31, 0x35, 0x39, 0x3D, + 0x41, 0x45, 0x49, 0x4D, 0x51, 0x55, 0x59, 0x5D, 0x61, 0x65, 0x69, 0x6D, 0x71, 0x75, 0x79, 0x7D, + 0x82, 0x86, 0x8A, 0x8E, 0x92, 0x96, 0x9A, 0x9E, 0xA2, 0xA6, 0xAA, 0xAE, 0xB2, 0xB6, 0xBA, 0xBE, + 0xC2, 0xC6, 0xCA, 0xCE, 0xD2, 0xD7, 0xDB, 0xDF, 0xE3, 0xE7, 0xEB, 0xEF, 0xF3, 0xF7, 0xFB, 0xFF +}; + + +ImgFile ImgFile::nullNode(nullptr); + +ImgFile::ImgFile(NpkFile* file):file(file) +{ + +} + +auto ImgFile::unpack() -> std::string +{ + file->readBytes(reinterpret_cast(&metaInfo), sizeof(NpkIndex)); + //TODO use SIMD later + for (int i = 0; i < MAX_FILENAME_LENGTH; ++i) { + metaInfo.fileName[i] = metaInfo.fileName[i] ^ FileNameFlag[i]; + } + return metaInfo.fileName; +} + +auto ImgFile::openColorBoard(std::vector& color) -> void +{ + auto count = file->read(); + color.resize(count); + file->readBytes((uint8_t*)color.data(), sizeof(uint32_t) * count); +} + +auto ImgFile::openMapImages(int32_t size) -> void +{ + mapImages.resize(size); + file->readBytes((uint8_t*)mapImages.data(), sizeof(MapImage) * size); +} + +auto ImgFile::expand() -> void +{ + + file->setPosition(metaInfo.offset); + file->readBytes((uint8_t*)header.flag, 18); + bool ok = false; + + if (strcmp(header.flag, ImgFlag2) == 0) { + //Old Client Version + ok = true; + oldVersion = true; + header.indexSize = file->read(); + } + + if (!ok) { + if (strcmp(header.flag, ImgFlag) == 0) { + ok = true; + file->setPosition(file->getOffset() - 2); + header.indexSize = file->read(); + } + } + + if (!ok) { + printf(" Invalid file %s \n", metaInfo.fileName); + return; + } + + header.keep = file->read(); + header.version = file->read(); + header.indexCount = file->read(); + + printf(" %s : Version %d \n", metaInfo.fileName, header.version); + + switch (header.version) { + case 4: + openColorBoard(colorBoard); + break; + case 5: + { + auto mapCount = file->read(); + auto fileSize = file->read(); + openColorBoard(colorBoard); + openMapImages(mapCount); + } + break; + case 6://multiple color board. + { + int32_t colorBoardCount = file->read(); + auto& color = colorBoards.emplace_back(); + for (auto i = 0; i < colorBoardCount; i++) { + openColorBoard(color); + } + } + break; + } + + nodes.resize(header.indexCount); + + for (auto i = 0; i < header.indexCount; i++) + { + auto& imgNode = nodes[i]; + imgNode.reader = file; + imgNode.uniqueName = metaInfo.fileName + std::to_string(i); + file->read(imgNode.format); + imgNode.isLink = imgNode.format == BitmapType::ARGB_LINK; + if (imgNode.isLink) + { + file->read(imgNode.linkId); + } + else + { + imgNode.linkId = -1; + + file->read(imgNode.texture); + if ((header.version == 1 || header.version == 2) && imgNode.texture.extra == CompressType::COMPRESS_NONE){ + imgNode.texture.size = imgNode.texture.width * imgNode.texture.height * (imgNode.format == ARGB_8888 ? 4 : 2); + } + + if (imgNode.texture.extra == CompressType::COMPRESS_MAP_ZLIB) { + file->readBytes((uint8_t*)&imgNode.zlibInfo, sizeof(imgNode.zlibInfo)); + } + + if (header.version == 1) { + imgNode.offset = file->getOffset(); + file->setPosition(file->getOffset() + imgNode.texture.size); + } + } + } + + if (header.version != 1) + { + auto offset = file->getOffset(); + if (header.version == 2) { + // file->setPosition(metaInfo.offset + header.indexSize + 32); + offset = metaInfo.offset + header.indexSize + 32; + } + + if (header.version == 5) + { + mapImagesOffset.resize(mapImages.size()); + for (int32_t i = 0; i < mapImages.size();i++) { + mapImagesOffset[i] = offset; + offset += mapImages[i].dataSize; + } + } + + for (auto & image: nodes) + { + if (image.format != BitmapType::ARGB_LINK && image.texture.extra != COMPRESS_MAP_ZLIB) + { + image.offset = offset; + offset += image.texture.size; + } + } + } + + for (auto& n : nodes) + { + if (n.isLink) + { + n.texture = nodes[n.linkId].texture; + n.offset = nodes[n.linkId].offset; + n.zlibInfo = nodes[n.linkId].zlibInfo; + } + } +} + +struct color8888 +{ + uint8_t r : 8; + uint8_t g : 8; + uint8_t b : 8; + uint8_t a : 8; +}; + +struct color4444 +{ + uint8_t a : 4; + uint8_t r : 4; + uint8_t g : 4; + uint8_t b : 4; +}; + +/* +struct color1555 +{ + uint8_t a : 1; + uint8_t r : 5; + uint8_t g : 5; + uint8_t b : 5; +};*/ + + +static auto convert1555To8888(std::vector& input, std::vector& output, int32_t pixels) -> void +{ + auto argb1555 = reinterpret_cast(input.data()); + auto pixelsout = reinterpret_cast(output.data()); + for (auto i = 0; i < pixels; ++i) + { + auto& c = argb1555[i]; + //16 bit + uint16_t a = c & 0x8000, r = c & 0x7C00, g = c & 0x03E0, b = c & 0x1F; + a = (a >> 15) & 0x1f; + r = (r >> 10) & 0x1f; + g = (g >> 5) & 0x1f; + b = b; + + pixelsout[i] = { table5[r], table5[g], table5[b], (uint8_t)(a == 1 ? 0xFF : 0) }; + } + input.swap(output); +} + + +static auto convert4444To8888(std::vector & input, std::vector& output, int32_t pixels) -> void +{ + auto pixels4444 = reinterpret_cast(input.data()); + auto pixelsout = reinterpret_cast(output.data()); + for (auto i = 0; i < pixels; ++i) + { + auto p = pixels4444[i]; + pixelsout[i] = { table4[p.r], table4[p.g], table4[p.b], table4[p.a] }; + } + input.swap(output); +} + + +auto ImgNode::getData() ->const std::vector & +{ + if (!input.empty()) { + return input; + } + input.resize(texture.size); + reader->setPosition(offset); + reader->readBytes(input.data(),texture.size); + if (texture.extra == CompressType::COMPRESS_ZLIB) { + std::vector output; + output.resize(texture.width * texture.height * (format == ARGB_8888 ? 4 : 2)); + unsigned long size = output.size(); + auto err = uncompress(output.data(), &size, input.data(), input.size()); + if (err != Z_OK) + { + std::cerr << "uncompess error: " << err << '\n'; + } + input.swap(output); + if (format == ARGB_8888) { + return input; + } + + output.resize(texture.width * texture.height * 4); + + if (format == ARGB_4444) { + convert4444To8888(input, output, texture.width * texture.height); + } + else if (format == ARGB_1555) + { + convert1555To8888(input, output, texture.width * texture.height); + } + return input; + } + return input; +} diff --git a/src/ImgFile.h b/src/ImgFile.h new file mode 100644 index 0000000..e68d915 --- /dev/null +++ b/src/ImgFile.h @@ -0,0 +1,61 @@ +#pragma once +#include "Npk.h" +#include + +class NpkFile; + +struct ImgNode +{ + NpkTexture texture; + + uint32_t format; //目前已知的类型有 0x0E(1555格式) 0x0F(4444格式) 0x10(8888格式) 0x11(不包含任何数据,可能是指内容同上一帧) + int32_t linkId; + + struct { + int32_t keep; + int32_t mapIndex; + int32_t left; + int32_t top; + int32_t right; + int32_t bottom; + int32_t rotate; + }zlibInfo; + + uint32_t offset; + + bool isLink; + // + std::string uniqueName; + NpkFile* reader = nullptr; + auto getData()->const std::vector&; + std::vector input; +}; + +class ImgFile +{ +public: + + static ImgFile nullNode; + ImgFile(NpkFile* file); + + inline auto isValid() const { return this != &nullNode; } + inline operator bool() const { return isValid(); } + auto unpack() -> std::string; + auto openColorBoard(std::vector& color) -> void; + auto openMapImages(int32_t size) -> void; + inline auto getFileName() const -> std::string { return metaInfo.fileName; } + auto expand() -> void; + inline auto& operator[](int32_t index) { assert(index < nodes.size() && index >= 0); return nodes[index]; } +private: + NpkIndex metaInfo; + ImgHeader header; + NpkFile* file = nullptr; + bool oldVersion = false; + + std::vector nodes; + std::vector colorBoard; + std::vector> colorBoards; + std::vector mapImages; + std::vector mapImagesOffset; + +}; \ No newline at end of file diff --git a/src/Npk.cpp b/src/Npk.cpp new file mode 100644 index 0000000..9ed0d66 --- /dev/null +++ b/src/Npk.cpp @@ -0,0 +1,3 @@ +#include "Npk.h" + + diff --git a/src/Npk.h b/src/Npk.h new file mode 100644 index 0000000..4724fc8 --- /dev/null +++ b/src/Npk.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include +#include + +#define MAX_FILENAME_LENGTH 256 +#define MAX_HEADER_FLAG 16 + + + + +struct NpkHeader +{ + char flag[MAX_HEADER_FLAG]; //"NeoplePack_Bill" + int32_t count; // 包内文件的数目 +}; + +struct NpkIndex +{ + uint32_t offset; + uint32_t size; + char fileName[MAX_FILENAME_LENGTH];//256;//MAX_FILENAME_LENGTH +}; + + +struct ImgHeader +{ + char flag[MAX_HEADER_FLAG + 2]; //"Neople Img File" + int32_t indexSize;// 索引表大小,以字节为单位 + int32_t keep; + int32_t version; + int32_t indexCount;// 索引表数目 +}; + +struct NpkTextureOffset +{ + //目前已知的类型有 0x0E(1555格式) 0x0F(4444格式) 0x10(8888格式) 0x11(不包含任何数据,可能是指内容同上一帧) + uint32_t format; + uint32_t compress; //0x06(zlib压缩) 0x05(未压缩) (0x07) map_zlib + //offset in file + uint32_t offset; + uint32_t bufferSize; +}; + +struct NpkTexture +{ + int32_t extra; + int32_t width; + int32_t height; + int32_t size;// 压缩时size为压缩后大小,未压缩时size为转换成8888格式时占用的内存大小 + int32_t x; + int32_t y; + int32_t maxWidth; + int32_t maxWeight; +}; + +enum BitmapType +{ + ARGB_1555 = 14, + ARGB_4444 = 15, + ARGB_8888 = 16, + ARGB_LINK = 17, + Format_DXT_1 = 18, + Format_DXT_3 = 19, + Format_DXT_5 = 20 +}; + +enum CompressType +{ + COMPRESS_NONE = 0x05, + COMPRESS_ZLIB = 0x06, + COMPRESS_MAP_ZLIB = 0x07, +}; + +struct MapImage +{ + int32_t keep; + int32_t fmt; + int32_t index; + int32_t dataSize; + int32_t rawSize; + int32_t w; + int32_t h; +}; diff --git a/src/NpkFile.cpp b/src/NpkFile.cpp new file mode 100644 index 0000000..bfd9245 --- /dev/null +++ b/src/NpkFile.cpp @@ -0,0 +1,148 @@ + +#include "NpkFile.h" +#include +#include +#include "PvfString.h" + + +const char* HeaderFlag = "NeoplePack_Bill"; + + +NpkFile::NpkFile(const std::string& initFile) + :fileName(initFile) +{ + +} +auto NpkFile::openFile() -> void +{ + file = fopen(fileName.c_str(), "rb"); + if (file == nullptr) { + printf("fail to open this file : %s", fileName.c_str()); + return; + } + fseek(file, 0, SEEK_END); + length = ftell(file); + fseek(file, 0, SEEK_SET); +} + +auto NpkFile::loadAll(const std::string& path) -> void +{ + for (const auto& entry : std::filesystem::directory_iterator(path)) + { + bool isDir = std::filesystem::is_directory(entry); + + if ( + PvfString::endWith(entry.path().string(), ".npk") || + PvfString::endWith(entry.path().string(), ".NPK") + && !isDir) + { + std::string path = entry.path().string(); + // PvfString::toLower(path); + + GlobalFileTable.emplace( + path, + path + );// .first->second.unpack(); + } + } +} + +auto NpkFile::unpack() -> void +{ + openFile(); + NpkHeader header; + readBytes(reinterpret_cast(&header), sizeof(NpkHeader)); + + if (strcmp(header.flag, HeaderFlag) != 0) { + printf("is not a valid npk file"); + return; + } + + for (int32_t i = 0; i < header.count; ++i) + { + auto & node = imgNodes.emplace_back(this); + node.unpack(); + } + + for (auto & node : imgNodes) + { + GlobalTable[node.getFileName()] = &node; + node.expand(); + } +} + +auto NpkFile::setPosition(uint32_t position) -> void +{ + if (position > length) + { + printf("NpkFile :: OutOfFileSizeException : %d \n", position); + return; + } + fseek(file, position, SEEK_SET); + this->offset = position; +} + +auto NpkFile::readBytes(uint32_t length) ->std::unique_ptr +{ + auto bytes = std::make_unique(length); + fread(bytes.get(), length, 1, file); + offset += length; + return bytes; +} + +auto NpkFile::readBytes(uint8_t* data, int32_t len) ->void +{ + fread(data, len, 1, file); + offset += len; +} + +auto NpkFile::readString(int32_t len) -> std::string +{ + std::string str; + str.resize(len); + fread(str.data(), len, 1, file); + offset += len; + return str; +} + + +auto NpkFile::expand(const std::string& name) -> void +{ + +} + +#ifdef _WIN32 +static const std::string delimiter = "\\"; +#else +static const std::string delimiter = "/"; +#endif + +std::unordered_map NpkFile::GlobalTable; + +std::unordered_map NpkFile::GlobalFileTable; + +auto NpkFile::getNpkImgNode(const std::string& path, int32_t index) -> ImgNode& +{ + std::vector outs; + PvfString::split(path, "/", outs); + std::string newStr = "ImagePacks2"+ delimiter +"sprite_"; + std::string newStr2 = "sprite/"; + + for (auto i = 0;isecond.unpack(); + } + return (*GlobalTable[newStr2])[index]; +} diff --git a/src/NpkFile.h b/src/NpkFile.h new file mode 100644 index 0000000..b48313d --- /dev/null +++ b/src/NpkFile.h @@ -0,0 +1,57 @@ + + +#pragma once +#include "Npk.h" +#include +#include +#include +#include +#include + +#include "ImgFile.h" + +class NpkFile +{ +public: + NpkFile(const std::string& file); + NpkFile() = default; + static auto loadAll(const std::string& path) -> void; + static std::unordered_map GlobalTable; + static std::unordered_map GlobalFileTable; + + static auto getNpkImgNode(const std::string& path, int32_t index) ->ImgNode&; + + auto unpack() -> void; + + + template + inline auto read() -> T + { + T t; + fread(&t, sizeof(T), 1, file); + offset += sizeof(T); + return t; + } + template + inline auto read(T& t) -> void + { + fread(&t, sizeof(T), 1, file); + offset += sizeof(T); + } + + auto setPosition(uint32_t offset) -> void; + auto readBytes(uint32_t length)->std::unique_ptr; + auto readBytes(uint8_t* data, int32_t len)->void; + auto readString(int32_t len) ->std::string; + inline auto getOffset() const { return offset; } + + +private: + auto openFile() -> void; + auto expand(const std::string & name) -> void; + std::string fileName; + FILE* file = nullptr; + uint32_t offset = 0; + uint32_t length = 0; + std::vector imgNodes; +}; diff --git a/src/NpkLoader.cpp b/src/NpkLoader.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/NpkLoader.h b/src/NpkLoader.h new file mode 100644 index 0000000..91ecf46 --- /dev/null +++ b/src/NpkLoader.h @@ -0,0 +1,10 @@ + + +#pragma once + + +class NpkLoader +{ +public: + +}; diff --git a/src/Pvf.cpp b/src/Pvf.cpp new file mode 100644 index 0000000..7d13ee2 --- /dev/null +++ b/src/Pvf.cpp @@ -0,0 +1,4 @@ + + +#include "Pvf.h" + diff --git a/src/Pvf.h b/src/Pvf.h new file mode 100644 index 0000000..3f59c93 --- /dev/null +++ b/src/Pvf.h @@ -0,0 +1,2 @@ +#pragma once + diff --git a/src/PvfAnimation.cpp b/src/PvfAnimation.cpp new file mode 100644 index 0000000..3a0b06e --- /dev/null +++ b/src/PvfAnimation.cpp @@ -0,0 +1,158 @@ +#pragma once + +#include +#include +#include +#include +#include "PvfReader.h" +#include "PvfAnimation.h" +#include "BufferReader.h" +#include "PvfString.h" + +PvfAnimation::PvfAnimation(const uint8_t* buffer, int32_t len, PvfReader* reader) + :buffer(buffer),len(len), reader(reader) +{ + type = PvfScriptType::Animation; +} + +auto PvfAnimation::unpack() -> void +{ + BufferReader reader(buffer, len); + framesCount = reader.read(); + auto countOfResources = reader.read(); + frames.resize(framesCount); + std::vector sprites; + for (auto i = 0; i < countOfResources; i++) + { + int32_t len = reader.read(); + sprites.emplace_back(reader.readAsciiString(len)); + PvfString::toLower(sprites.back()); + } + + auto params = reader.read(); + for (auto j = 0; j < params; j++) + { + auto type = reader.read(); + switch (type) + { + case AnimationNodeType::LOOP: + loop = reader.read(); + break; + + case AnimationNodeType::SHADOW: + shadow = reader.read(); + break; + } + } + + for (auto i = 0; i < framesCount; i++) + { + auto& frame = frames[i]; + + auto boxes = reader.read(); + for (auto j = 0; j < boxes; j++) + { + auto type = reader.read(); + assert(type == DAMAGE_BOX || type == ATTACK_BOX); + auto& box = type == DAMAGE_BOX ? frame.damageBox.emplace_back() : frame.attackBox.emplace_back(); + for (int32_t m = 0; m < 6; m++) + { + box[m] = reader.read(); + } + } + frame.imgId = reader.read(); + frame.imgParam = reader.read(); + frame.path = sprites[frame.imgId]; + assert(frame.imgId >= 0 && frame.imgId < sprites.size()); + + frame.x = reader.read(); + frame.y = reader.read(); + + int32_t propertyCount = reader.read(); + for (int32_t m = 0; m < propertyCount; m++) + { + AnimationNodeType type = (AnimationNodeType)reader.read(); + switch (type) { + case LOOP: + frame.loop = reader.read(); + break; + case SHADOW: + frame.shadow = reader.read(); + break; + case INTERPOLATION: + frame.interpolation = reader.read(); + break; + case 2: + case 4: + case 5: + case 6: + case DAMAGE_BOX: + case ATTACK_BOX: + case SPECTRUM://18 + case 19://18 + case 20: + case 21: + case 22: + break; + case Ani_COORD: + frame.coord = reader.read(); + break; + case IMAGE_RATE: + frame.rateX = reader.read(); + frame.rateY = reader.read(); + break; + case IMAGE_ROTATE: + frame.rotate = reader.read(); + break; + case RGBA: + frame.color = reader.read(); + break; + case GRAPHIC_EFFECT: + frame.itemType = (EffectItem)reader.read(); + if (frame.itemType == EffectItem::MONOCHROME) + { + frame.effectItem.effectColor.r = reader.read(); + frame.effectItem.effectColor.g = reader.read(); + frame.effectItem.effectColor.b = reader.read(); + } + else if (frame.itemType == SPACEDISTORT) + { + frame.effectItem.pos.x = reader.read(); + frame.effectItem.pos.y = reader.read(); + } + break; + case DELAY://12 + frame.delay = reader.read(); + break; + case DAMAGE_TYPE: + frame.damageType = (DamageType)reader.read(); + break; + case PLAY_SOUND: + frame.sound = reader.readAsciiString(reader.read()); + break; + case PRELOAD: + break; + case SET_FLAG: + frame.setFlag = reader.read(); + break; + case FLIP_TYPE: + frame.flipType = (FlipType)reader.read(); + break; + case LOOP_START: + frame.loopStart = true; + break; + case LOOP_END: + frame.loopEnd = reader.read(); + break; + case CLIP: + frame.clip[0] = reader.read(); + frame.clip[1] = reader.read(); + frame.clip[2] = reader.read(); + frame.clip[3] = reader.read(); + break; + default: + break; + } + } + } +} diff --git a/src/PvfAnimation.h b/src/PvfAnimation.h new file mode 100644 index 0000000..92e1cb5 --- /dev/null +++ b/src/PvfAnimation.h @@ -0,0 +1,131 @@ +#pragma once +#include +#include +#include + +#include "PvfScript.h" + +class PvfReader; + +enum AnimationNodeType +{ + LOOP = 0, + SHADOW = 1, + Ani_COORD = 3, + IMAGE_RATE = 7, + IMAGE_ROTATE, + RGBA, + INTERPOLATION = 10, + GRAPHIC_EFFECT, + DELAY, + DAMAGE_TYPE, + DAMAGE_BOX, + ATTACK_BOX, + PLAY_SOUND, + PRELOAD, + SPECTRUM, + SET_FLAG = 23, + FLIP_TYPE, + LOOP_START, + LOOP_END, + CLIP, + OPERATION, + LENGTH +}; + +enum EffectItem +{ + NONE, + DODGE, + LINEARDODGE, + DARK, + XOR, + MONOCHROME, + SPACEDISTORT//空间错乱 +}; + +enum FlipType +{ + HORIZON = 1, + VERTICAL, + ALL +}; + +enum DamageType +{ + NORMAL, + SUPERARMOR, + UNBREAKABLE +}; + +class PvfAnimation : public PvfScript +{ +public: + PvfAnimation(const uint8_t* buffer, int32_t len,PvfReader * reader); + auto unpack() -> void override; + inline auto& getFrames() const { return frames; } + inline auto isLoop() const { return loop; } +private: + + union NodeData + { + int32_t intValue; + bool flag; + }; + + struct PvfFrame + { + int32_t x; + int32_t y; + int32_t imgId;//link to sprites + std::string path; + uint16_t imgParam;//unknown what is it + + float rateX;//?? scale in here? + float rateY; + float rotate; + uint32_t color = UINT32_MAX; + EffectItem itemType; + int32_t delay; + DamageType damageType; + std::string sound; + int32_t setFlag; + FlipType flipType; + bool loopStart = false; + bool shadow = false; + bool interpolation = false; + int32_t loopEnd; + int16_t clip[4]; + int32_t coord; + union + { + struct + { + uint8_t r; + uint8_t g; + uint8_t b; + }effectColor; + + struct + { + uint32_t x; + uint32_t y; + }pos; + + }effectItem; + + std::vector> damageBox; + std::vector> attackBox; + bool loop = false; + }; + + + std::vector frames; + int32_t framesCount = 0; + int32_t len = 0; + bool loop = false; + + bool shadow = false; + const uint8_t* buffer = nullptr; + PvfReader* reader = nullptr; +}; diff --git a/src/PvfDocument.cpp b/src/PvfDocument.cpp new file mode 100644 index 0000000..bf984b4 --- /dev/null +++ b/src/PvfDocument.cpp @@ -0,0 +1,174 @@ +#include "PvfDocument.h" +#include "PvfNode.h" +#include +#include "PvfReader.h" +#include "ValueType.h" +#include +#include +#include "PvfString.h" +#include "BufferReader.h" + + + +PvfDocument::PvfDocument(const uint8_t* buffer, int32_t len,PvfReader * reader) + :buffer(buffer),len(len),pvfReader(reader) +{ + type = PvfScriptType::Document; +} + + +auto PvfDocument::unpack() -> void +{ + if (len > 7) { + BufferReader reader(buffer, len); + auto header = reader.read(); + + std::unordered_set tags; + + while (reader.getOffset() < len - 4) + { + auto type = reader.read(); + if (type >= 2 && type <= 10) { + auto index = reader.read(); + if (type == ValueType::Section) + { + tags.emplace(pvfReader->stringBinMap[index]); + } + } + } + + reader.setOffset(2); + node = &root; + std::stack stack; + stack.push(node); + + + while (reader.getOffset() < len) + { + auto type = reader.read(); //到最后了就不处理了防止内存越界 + + if (type >= 2 && type <= 10) + { + auto index = reader.read(); + + switch (type) { + case ValueType::IntEx: + case ValueType::Int://2 + { + node->addAttribute(index); + } + break; + case ValueType::Float://4 + { + float f = *reinterpret_cast(&index); + node->addAttribute(f); + } + break; + + case ValueType::Section: + { + auto name = pvfReader->stringBinMap[index]; + auto endTagName = name; + endTagName.insert(endTagName.begin() + 1, '/'); + + if(!PvfString::startWith(name,"[/")){//Start Node + + PvfString::trim(name, "["); + PvfString::trim(name, "]"); + if (node != nullptr && !node->hasEndTag && node != &root) + { + pop(stack, name); + } + if (node != nullptr) // && node->hasEndTag + {//new node is a child in pre-node + auto& newNode = node->children[name].emplace_back(); + newNode.name = name; + newNode.hasEndTag = tags.count(endTagName); + stack.push(&newNode); + node = &newNode; + } + } + else //end Node + { + PvfString::trim(name, "[/"); + PvfString::trim(name, "]"); + pop(stack, name); + } + + } + break; + + case ValueType::String://Child + { + node->addAttribute(pvfReader->stringBinMap[index]); + } + break; + case ValueType::Command: + case ValueType::CommandSeparator: + { + node->addAttribute(pvfReader->stringBinMap[index]); + } + break; + + case ValueType::StringLink: + if (auto str = pvfReader->stringBinMap[index]; str != "") { + node->addAttribute(pvfReader->stringStringMap[str]); + //std::cout<stringStringMap[str]<stringBinMap[index]; str != "") { + node->addAttribute(pvfReader->stringStringMap[str]); + } + }*/ + break; + } + } + else + { + std::cout << "Unknown type in pvf node :" << (int32_t)type << std::endl; + } + } + } +} + +auto PvfDocument::splitNode(const std::string& name) ->std::shared_ptr +{ + std::vector outs; + PvfString::split(name, "/", outs); + auto& pvfNode = root[outs[0]]; + Node * node = nullptr; + int32_t i = 1; + while (i < outs.size() - 1)//last + { + node = &pvfNode[std::stoi(outs[i++])]; + } + return node != nullptr ? node->attribute[std::stoi(outs[i])] : nullptr; +} + +auto PvfDocument::pop(std::stack& stack, const std::string& name) -> void +{ + if (node != nullptr && node != &root) + { + if (!stack.empty()) { + stack.pop(); + } + + if (!stack.empty()) { + node = stack.top(); + } + else + { + node = nullptr; + } + + if (node != nullptr && node->name == name) + { + pop(stack, node->name); + } + } +} + +PvfDocument::Node PvfDocument::nullNode; diff --git a/src/PvfDocument.h b/src/PvfDocument.h new file mode 100644 index 0000000..4213972 --- /dev/null +++ b/src/PvfDocument.h @@ -0,0 +1,156 @@ + +#pragma once +#include +#include +#include +#include +#include +#include +#include "PvfScript.h" + +class PvfReader; + +class PvfDocument : public PvfScript +{ +public: + + enum AttributeType + { + Number, + String, + }; + + union DNumber + { + int32_t intValue; + float floatValue; + }; + + struct IAttribute { + AttributeType type; + }; + + template + struct Attribute : IAttribute + { + inline Attribute() : value() { type = TType; } + T value; + }; + + struct NumberAttribute final : public Attribute {}; + struct StringAttribute final : public Attribute {}; + + struct Node + { + std::string name; + std::vector> attribute; + std::unordered_map< + std::string, + std::vector + > children; + + inline auto size() const { return attribute.size(); }; + + inline auto& operator[](const std::string& name) { return children[name]; }; + + template + inline auto to(int32_t index) const -> const T&{ + return T(); + } + template<> + inline auto to(int32_t index) const -> const float& { + return std::static_pointer_cast(attribute[index])->value.floatValue; + } + template<> + inline auto to(int32_t index) const -> const int32_t& { + return std::static_pointer_cast(attribute[index])->value.intValue; + } + template<> + inline auto to(int32_t index) const -> const std::string& { + return std::static_pointer_cast(attribute[index])->value; + } + + + + template + auto addAttribute(const T& t)->void {}; + + template<> + auto addAttribute(const float & t)->void + { + auto att = std::make_shared(); + att->value.floatValue = t; + attribute.emplace_back(att); + }; + + template<> + auto addAttribute(const int32_t & t)->void + { + auto att = std::make_shared(); + att->value.intValue = t; + attribute.emplace_back(att); + }; + + template<> + auto addAttribute(const std::string& t)->void + { + auto att = std::make_shared(); + att->value = t; + attribute.emplace_back(att); + }; + + bool hasEndTag = false; + + }; + + PvfDocument(const uint8_t* buffer, int32_t len, PvfReader* reader); + auto unpack() -> void override; + + inline auto& operator[](const std::string& name) { + return root[name]; + } + + template + inline auto get(const std::string& name) -> const T& { + return T(); + } + + template<> + inline auto get(const std::string& name)-> const std::string& + { + static std::string nullName = ""; + auto attr = splitNode(name); + return attr ? std::static_pointer_cast(attr)->value : nullName; + } + + template<> + inline auto get(const std::string& name)->const float& + { + static float nullName = 0; + auto attr = splitNode(name); + return attr ? std::static_pointer_cast(attr)->value.floatValue : nullName; + } + + template<> + inline auto get(const std::string& name)->const int32_t& + { + static int32_t nullName = 0; + auto attr = splitNode(name); + return attr ? std::static_pointer_cast(attr)->value.intValue : nullName; + } + +private: + + auto splitNode(const std::string& name)->std::shared_ptr; + + auto pop(std::stack& stack, const std::string& name) -> void; + + const uint8_t* buffer; + int32_t len; + PvfReader* pvfReader = nullptr; + Node root; + Node* node = nullptr; + + static Node nullNode; + +}; \ No newline at end of file diff --git a/src/PvfNode.cpp b/src/PvfNode.cpp new file mode 100644 index 0000000..4850ec5 --- /dev/null +++ b/src/PvfNode.cpp @@ -0,0 +1,81 @@ + +#include "PvfNode.h" +#include "PvfReader.h" +#include "PvfAnimation.h" +#include "PvfDocument.h" +#include "PvfString.h" +#include + +auto PvfNode::unpack() -> std::shared_ptr +{ + if (pvfScript != nullptr) + { + return pvfScript; + } + auto buffer = expand(); + if (PvfString::endWith(fileName,".ani")) + { + pvfScript = std::make_shared(buffer.get(), getComputedFileLength(),reader); + } + else if (PvfString::endWith(fileName, ".str")) + { + pvfScript = std::make_shared(buffer.get(), getComputedFileLength(), reader); + } + else + { + pvfScript = std::make_shared(buffer.get(), getComputedFileLength(), reader); + } + pvfScript->unpack(); + return pvfScript; +} + +auto PvfNode::expand() -> std::unique_ptr +{ + if (fileLength > 0) { + auto computedFileLength = (int32_t)((fileLength + 3L) & 4294967292L); + reader->setPosition(relativeOffset); + auto bytes = reader->readBytes(computedFileLength); + reader->decrypt(bytes.get(), computedFileLength, fileCrc32); + + for (int32_t i = 0; i < (computedFileLength - fileLength); i++) + { + bytes.get()[fileLength + i] = 0; + } + return bytes; + } + return nullptr; +} + + +auto PvfNode::getComputedFileLength() const -> int32_t +{ + return (int32_t)((fileLength + 3L) & 4294967292L); +} + + +PvfTreeNode PvfTreeNode::nullNode = {}; + +auto PvfTreeNode::operator[](const std::string& path) -> PvfTreeNode& +{ + if (auto iter = children.find(path); iter != children.end()) { + return *iter->second; + } + return PvfTreeNode::nullNode; +} + +auto PvfTreeNode::getByPath(const std::string& path) -> PvfTreeNode& +{ + std::vector outs; + PvfString::split(path, "/", outs); + PvfTreeNode * node = this; + for (auto & path : outs) + { + node = &(*node)[path]; + } + return *node; +} + +auto PvfTreeNode::unpack() ->std::shared_ptr< PvfScript> +{ + assert(node != nullptr); return node->unpack(); +} diff --git a/src/PvfNode.h b/src/PvfNode.h new file mode 100644 index 0000000..7a6549b --- /dev/null +++ b/src/PvfNode.h @@ -0,0 +1,46 @@ +#pragma once +#include +#include +#include +#include +#include "PvfScript.h" + + +class PvfNode; +class PvfAnimation; +class PvfDocument; + +struct PvfTreeNode +{ + static PvfTreeNode nullNode; + auto operator[](const std::string& path) -> PvfTreeNode&; + auto getByPath(const std::string& path) -> PvfTreeNode&; + auto unpack()->std::shared_ptr< PvfScript>; + inline auto isValid() { return this != &nullNode; } + + PvfTreeNode(const std::string& name = "") :name(name) { }; + std::string name; + std::unordered_map> children; + PvfNode* node = nullptr; + PvfTreeNode* parent = nullptr; +}; + +class PvfNode +{ +public: + friend class PvfReader; + auto unpack() ->std::shared_ptr; +private: + auto expand() -> std::unique_ptr; + auto getComputedFileLength() const->int32_t; + PvfReader* reader = nullptr; + uint32_t fileNumber = 0; + int32_t filePathLength = 0; + uint8_t * offset = nullptr; + int32_t fileLength = 0; + uint32_t fileCrc32 = 0; + int32_t relativeOffset = 0; + std::string fileName; + std::string content; + std::shared_ptr pvfScript; +}; \ No newline at end of file diff --git a/src/PvfReader.cpp b/src/PvfReader.cpp new file mode 100644 index 0000000..5694ee3 --- /dev/null +++ b/src/PvfReader.cpp @@ -0,0 +1,381 @@ +#include "PvfReader.h" +#include "iconv.h" +#include +#include +#include +#include +#include + +#include "PvfString.h" + +inline static auto rotateRight4(uint32_t x, uint32_t y) -> uint32_t { + return x >> y | x << 32 - y; +} + +// trim from start (in place) +static inline auto ltrim(std::string& s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { + return !std::isspace(ch); + })); +} + +// trim from end (in place) +static inline auto rtrim(std::string& s) { + s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { + return !std::isspace(ch); + }).base(), s.end()); +} + +// trim from both ends (in place) +static inline auto trim(std::string& s) { + ltrim(s); + rtrim(s); +} + + +static inline auto split(const std::string& line, const std::string& a, const std::string& b) -> std::string +{ + auto index = 0; + if (a != "") + index = line.find_first_of(a);//如果a不为空在行中搜索a + if (index == std::string::npos) + return ""; + index = index + a.length(); + auto str = line.substr(index, line.length() - index);//对line提取a后面的部分 + if (b == "") + return str; + auto num = str.find_first_of(b); + if (num == std::string::npos) + { + return ""; + } + return str.substr(0, num); +} + +static inline auto toLower(std::string& data) { + std::transform(data.begin(), data.end(), data.begin(), + [](unsigned char c) { return std::tolower(c); }); +} + +PvfReader::PvfReader(const std::string& path) +{ + file = fopen(path.c_str(), "rb"); + if (file == nullptr) { + printf("fail to open this file : %s", path.c_str()); + exit(0); + } + fseek(file, 0, SEEK_END); + length = ftell(file); + fseek(file, 0, SEEK_SET); +} + +PvfReader::~PvfReader() +{ + + if (file != nullptr) + fclose(file); +} + +auto PvfReader::codeConvert(const char* fromCharset, const char* toCharset, const char* inbuf, size_t inlen, + char* outbuf, size_t outlen) -> int32_t +{ + iconv_t cd; + const char* temp = inbuf; + char** pout = &outbuf; + memset(outbuf, 0, outlen); + cd = iconv_open(toCharset, fromCharset); + if (cd == nullptr) + return -1; + + iconv(cd, const_cast(&inbuf), &inlen, pout, &outlen); + iconv_close(cd); + return outlen; +} + + +auto PvfReader::unpack() -> void +{ + fread(&header, sizeof(PvfHeader), 1, file); + auto headLength = header.dirTreeLength;//获取文件索引列表字节总大小 + auto dirTreeData = new uint8_t[header.dirTreeLength]; + fread(dirTreeData, header.dirTreeLength, 1, file); + decrypt(dirTreeData, header.dirTreeLength, header.dirTreeChecksum); + + int32_t offset = 0; + auto outChr = new char[1024]; + + for (int32_t i = 0; i < header.numFilesInDirTree; i++) + { + + auto fileNumber = read(dirTreeData, offset); + auto filePathLength = read(dirTreeData, offset + 4); + auto filePath = dirTreeData + offset + 8; + auto fileLength = read(dirTreeData, offset + 8 + filePathLength); + auto fileCrc32 = read(dirTreeData, offset + 12 + filePathLength); + auto relativeOffset = read(dirTreeData, offset + 0x10 + filePathLength); + + //CP949(韩语) + codeConvert(PvfReader::ENCODING, "UTF-8", (char*)filePath, filePathLength, outChr, filePathLength * 2); + std::string filePathName(outChr); + rtrim(filePathName); + auto & node = pvfNodes[filePathName]; + node.fileNumber = fileNumber; + node.filePathLength = filePathLength; + node.offset = filePath; + node.fileLength = fileLength; + node.fileCrc32 = fileCrc32; + node.relativeOffset = sizeof(PvfHeader) + header.dirTreeLength + relativeOffset; + node.reader = this; + node.fileName = filePathName; + offset += filePathLength + 20; + } + unpackStringTable(nullptr,nullptr); + mapping(); + + delete[] outChr; + delete[] dirTreeData; +} + +auto PvfReader::setPosition(uint64_t position) -> void +{ + if (position >= length) + { + printf("PvfReader :: OutOfFileSizeException : %lld \n",position); + return; + } + fseek(file, position, SEEK_SET); + this->pos = position; +} + + +auto PvfReader::decrypt(uint8_t* ptr, uint32_t len, uint32_t crc32) -> void +{ + uint32_t* newPtr = reinterpret_cast(ptr); + int32_t i = 0; + while ( i < len / 4) + { + newPtr[i] = rotateRight4(newPtr[i] ^ PASSWORD_PVF ^ crc32, 6); + i++; + } + +/* + int32_t index = 0; + while (index < len ) + { + auto intv = read(ptr, index); + auto decryptWord = rotateRight4(intv ^ PASSWORD_PVF ^ crc32, 6); + memcpy(ptr + index, decryptWord, 4); + index += 4; + }*/ +} + +auto PvfReader::dfsCreateNode(PvfNode& tag, PvfTreeNode * tree, const std::vector& pathes, int32_t deep) -> void +{ + if (pathes.size() - 1 == deep) + { + tree->children[pathes[deep]] = std::make_unique(pathes[deep]); + tree->children[pathes[deep]]->parent = tree; + tree->children[pathes[deep]]->node = &tag; + return; + } + + if (tree->children.find(pathes[deep]) == tree->children.end()) + { + tree->children[pathes[deep]] = std::make_unique(pathes[deep]); + tree->children[pathes[deep]]->parent = tree; + } + auto & item1 = tree->children[pathes[deep]]; + dfsCreateNode(tag, item1.get(), pathes, deep + 1); +} + +auto PvfReader::mapping() -> void +{ + for (auto & kv : pvfNodes) + { + std::vector out; + PvfString::split(kv.first,"/", out); + dfsCreateNode(kv.second, &root, out, 0); + } + std::cout << "mapping over"<, std::function,int32_t)>& addTask, const std::function& waitAll) -> void +{ + auto& strtable = pvfNodes["stringtable.bin"]; + + auto ptr = strtable.expand(); + + auto buffer = ptr.get(); + + int32_t count = read(ptr.get(), 0); + + stringBinMap.resize(count); + char* outChars = new char[32767]; + for (int32_t i = 0; i < count; i++) + { + auto startPos = read(buffer, i * 4 + 4);//每次循环的第一个int是键开始的地址 + auto endPos = read (buffer, i * 4 + 8);//每次循环的第二个int是键结束的地址 + auto len = endPos - startPos;//相减就是值的长度 + int32_t index = i;//索引就是出现的第几个 + + + codeConvert(PvfReader::ENCODING, "UTF-8", (char*)buffer + startPos + 4, len, outChars, len * 2); + this->stringBinMap[index] = { outChars };//放到索引表中备用 + toLower(this->stringBinMap[index]); + trim(this->stringBinMap[index]); + + } + delete[] outChars; + //################################## + auto& nstrtable = pvfNodes["n_string.lst"]; + ptr = nstrtable.expand(); + auto len = nstrtable.getComputedFileLength(); + + auto magicNumber = read(ptr.get(), 0); + assert(magicNumber == 53424); + + for (auto i = 2; i < len; i += 10) + { + if (len - i >= 10)//如果是最后十个字节或者最后不满十个字节就不执行 + { + //前6位干嘛的不知道,6-10位的int值是stringtable的键,取出来 + auto v = read(ptr.get(), i + 6); + const auto & k = stringBinMap[v]; + //取出来的stringtable的值是文件列表的一个文件的文件名,不过使用了驼峰命名需要将其置为小写并清除空格。 + + if (auto node = pvfNodes.find(k); node != pvfNodes.end()) + { + auto full = std::static_pointer_cast(node->second.unpack()); + std::vector out; + PvfString::split(full->getContent(),"\r\n", out); + + for (auto& line : out)//根据换行分割,逐行遍历 + { + if (auto pos = line.find_first_of('>'); pos != line.npos)//行包含符号'>',如name_xxx>格斗家 + { + auto key = split(line, "", ">"); + auto val = split(line, ">", ""); + stringStringMap[key] = val;//放到索引表中备用 + } + } + } + } + } +} + +auto PvfReader::write(const std::string& file, const std::string& str) -> void +{ + std::string delimiter = "/"; + std::vector outs; + PvfString::split ("/", file, outs); + + if (outs.size() > 1) { + auto pos = file.find_last_of(delimiter); + if (pos != std::string::npos) { + auto name = file.substr(0, pos); + std::filesystem::create_directories(name); + } + } + + FILE* f = fopen(file.c_str(), "wb"); + fwrite(str.c_str(), str.length(), 1, f); + fclose(f); +} + +auto PvfReader::operator[](const std::string& path) ->PvfTreeNode& +{ + return root[path]; +} + +auto PvfReader::readBytes(uint32_t length) ->std::unique_ptr +{ + auto bytes = std::make_unique(length); + fread(bytes.get(), length, 1, file); + return bytes; +} + + +auto PvfReader::decryptString(const std::unique_ptr& buffer, int32_t len, std::string& out) -> void +{ + /*if (len > 7) { + for (int32_t i = 2; i < len; i += 5)//以5为单步从第二位开始遍历字节 + { + if (len - i >= 5)//到最后了就不处理了防止内存越界 + { + auto type = buffer.get()[i];//猜测应该是内容指示位 + if (type>=1 && type <= 10) + { + auto index = read(buffer.get(), i + 1); + + switch (type) { + case ValueType::Value1: + case ValueType::Value3: + case ValueType::Value9: + case ValueType::Int://2 + { + auto value = std::to_string(index) +'\t'; + out.append(value); + } + break; + case ValueType::Float://4 + { + float f = *reinterpret_cast(&index); + auto value = std::to_string(f) + '\t'; + out.append(value); + } + break; + + case ValueType::IntString5: + { + out.append("\r\n"); + out.append(stringBinMap[index]); + out.append("\r\n"); + } + break; + case ValueType::IntString7: + { + out.append("`"); + out.append(stringBinMap[index]); + out.append("`\r\n"); + } + break; + case ValueType::IntString6: + case ValueType::IntString8: + { + out.append("{"); + out.append(std::to_string(type)); + out.append("=`"); + out.append(stringBinMap[index]); + out.append("`}\r\n"); + } + break; + + case ValueType::StringTable: + { + auto before = read(buffer.get(), i - 4); + + if (auto str = stringBinMap[index];str != "") { + out.append("<"); + out.append(std::to_string(before)); + out.append("::"); + out.append(str); + out.append("`"); + out.append(stringStringMap[str]); + out.append("`>"); + } + out.append("\r\n"); + } + break; + } + } + else + { + std::cout << "Unknown type in pvf node :"<< type << std::endl; + } + } + } + }*/ +} + + diff --git a/src/PvfReader.h b/src/PvfReader.h new file mode 100644 index 0000000..74f477e --- /dev/null +++ b/src/PvfReader.h @@ -0,0 +1,98 @@ +#pragma once +#include +#include +#include +#include + +#include "PvfNode.h" +#include +#include + +enum EncodingType +{ + TW = 950, + CN = 936, + KR = 949, + JP = 932, + UTF8 = 65001, + Unicode = 1200 +}; + + + +class PvfReader +{ + struct PvfHeader + { + int32_t sizeGUID; //Always 0x24 + uint8_t GUID[0x24]; + int32_t fileVersion; + int32_t dirTreeLength;//头文件占用字节大小 + int32_t dirTreeChecksum;//CRC32码 + int32_t numFilesInDirTree;//PVF文件总数 + }; + + +public: + friend class PvfDocument; + + static constexpr uint32_t PASSWORD_PVF = 0x81A79011; + //static constexpr char* ENCODING = "CP949"; + static constexpr char* ENCODING = "BIG5HKSCS"; + PvfReader(const std::string& path); + ~PvfReader(); + auto unpack() -> void; + auto setPosition(uint64_t pos) -> void; + auto decrypt(uint8_t* ptr, uint32_t len, uint32_t crc32) -> void; + auto readBytes(uint32_t length)->std::unique_ptr; + auto decryptString(const std::unique_ptr& buffer, int32_t len, std::string& out) -> void; + auto codeConvert(const char* fromCharset, const char* toCharset, const char* inbuf, size_t inlen, char* outbuf, size_t outlen)->int32_t; + auto write(const std::string& file, const std::string & str) -> void; + + auto operator[](const std::string & path) ->PvfTreeNode&; + + inline auto& getRoot() { return root; } + inline auto isLoaded() const { return loaded; } + + template + // Read a number and advance the buffer position. + inline T read(const uint8_t* buffer, int32_t offset) + { + size_t count = sizeof(T) / sizeof(int8_t); + T all = 0; + for (size_t i = 0; i < count; i++) + { + T val = static_cast(buffer[offset]); + all += val << (8 * i); + offset++; + } + return static_cast(all); + } + + auto unpackStringTable(const std::function, std::function, int32_t + )> & addTask, const std::function & waitAll + ) -> void; + + + +private: + auto dfsCreateNode(PvfNode& tag, PvfTreeNode* tree, const std::vector& pathes, int32_t deep) -> void; + auto mapping() -> void; + + + + int64_t length = 0; + uint64_t pos = 0; + FILE* file = nullptr; + std::string path; + PvfHeader header; + + std::unordered_map pvfNodes; + std::unordered_map stringStringMap; + std::vector stringBinMap; + + + PvfTreeNode root; + bool loaded = false; +}; diff --git a/src/PvfScript.cpp b/src/PvfScript.cpp new file mode 100644 index 0000000..c4d2964 --- /dev/null +++ b/src/PvfScript.cpp @@ -0,0 +1,29 @@ +#include "PvfScript.h" +#include "PvfReader.h" +#include "tellenc.h" + +PvfTextScript::PvfTextScript(const uint8_t* buffer, int32_t len, PvfReader* reader) + :buffer(buffer),len(len),reader(reader) +{ + type = PvfScriptType::Text; +} + +auto PvfTextScript::unpack() -> void +{ + //libIconv 在(BIG5字符集下) 转换'恒'的时候会中断 + //使用BIG5HKSCS香港增补字符集 + char* outBuffer = new char[len * 2]; + memset(outBuffer, 0, len * 2); + + //auto charset = tellenc(buffer, len); + /*if (/ *charset == std::string("big5") || * /charset == std::string("binary")) { + charset = PvfReader::ENCODING; + } +*/ + int32_t retLen = 0; + if (buffer && len > 0) + { + retLen = reader->codeConvert(PvfReader::ENCODING, "UTF-8", (const char*)buffer, len, outBuffer, len * 2); + str = {outBuffer}; + } +} diff --git a/src/PvfScript.h b/src/PvfScript.h new file mode 100644 index 0000000..27732d5 --- /dev/null +++ b/src/PvfScript.h @@ -0,0 +1,36 @@ +#pragma once +#include +#include + +class PvfReader; + +enum PvfScriptType +{ + Animation, + Text, + Document +}; + +class PvfScript +{ +public: + virtual auto unpack() -> void = 0; + inline auto& getType() const { return type; } +protected: + PvfScriptType type; + +}; + + +class PvfTextScript : public PvfScript +{ +public: + PvfTextScript(const uint8_t* buffer, int32_t len, PvfReader* reader); + auto unpack() -> void override; + inline auto& getContent() const { return str; } +private: + const uint8_t* buffer = nullptr; + PvfReader* reader = nullptr; + int32_t len = 0; + std::string str; +}; diff --git a/src/PvfString.cpp b/src/PvfString.cpp new file mode 100644 index 0000000..829ffa2 --- /dev/null +++ b/src/PvfString.cpp @@ -0,0 +1,53 @@ + +#include +#include +#include +#include +#include +#include "PvfString.h" + + +auto PvfString::startWith(const std::string& str, const std::string& start) -> bool +{ + return str.compare(0, start.size(), start) == 0; +} + +auto PvfString::contains(const std::string& str, const std::string& start) -> bool +{ + return str.find(start) != std::string::npos; +} + +auto PvfString::endWith(const std::string& str, const std::string& start) -> bool +{ + return str.compare(str.length() - start.length(), start.size(), start) == 0; +} + +auto PvfString::split(std::string input, const std::string& delimiter, std::vector& outs) -> void +{ + size_t pos = 0; + std::string token; + while ((pos = input.find(delimiter)) != std::string::npos) + { + token = input.substr(0, pos); + outs.push_back(token); + input.erase(0, pos + delimiter.length()); + } + outs.push_back(input); +} + + +auto PvfString::trim(std::string& str, const std::string& trimStr) -> void +{ + if (!str.empty()) + { + str.erase(0, str.find_first_not_of(trimStr)); + str.erase(str.find_last_not_of(trimStr) + 1); + } +} + +auto PvfString::toLower(std::string& data) -> void +{ + std::transform(data.begin(), data.end(), data.begin(), + [](unsigned char c) { return std::tolower(c); }); +} + diff --git a/src/PvfString.h b/src/PvfString.h new file mode 100644 index 0000000..a2a15fe --- /dev/null +++ b/src/PvfString.h @@ -0,0 +1,23 @@ + +#pragma once +#include +#include +#include +#include +#include + +namespace PvfString +{ + + auto split(std::string input, const std::string& delimiter, std::vector& outs) -> void; + auto startWith(const std::string& str, const std::string& start) -> bool; + auto contains(const std::string& str, const std::string& start) -> bool; + auto endWith(const std::string& str, const std::string& start) -> bool; + auto trim(std::string& str,const std::string & trimStr = " ") -> void; + auto toLower(std::string& data) -> void; +#ifdef _WIN32 + static const std::string delimiter = "\\"; +#else + static const std::string delimiter = "/"; +#endif +}; diff --git a/src/ValueType.h b/src/ValueType.h new file mode 100644 index 0000000..c9799fe --- /dev/null +++ b/src/ValueType.h @@ -0,0 +1,14 @@ +#pragma once + +enum ValueType +{ + Int = 2, + IntEx, + Float, + Section, + Command, + String, + CommandSeparator, + StringLinkIndex, + StringLink +}; \ No newline at end of file