├── .gitignore ├── .gitmodules ├── include └── wz │ ├── NumTypes.hpp │ ├── Directory.hpp │ ├── Property.hpp │ ├── File.hpp │ ├── Types.hpp │ ├── Wz.hpp │ ├── Node.hpp │ ├── Reader.hpp │ └── Keys.hpp ├── license ├── main └── main.cpp ├── src ├── Wz.cpp ├── Directory.cpp ├── Keys.cpp ├── Property.cpp ├── Reader.cpp ├── File.cpp └── Node.cpp ├── CMakeLists.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /.vs 2 | /Debug 3 | /Release 4 | /x64 5 | /cmake-build-debug 6 | /.idea 7 | /build 8 | 9 | *.sln 10 | *.vcproj 11 | *.vcxproj 12 | *.vcxproj.* 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "mio"] 2 | path = mio 3 | url = https://github.com/mandreyel/mio 4 | [submodule "AES"] 5 | path = AES 6 | url = https://github.com/SeaniaTwix/AES 7 | [submodule "zlib"] 8 | path = zlib 9 | url = https://github.com/madler/zlib.git 10 | ignore = dirty 11 | -------------------------------------------------------------------------------- /include/wz/NumTypes.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | using u8 = uint8_t; 6 | using u16 = uint16_t; 7 | using u32 = uint32_t; 8 | using u64 = uint64_t; 9 | 10 | using i8 = int8_t; 11 | using i16 = int16_t; 12 | using i32 = int32_t; 13 | using i64 = int64_t; 14 | 15 | using f32 = float; 16 | using f64 = double; -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | 2 | Noa `자유` 오픈소스 라이센스 20200808 3 | 4 | 본 라이센스 문서는 어떠한 경우에서라도 한국어로 기술되며 그 이외의 버전은 효력이 없습니다. 5 | 다음과 같은 내용을 허가합니다. 6 | - 소스 코드를 배포 7 | - 소스 코드를 수정 8 | - 수정한 소스 코드를 배포 9 | - 배포시 원저자를 변경하거나 익명으로 표시, 혹은 표시하지 않음 10 | - 서로 호환되는 다른 제3의 라이센스 적용 11 | 다음과 같은 내용을 불허합니다. 12 | - 수정 시 공개의 의무가 있는 라이센스를 이 라이센스가 적용된 저작물에 적용 13 | - 소스 코드 변경시 원저자의 이름으로 배포 14 | 이 저작물과 원저자는 다음을 보증하지 않습니다. 15 | - 소프트웨어의 작동 및 안전과 안정성에 관한 모든 것 16 | - 사용시 모든 법적 및 그 이외의 책임 17 | 배포시 의무 사항은 다음과 같습니다. 18 | - 원 저작물에서 변경사항이 있을 경우 변경사항을 고지 19 | - 이 라이센스를 그대로 유지 20 | -------------------------------------------------------------------------------- /include/wz/Directory.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Node.hpp" 4 | #include "NumTypes.hpp" 5 | 6 | namespace wz { 7 | class Directory : public Node { 8 | public: 9 | explicit Directory(File* root_file, bool img, int new_size, int new_checksum, unsigned int new_offset); 10 | 11 | [[nodiscard]] 12 | u32 get_offset() const; 13 | 14 | [[nodiscard]] 15 | bool is_image() const; 16 | 17 | [[maybe_unused]] 18 | bool parse_image(Node* node); 19 | 20 | private: 21 | bool image; 22 | int size; 23 | int checksum; 24 | unsigned int offset; 25 | }; 26 | } -------------------------------------------------------------------------------- /main/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #define U8 static_cast 15 | #define IV4(A, B, C, D) \ 16 | { \ 17 | U8(A), U8(B), U8(C), U8(D) \ 18 | } 19 | 20 | int main() 21 | { 22 | const auto iv = IV4(0xb9, 0x7d, 0x63, 0xe9); 23 | wz::File file(iv, "Map.wz"); 24 | 25 | if (file.parse()) 26 | { 27 | wz::Node *node=file.get_root()->find_from_path(u"Map/Map1/101000000.img"); 28 | return 1; 29 | } 30 | return 0; 31 | } -------------------------------------------------------------------------------- /src/Wz.cpp: -------------------------------------------------------------------------------- 1 | #include "Wz.hpp" 2 | #include "Property.hpp" 3 | 4 | u32 wz::get_version_hash(i32 encryptedVersion, i32 realVersion) { 5 | i32 versionHash = 0; 6 | auto versionString = std::to_string(realVersion); 7 | 8 | auto len = versionString.size(); 9 | 10 | for (int i = 0; i < len; ++i) { 11 | versionHash = (32 * versionHash) + static_cast(versionString[i]) + 1; 12 | } 13 | 14 | #define HASHING(V, S) ((V >> S##u) & 0xFFu) 15 | #define AUTO_HASH(V) (0xFFu ^ HASHING(V, 24) ^ HASHING(V, 16) ^ HASHING(V, 8) ^ V & 0xFFu) 16 | 17 | i32 decryptedVersionNumber = AUTO_HASH(static_cast(versionHash)); 18 | 19 | if (encryptedVersion == decryptedVersionNumber) { 20 | return static_cast(versionHash); 21 | } 22 | 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /src/Directory.cpp: -------------------------------------------------------------------------------- 1 | #include "Directory.hpp" 2 | 3 | wz::Directory::Directory(File *root_file, bool img, int new_size, int new_checksum, unsigned int new_offset) 4 | : image(img), size(new_size), checksum(new_checksum), offset(new_offset), Node(img ? Type::Image : Type::Directory, root_file) 5 | { 6 | } 7 | 8 | u32 wz::Directory::get_offset() const 9 | { 10 | return offset; 11 | } 12 | 13 | bool wz::Directory::is_image() const 14 | { 15 | return image; 16 | } 17 | 18 | bool wz::Directory::parse_image(Node *node) 19 | { 20 | if (is_image()) 21 | { 22 | node->reader = reader; 23 | node->path = this->path; 24 | const auto current_offset = get_offset(); 25 | reader->set_position(current_offset); 26 | if (reader->is_wz_image()) 27 | { 28 | return parse_property_list(node, current_offset); 29 | } 30 | } 31 | return false; 32 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17) 2 | project(wzlibcpp) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | 6 | add_subdirectory(zlib) 7 | 8 | set(ZLIB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/zlib) 9 | 10 | set(MIO_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/mio/include) 11 | 12 | set(WZLIB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include/wz ${CMAKE_CURRENT_SOURCE_DIR}/include) 13 | 14 | include_directories( 15 | ${CMAKE_CURRENT_SOURCE_DIR} 16 | ${WZLIB_INCLUDE_DIR} 17 | ${MIO_INCLUDE_DIR} 18 | ${ZLIB_INCLUDE_DIR}) 19 | 20 | file(GLOB SOURCE_FILES src/*.cpp) 21 | file(GLOB AES_SOURCE_FILES AES/AES.cpp) 22 | 23 | add_library(wzlib ${SOURCE_FILES} ${AES_SOURCE_FILES}) 24 | target_link_libraries(wzlib zlibstatic) 25 | 26 | if(WIN32) 27 | project(wzlibtest) 28 | 29 | add_executable(wzlibtest main/main.cpp ${SOURCE_FILES}) 30 | target_link_libraries( 31 | wzlibtest 32 | PRIVATE 33 | wzlib 34 | zlibstatic 35 | ) 36 | endif() -------------------------------------------------------------------------------- /include/wz/Property.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "Node.hpp" 9 | #include "Keys.hpp" 10 | 11 | namespace wz 12 | { 13 | template 14 | class Property : public Node 15 | { 16 | public: 17 | explicit Property(const Type &new_type, File *root_file) : Node(new_type, root_file) {} 18 | 19 | explicit Property(const Type &new_type, File *root_file, T new_data) 20 | : data(new_data), Node(new_type, root_file) {} 21 | 22 | void set(T new_data) 23 | { 24 | data = new_data; 25 | } 26 | 27 | [[maybe_unused]] const T &get() const 28 | { 29 | return data; 30 | } 31 | 32 | [[nodiscard]] [[maybe_unused]] std::vector get_raw_data(); 33 | 34 | [[nodiscard]] [[maybe_unused]] wz::Node *get_uol(); 35 | 36 | private: 37 | T data; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /include/wz/File.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Node.hpp" 4 | #include "Reader.hpp" 5 | #include "Wz.hpp" 6 | #include "Keys.hpp" 7 | 8 | namespace wz 9 | { 10 | class File final 11 | { 12 | 13 | public: 14 | [[maybe_unused]] explicit File(const std::initializer_list &new_iv, unsigned char *wz_buf, unsigned int wz_size); 15 | 16 | [[maybe_unused]] explicit File(const std::initializer_list &new_iv, const char *path); 17 | 18 | [[maybe_unused]] explicit File(u8 *new_iv, const char *path); 19 | 20 | ~File(); 21 | 22 | [[maybe_unused]] bool parse(const wzstring &name = u""); 23 | 24 | [[maybe_unused]] [[nodiscard]] Node *get_root() const; 25 | Node &get_child(const wzstring &name); 26 | 27 | MutableKey key; 28 | 29 | private: 30 | // u8* key; 31 | u8 *iv; 32 | 33 | Node *root; 34 | 35 | Description desc{}; 36 | 37 | Reader reader; 38 | 39 | bool parse_directories(Node *node); 40 | 41 | u32 get_wz_offset(); 42 | 43 | void init_key(); 44 | 45 | friend class Node; 46 | }; 47 | } -------------------------------------------------------------------------------- /include/wz/Types.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "NumTypes.hpp" 5 | 6 | namespace wz { 7 | 8 | using wzstring = std::u16string; 9 | 10 | struct WzNull { 11 | }; 12 | 13 | struct WzSubProp { 14 | }; 15 | 16 | struct WzConvex { 17 | }; 18 | 19 | struct WzUOL { 20 | [[maybe_unused]] 21 | wzstring uol; 22 | }; 23 | 24 | struct WzCanvas { 25 | i32 width; 26 | i32 height; 27 | i32 format; 28 | i32 format2; 29 | bool is_encrypted; 30 | i32 size; 31 | i32 uncompressed_size; 32 | size_t offset; 33 | 34 | WzCanvas() 35 | : width(0), height(0), 36 | format(0), format2(0), 37 | is_encrypted(false), size(0), uncompressed_size(0), 38 | offset(0) {} 39 | }; 40 | 41 | struct WzSound { 42 | i32 length; 43 | i32 frequency; 44 | i32 size; 45 | size_t offset; 46 | 47 | WzSound() : length(0), frequency(0), size(0), offset(0) {} 48 | }; 49 | 50 | struct WzVec2D { 51 | 52 | i32 x; 53 | i32 y; 54 | 55 | WzVec2D() : x(0), y(0) {} 56 | WzVec2D(int new_x, int new_y) : x(new_x), y(new_y) {} 57 | }; 58 | } -------------------------------------------------------------------------------- /include/wz/Wz.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Types.hpp" 4 | 5 | ////////////////////////////////////////////////////////////////////////// 6 | 7 | namespace wz { 8 | enum class Type : u8 { 9 | NotSet = 0x00, 10 | Directory = 0x10, 11 | Image = 0x20, 12 | Property = 0x30, 13 | 14 | Null = 0x31, 15 | Int = 0x32, 16 | UnsignedShort = 0x33, 17 | Float = 0x34, 18 | Double = 0x35, 19 | String = 0x36, 20 | 21 | SubProperty = 0x37, 22 | Canvas = 0x38, 23 | Vector2D = 0x39, 24 | Convex2D = 0x3A, 25 | Sound = 0x3B, 26 | UOL = 0x3C, 27 | }; 28 | 29 | constexpr u8 bit(const Type& type) { 30 | return static_cast(type); 31 | } 32 | 33 | namespace keys { 34 | [[maybe_unused]] 35 | const unsigned char gms[4] = { 36 | 0x4D, 0x23, 0xC7, 0x2B 37 | }; 38 | 39 | [[maybe_unused]] 40 | const unsigned char kms[4] = { 41 | 0xB9, 0x7D, 0x63, 0xE9 42 | }; 43 | } 44 | } 45 | 46 | ////////////////////////////////////////////////////////////////////////// 47 | 48 | 49 | [[deprecated]] 50 | void WzGenKeys(const unsigned char* IV); 51 | 52 | namespace wz { 53 | 54 | struct Description { 55 | u32 start; 56 | u32 hash; 57 | i16 version; 58 | }; 59 | 60 | u32 get_version_hash(i32 encryptedVersion, i32 realVersion); 61 | 62 | [[deprecated]] 63 | void initAES(const u8* iv); 64 | 65 | } -------------------------------------------------------------------------------- /src/Keys.cpp: -------------------------------------------------------------------------------- 1 | #include "Keys.hpp" 2 | 3 | wz::MutableKey::MutableKey(const array& new_iv, std::vector new_aes_key) 4 | : iv(new_iv), aes_key(std::move(new_aes_key)) { 5 | } 6 | 7 | u8& wz::MutableKey::operator[](size_t index) { 8 | if (keys.empty() || keys.size() <= index) { 9 | ensure_key_size(index + 1); 10 | } 11 | return keys[index]; 12 | } 13 | 14 | void wz::MutableKey::ensure_key_size(size_t size) { 15 | size = static_cast(ceil(1.0 * size / batch_size) * batch_size); 16 | decltype(keys) new_keys; 17 | new_keys.reserve(size); 18 | if (*reinterpret_cast(iv.data()) == 0) { 19 | keys = new_keys; 20 | return; 21 | } 22 | 23 | auto start_index = 0; 24 | 25 | if (!keys.empty()) { 26 | std::copy(keys.begin(), keys.end(), new_keys.begin()); 27 | start_index = keys.size(); 28 | } 29 | 30 | AES aes(256, 128); 31 | 32 | for (int i = start_index; i < size; i += 16) { 33 | if (i == 0) { 34 | u8 block[16]; 35 | for (int n = 0; n < 16; ++n) { 36 | block[n] = iv[n % 4]; 37 | } 38 | u32 out_len; 39 | auto* eb = aes.EncryptECB(&block[0], 16, aes_key.data(), out_len); 40 | u8 buf[16]; 41 | memcpy(buf, eb, 16); 42 | delete[] eb; 43 | for (unsigned char & n : buf) { 44 | new_keys.emplace_back(n); 45 | } 46 | } else { 47 | size_t len = 16; 48 | u32 out_len; 49 | auto* eb = aes.EncryptECB(new_keys.data() + i - 16, len, aes_key.data(), out_len); 50 | u8 buf[len]; 51 | memcpy(buf, eb, 16); 52 | delete[] eb; 53 | for (unsigned char & n : buf) { 54 | new_keys.emplace_back(n); 55 | } 56 | 57 | } 58 | } 59 | 60 | keys = new_keys; 61 | 62 | } 63 | 64 | -------------------------------------------------------------------------------- /include/wz/Node.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "Wz.hpp" 8 | #include "Reader.hpp" 9 | #include "Types.hpp" 10 | 11 | namespace wz 12 | { 13 | 14 | class Node; 15 | class File; 16 | 17 | typedef std::vector WzList; 18 | typedef std::map WzMap; 19 | 20 | class Node 21 | { 22 | public: 23 | explicit Node(); 24 | explicit Node(const Type &new_type, File *root_file); 25 | 26 | virtual ~Node(); 27 | 28 | Node &operator[](const wzstring &name); 29 | 30 | virtual void appendChild(const wzstring &name, Node *node); 31 | 32 | Node *get_child(const wzstring &name); 33 | 34 | Node *get_child(const std::string &name); 35 | 36 | [[maybe_unused]] [[nodiscard]] virtual const WzMap &get_children() const; 37 | 38 | [[maybe_unused]] [[nodiscard]] virtual Node *get_parent() const; 39 | 40 | [[nodiscard]] [[maybe_unused]] size_t children_count() const; 41 | 42 | [[maybe_unused]] WzMap::iterator begin(); 43 | 44 | [[maybe_unused]] WzMap::iterator end(); 45 | 46 | [[maybe_unused]] [[nodiscard]] Type get_type() const; 47 | 48 | [[nodiscard]] bool is_property() const; 49 | 50 | Node *find_from_path(const std::u16string &path); 51 | 52 | Node *find_from_path(const std::string &path); 53 | 54 | public: 55 | Type type; 56 | 57 | Node *parent; 58 | WzMap children; 59 | 60 | File *file; 61 | Reader *reader = nullptr; 62 | 63 | std::u16string path = u""; 64 | 65 | bool parse_property_list(Node *target, size_t offset); 66 | void parse_extended_prop(const wzstring &name, Node *target, const size_t &offset); 67 | WzCanvas parse_canvas_property(); 68 | WzSound parse_sound_property(); 69 | 70 | [[nodiscard]] u8 *get_iv() const; 71 | [[nodiscard]] wz::MutableKey &get_key() const; 72 | 73 | friend class Directory; 74 | }; 75 | 76 | } -------------------------------------------------------------------------------- /include/wz/Reader.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "NumTypes.hpp" 5 | #include "Keys.hpp" 6 | 7 | namespace wz 8 | { 9 | 10 | using wzstring = std::u16string; 11 | 12 | class Reader final 13 | { 14 | public: 15 | explicit Reader(wz::MutableKey &new_key, const char *file_path); 16 | 17 | template 18 | [[nodiscard]] T read() 19 | { 20 | T result = *reinterpret_cast(&mmap[cursor]); 21 | cursor += sizeof(decltype(result)); 22 | return result; 23 | } 24 | 25 | void skip(const size_t &size); 26 | 27 | [[nodiscard]] u8 read_byte(); 28 | 29 | [[maybe_unused]] [[nodiscard]] std::vector read_bytes(const size_t &len); 30 | 31 | /* 32 | * read string until **null terminated** 33 | */ 34 | [[nodiscard]] wzstring read_string(); 35 | 36 | [[nodiscard]] wzstring read_string(const size_t &len); 37 | 38 | [[nodiscard]] i32 read_compressed_int(); 39 | 40 | i16 read_i16(); 41 | 42 | [[nodiscard]] wzstring read_wz_string(); 43 | 44 | wzstring read_string_block(const size_t &offset); 45 | 46 | template 47 | [[nodiscard]] T read_wz_string_from_offset(const size_t &offset, wzstring &out) 48 | { 49 | auto prev = cursor; 50 | set_position(offset); 51 | auto result = read(); 52 | out = read_wz_string(); 53 | set_position(prev); 54 | return result; 55 | } 56 | 57 | wzstring read_wz_string_from_offset(const size_t &offset); 58 | 59 | [[nodiscard]] size_t get_position() const; 60 | 61 | void set_position(const size_t &size); 62 | 63 | [[nodiscard]] mio::mmap_source::size_type size() const; 64 | 65 | [[nodiscard]] bool is_wz_image(); 66 | 67 | void set_key(MutableKey &new_key); 68 | 69 | private: 70 | MutableKey &key; 71 | 72 | size_t cursor = 0; 73 | 74 | mio::mmap_source mmap; 75 | 76 | explicit Reader() = delete; 77 | 78 | friend class Node; 79 | }; 80 | } 81 | -------------------------------------------------------------------------------- /src/Property.cpp: -------------------------------------------------------------------------------- 1 | #include "Property.hpp" 2 | #include "Types.hpp" 3 | 4 | // get ARGB4444 piexl,ARGB8888 piexl and others..... 5 | template <> 6 | std::vector wz::Property::get_raw_data() 7 | { 8 | WzCanvas canvas = get(); 9 | 10 | std::vector data_stream; 11 | reader->set_position(canvas.offset); 12 | size_t end_offset = reader->get_position() + canvas.size; 13 | unsigned long uncompressed_len = canvas.uncompressed_size; 14 | u8 *uncompressed = new u8[uncompressed_len]; 15 | if (!canvas.is_encrypted) 16 | { 17 | for (size_t i = 0; i < canvas.size; ++i) 18 | { 19 | data_stream.push_back(reader->read_byte()); 20 | } 21 | uncompress(uncompressed, (unsigned long *)&uncompressed_len, data_stream.data(), data_stream.size()); 22 | } 23 | else 24 | { 25 | auto wz_key = get_key(); 26 | 27 | while (reader->get_position() < end_offset) 28 | { 29 | auto block_size = reader->read(); 30 | for (size_t i = 0; i < block_size; ++i) 31 | { 32 | auto n = wz_key[i]; 33 | data_stream.push_back( 34 | static_cast(reader->read_byte() ^ n)); 35 | } 36 | } 37 | uncompress(uncompressed, (unsigned long *)&uncompressed_len, data_stream.data(), data_stream.size()); 38 | } 39 | 40 | std::vector pixel_stream(uncompressed, uncompressed + uncompressed_len); 41 | delete[] uncompressed; 42 | return pixel_stream; 43 | } 44 | 45 | // get Sound node raw data 46 | template <> 47 | std::vector wz::Property::get_raw_data() 48 | { 49 | WzSound sound = get(); 50 | std::vector data_stream; 51 | 52 | reader->set_position(sound.offset); 53 | size_t end_offset = reader->get_position() + sound.size; 54 | 55 | while (reader->get_position() < end_offset) 56 | { 57 | data_stream.push_back(static_cast(reader->read_byte())); 58 | } 59 | return data_stream; 60 | } 61 | 62 | // get uol By uol node 63 | template <> 64 | wz::Node *wz::Property::get_uol() 65 | { 66 | auto path = get().uol; 67 | auto uol_node = parent->find_from_path(path); 68 | while (uol_node->type == wz::Type::UOL) 69 | { 70 | path = dynamic_cast *>(uol_node)->get().uol; 71 | uol_node = uol_node->parent->find_from_path(path); 72 | } 73 | 74 | return uol_node; 75 | } 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wzlibcpp 2 | 3 | modern first, more easier, cleaner api and support cross-platform 4 | 5 | # Status 6 | 7 | ⬜️ utf8 formatted string with `std::string` 8 | 9 | ⬜️ remove all of older (deprecated) api - 10 | `[[deprecated]]` attribute is currently marked. 11 | 12 | ☑️ modern api 13 | 14 | ☑️ cross-platform 15 | 16 | # Dependencies 17 | 18 | * zlib 19 | * mio - for mmap (aka file mapping in windows) 20 | 21 | # Usage 22 | 23 | ```cpp 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | template 35 | constexpr auto string(T iterable) { 36 | return std::wstring{iterable.begin(), iterable.end()}; 37 | } 38 | 39 | template 40 | constexpr auto back(T iterable) { 41 | return std::string{iterable.begin(), iterable.end()}; 42 | } 43 | 44 | template 45 | T pop(std::vector& vec) { 46 | auto last = vec.back(); 47 | vec.pop_back(); 48 | return last; 49 | } 50 | 51 | wz::WzMap find_from_path(wz::Node* root, const std::wstring& path) { 52 | std::vector next{}; 53 | std::string s{}; 54 | s.assign(path.begin(), path.end()); 55 | pystring::split(s, next, "/"); 56 | std::reverse(next.begin(), next.end()); 57 | const auto current = string(pop(next)); 58 | for (const auto& it : *root) { 59 | if (it.first == current) { 60 | if (next.empty()) { 61 | return root->get_children(); 62 | } 63 | 64 | std::reverse(next.begin(), next.end()); 65 | return find_from_path(root, string(pystring::join("/", next))); 66 | } 67 | } 68 | return {}; 69 | } 70 | 71 | #define U8 static_cast 72 | #define IV4(A,B,C,D) {U8(A),U8(B),U8(C),U8(D)} 73 | 74 | int main() { 75 | 76 | const auto iv = IV4(0x45, 0x50, 0x33, 0x01); 77 | wz::File file(iv, "C:/classic maple/Character_original.wz"); 78 | 79 | if (file.parse()) { 80 | auto node = find_from_path(file.get_root(), L"00002000.img"); 81 | 82 | for (const auto& it : node) { 83 | std::wcout << it.first << ", " << it.second.size() << std::endl; 84 | auto* dir = dynamic_cast(it.second[0]); 85 | if (dir && dir->is_image()) { 86 | auto* image = new wz::Node(); 87 | dir->parse_image(image); 88 | 89 | for (const auto& n : *image) { 90 | std::wcout << n.first << std::endl; 91 | } 92 | } 93 | } 94 | } 95 | 96 | return 0; 97 | } 98 | ``` 99 | 100 | ## output 101 | https://gist.github.com/SeaniaTwix/f8b7e7cc34c5761e9679efa491816b63 -------------------------------------------------------------------------------- /src/Reader.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "Reader.hpp" 5 | #include "Keys.hpp" 6 | 7 | wz::Reader::Reader(wz::MutableKey &new_key, const char *file_path) 8 | : cursor(0), key(new_key) 9 | { 10 | std::error_code error_code; 11 | mmap = mio::make_mmap_source(file_path, error_code); 12 | } 13 | 14 | u8 wz::Reader::read_byte() 15 | { 16 | return mmap[cursor++]; 17 | } 18 | 19 | [[maybe_unused]] std::vector wz::Reader::read_bytes(const size_t &len) 20 | { 21 | std::vector result(len); 22 | 23 | for (size_t i = 0; i < len; ++i) 24 | { 25 | result.emplace_back(read_byte()); 26 | cursor++; 27 | } 28 | 29 | return result; 30 | } 31 | 32 | wz::wzstring wz::Reader::read_string() 33 | { 34 | wz::wzstring result{}; 35 | 36 | while (true) 37 | { 38 | auto c = static_cast(read_byte()); 39 | if (!c) 40 | break; 41 | result.push_back(c); 42 | } 43 | 44 | return result; 45 | } 46 | 47 | wz::wzstring wz::Reader::read_string(const size_t &len) 48 | { 49 | wz::wzstring result{}; 50 | 51 | for (int i = 0; i < len; ++i) 52 | { 53 | result.push_back(read_byte()); 54 | } 55 | 56 | return result; 57 | } 58 | 59 | void wz::Reader::set_position(const size_t &size) 60 | { 61 | cursor = size; 62 | } 63 | 64 | size_t wz::Reader::get_position() const 65 | { 66 | return cursor; 67 | } 68 | 69 | void wz::Reader::skip(const size_t &size) 70 | { 71 | cursor += size; 72 | } 73 | 74 | i32 wz::Reader::read_compressed_int() 75 | { 76 | i32 result = static_cast(read()); 77 | if (result == INT8_MIN) 78 | return read(); 79 | return result; 80 | } 81 | 82 | i16 wz::Reader::read_i16() 83 | { 84 | i16 result = static_cast(read()); 85 | return result; 86 | } 87 | 88 | wz::wzstring wz::Reader::read_wz_string() 89 | { 90 | auto len8 = read(); 91 | 92 | if (len8 == 0) 93 | return {}; 94 | 95 | i32 len; 96 | 97 | if (len8 > 0) 98 | { 99 | u16 mask = 0xAAAA; 100 | 101 | len = len8 == 127 ? read() : len8; 102 | 103 | if (len <= 0) 104 | { 105 | return {}; 106 | } 107 | 108 | wz::wzstring result{}; 109 | 110 | for (int i = 0; i < len; ++i) 111 | { 112 | auto encryptedChar = read(); 113 | encryptedChar ^= mask; 114 | encryptedChar ^= *reinterpret_cast(&key[2 * i]); 115 | result.push_back(encryptedChar); 116 | mask++; 117 | } 118 | 119 | return result; 120 | } 121 | 122 | u8 mask = 0xAA; 123 | 124 | if (len8 == -128) 125 | { 126 | len = read(); 127 | } 128 | else 129 | { 130 | len = -len8; 131 | } 132 | 133 | if (len <= 0) 134 | { 135 | return {}; 136 | } 137 | 138 | wz::wzstring result{}; 139 | 140 | for (int n = 0; n < len; ++n) 141 | { 142 | u8 encryptedChar = read_byte(); 143 | encryptedChar ^= mask; 144 | encryptedChar ^= key[n]; 145 | result.push_back(static_cast(encryptedChar)); 146 | mask++; 147 | } 148 | 149 | return result; 150 | } 151 | 152 | mio::mmap_source::size_type wz::Reader::size() const 153 | { 154 | return mmap.size(); 155 | } 156 | 157 | bool wz::Reader::is_wz_image() 158 | { 159 | if (read() != 0x73) 160 | return false; 161 | if (read_wz_string() != u"Property") 162 | return false; 163 | if (read() != 0) 164 | return false; 165 | return true; 166 | } 167 | 168 | wz::wzstring wz::Reader::read_string_block(const size_t &offset) 169 | { 170 | switch (read()) 171 | { 172 | case 0: 173 | [[fallthrough]]; 174 | case 0x73: 175 | return read_wz_string(); 176 | case 1: 177 | [[fallthrough]]; 178 | case 0x1B: 179 | return read_wz_string_from_offset(offset + read()); 180 | default: 181 | { 182 | assert(0); 183 | } 184 | } 185 | return {}; 186 | } 187 | 188 | wz::wzstring wz::Reader::read_wz_string_from_offset(const size_t &offset) 189 | { 190 | auto prev = get_position(); 191 | set_position(offset); 192 | auto result = read_wz_string(); 193 | set_position(prev); 194 | return result; 195 | } 196 | 197 | void wz::Reader::set_key(wz::MutableKey &new_key) 198 | { 199 | key = new_key; 200 | } -------------------------------------------------------------------------------- /include/wz/Keys.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "NumTypes.hpp" 4 | #include 5 | #include 6 | #include 7 | #include "AES/AES.h" 8 | 9 | namespace wz { 10 | static const u8 AesKey1[] = { 11 | 0x13, 0x52, 0x2A, 0x5B, 0x08, 0x02, 0x10, 0x60, 12 | 0x06, 0x02, 0x43, 0x0F, 0xB4, 0x4B, 0x35, 0x05, 13 | 0x1B, 0x0A, 0x5F, 0x09, 0x0F, 0x50, 0x0C, 0x1B, 14 | 0x33, 0x55, 0x01, 0x09, 0x52, 0xDE, 0xC7, 0x1E 15 | }; 16 | 17 | static const u8 AesKey2[] = { 18 | 0x13, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 19 | 0x06, 0x00, 0x00, 0x00, 0xB4, 0x00, 0x00, 0x00, 20 | 0x1B, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 21 | 0x33, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00 22 | }; 23 | 24 | static const u8 ShuffleKey[] = { 25 | 0xEC, 0x3F, 0x77, 0xA4, 0x45, 0xD0, 0x71, 0xBF, 26 | 0xB7, 0x98, 0x20, 0xFC, 0x4B, 0xE9, 0xB3, 0xE1, 27 | 0x5C, 0x22, 0xF7, 0x0C, 0x44, 0x1B, 0x81, 0xBD, 28 | 0x63, 0x8D, 0xD4, 0xC3, 0xF2, 0x10, 0x19, 0xE0, 29 | 0xFB, 0xA1, 0x6E, 0x66, 0xEA, 0xAE, 0xD6, 0xCE, 30 | 0x06, 0x18, 0x4E, 0xEB, 0x78, 0x95, 0xDB, 0xBA, 31 | 0xB6, 0x42, 0x7A, 0x2A, 0x83, 0x0B, 0x54, 0x67, 32 | 0x6D, 0xE8, 0x65, 0xE7, 0x2F, 0x07, 0xF3, 0xAA, 33 | 0x27, 0x7B, 0x85, 0xB0, 0x26, 0xFD, 0x8B, 0xA9, 34 | 0xFA, 0xBE, 0xA8, 0xD7, 0xCB, 0xCC, 0x92, 0xDA, 35 | 0xF9, 0x93, 0x60, 0x2D, 0xDD, 0xD2, 0xA2, 0x9B, 36 | 0x39, 0x5F, 0x82, 0x21, 0x4C, 0x69, 0xF8, 0x31, 37 | 0x87, 0xEE, 0x8E, 0xAD, 0x8C, 0x6A, 0xBC, 0xB5, 38 | 0x6B, 0x59, 0x13, 0xF1, 0x04, 0x00, 0xF6, 0x5A, 39 | 0x35, 0x79, 0x48, 0x8F, 0x15, 0xCD, 0x97, 0x57, 40 | 0x12, 0x3E, 0x37, 0xFF, 0x9D, 0x4F, 0x51, 0xF5, 41 | 0xA3, 0x70, 0xBB, 0x14, 0x75, 0xC2, 0xB8, 0x72, 42 | 0xC0, 0xED, 0x7D, 0x68, 0xC9, 0x2E, 0x0D, 0x62, 43 | 0x46, 0x17, 0x11, 0x4D, 0x6C, 0xC4, 0x7E, 0x53, 44 | 0xC1, 0x25, 0xC7, 0x9A, 0x1C, 0x88, 0x58, 0x2C, 45 | 0x89, 0xDC, 0x02, 0x64, 0x40, 0x01, 0x5D, 0x38, 46 | 0xA5, 0xE2, 0xAF, 0x55, 0xD5, 0xEF, 0x1A, 0x7C, 47 | 0xA7, 0x5B, 0xA6, 0x6F, 0x86, 0x9F, 0x73, 0xE6, 48 | 0x0A, 0xDE, 0x2B, 0x99, 0x4A, 0x47, 0x9C, 0xDF, 49 | 0x09, 0x76, 0x9E, 0x30, 0x0E, 0xE4, 0xB2, 0x94, 50 | 0xA0, 0x3B, 0x34, 0x1D, 0x28, 0x0F, 0x36, 0xE3, 51 | 0x23, 0xB4, 0x03, 0xD8, 0x90, 0xC8, 0x3C, 0xFE, 52 | 0x5E, 0x32, 0x24, 0x50, 0x1F, 0x3A, 0x43, 0x8A, 53 | 0x96, 0x41, 0x74, 0xAC, 0x52, 0x33, 0xF0, 0xD9, 54 | 0x29, 0x80, 0xB1, 0x16, 0xD3, 0xAB, 0x91, 0xB9, 55 | 0x84, 0x7F, 0x61, 0x1E, 0xCF, 0xC5, 0xD1, 0x56, 56 | 0x3D, 0xCA, 0xF4, 0x05, 0xC6, 0xE5, 0x08, 0x49 57 | }; 58 | 59 | static const u8 DefaultKey[] = { 60 | 0xC6, 0x50, 0x53, 0xF2, 0xA8, 0x42, 0x9D, 0x7F, 61 | 0x77, 0x09, 0x1D, 0x26, 0x42, 0x53, 0x88, 0x7C 62 | }; 63 | 64 | static const u8 ShuffleConstant[] = { 65 | 0xF2, 0x53, 0x50, 0xC6 66 | }; 67 | 68 | static const u8 user_key[] = { 69 | 0x13, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 70 | 0x2A, 0x00, 0x00, 0x00, 0x5B, 0x00, 0x00, 0x00, 71 | 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 72 | 0x10, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 73 | 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 74 | 0x43, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 75 | 0xB4, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x00, 0x00, 76 | 0x35, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 77 | 0x1B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 78 | 0x5F, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 79 | 0x0F, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 80 | 0x0C, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 81 | 0x33, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 82 | 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 83 | 0x52, 0x00, 0x00, 0x00, 0xDE, 0x00, 0x00, 0x00, 84 | 0xC7, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00 85 | }; 86 | 87 | static const u32 OffsetKey = 0x581C3F6D; 88 | 89 | static const u32 HeaderMagic = 0x31474B50; 90 | 91 | static std::vector get_trimmed_user_key() { 92 | std::vector num(32); 93 | for (int i = 0; i < 128; i += 16) { 94 | num[i / 4] = wz::user_key[i]; 95 | } 96 | return num; 97 | } 98 | 99 | class MutableKey final { 100 | public: 101 | explicit MutableKey() = default; 102 | 103 | explicit MutableKey(const std::array& new_iv, std::vector new_aes_key); 104 | 105 | u8& operator[] (size_t index); 106 | 107 | private: 108 | static constexpr auto batch_size = 0x10000; 109 | std::array iv {0, 0, 0, 0}; 110 | std::vector aes_key; 111 | std::vector keys; 112 | 113 | void ensure_key_size(size_t size); 114 | }; 115 | 116 | 117 | } -------------------------------------------------------------------------------- /src/File.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "File.hpp" 3 | #include "Wz.hpp" 4 | #include "Directory.hpp" 5 | 6 | [[maybe_unused]] wz::File::File(const std::initializer_list &new_iv, const char *path) 7 | : reader(Reader(key, path)), root(new Node(Type::NotSet, this)), key(), iv(nullptr) 8 | { 9 | iv = new u8[4]; 10 | memcpy(iv, new_iv.begin(), 4); 11 | init_key(); 12 | reader.set_key(key); 13 | } 14 | 15 | [[maybe_unused]] wz::File::File(u8 *new_iv, const char *path) 16 | : reader(Reader(key, path)), root(new Node(Type::NotSet, this)), key(), iv(new_iv) 17 | { 18 | init_key(); 19 | reader.set_key(key); 20 | } 21 | 22 | wz::File::~File() 23 | { 24 | delete[] iv; 25 | delete root; 26 | } 27 | 28 | bool wz::File::parse(const wzstring &name) 29 | { 30 | auto magic = reader.read_string(4); 31 | if (magic != u"PKG1") 32 | return false; 33 | 34 | auto fileSize = reader.read(); 35 | auto startAt = reader.read(); 36 | 37 | auto copyright = reader.read_string(); 38 | 39 | reader.set_position(startAt); 40 | 41 | auto encryptedVersion = reader.read(); 42 | 43 | for (int i = 0; i < 0x7FFF; ++i) 44 | { 45 | i16 file_version = static_cast(i); 46 | u32 version_hash = wz::get_version_hash(encryptedVersion, file_version); 47 | 48 | if (version_hash != 0) 49 | { 50 | desc.start = startAt; 51 | desc.hash = version_hash; 52 | desc.version = file_version; 53 | 54 | auto prev_position = reader.get_position(); 55 | 56 | if (!parse_directories(nullptr)) 57 | { 58 | reader.set_position(prev_position); 59 | continue; 60 | } 61 | else 62 | { 63 | if (root) 64 | { 65 | root->path = name; 66 | reader.set_position(prev_position); 67 | parse_directories(root); 68 | } 69 | return true; 70 | } 71 | } 72 | } 73 | 74 | return false; 75 | } 76 | 77 | bool wz::File::parse_directories(wz::Node *node) 78 | { 79 | auto entry_count = reader.read_compressed_int(); 80 | 81 | for (int i = 0; i < entry_count; ++i) 82 | { 83 | auto type = reader.read_byte(); 84 | size_t prevPos = 0; 85 | wzstring name; 86 | 87 | if (type == 1) 88 | { 89 | reader.skip(sizeof(i32) + sizeof(u16)); 90 | 91 | get_wz_offset(); 92 | continue; 93 | } 94 | else if (type == 2) 95 | { 96 | i32 stringOffset = reader.read(); 97 | type = reader.read_wz_string_from_offset(desc.start + stringOffset, name); 98 | } 99 | else if (type == 3 || type == 4) 100 | { 101 | name = reader.read_wz_string(); 102 | } 103 | else 104 | { 105 | assert(0); 106 | } 107 | 108 | i32 size = reader.read_compressed_int(); 109 | i32 checksum = reader.read_compressed_int(); 110 | u32 offset = get_wz_offset(); 111 | 112 | if (node == nullptr && offset >= reader.size()) 113 | return false; 114 | 115 | if (type == 3) 116 | { 117 | if (node != nullptr) 118 | { 119 | auto *dir = new Directory(this, false, size, checksum, offset); 120 | node->appendChild({name.begin(), name.end()}, dir); 121 | } 122 | } 123 | else 124 | { 125 | if (node != nullptr) 126 | { 127 | auto *dir = new Directory(this, true, size, checksum, offset); 128 | node->appendChild({name.begin(), name.end()}, dir); 129 | } 130 | else 131 | { 132 | prevPos = reader.get_position(); 133 | reader.set_position(offset); 134 | 135 | if (!reader.is_wz_image()) 136 | return false; 137 | 138 | reader.set_position(prevPos); 139 | } 140 | } 141 | } 142 | 143 | if (node != nullptr) 144 | { 145 | for (auto &it : *node) 146 | { 147 | for (auto child : it.second) 148 | { 149 | auto *dir = dynamic_cast(child); 150 | 151 | if (dir != nullptr) 152 | { 153 | if (!dir->is_image()) 154 | { 155 | reader.set_position(dir->get_offset()); 156 | parse_directories(dir); 157 | } 158 | } 159 | } 160 | } 161 | } 162 | 163 | return true; 164 | } 165 | 166 | u32 wz::File::get_wz_offset() 167 | { 168 | u32 offset = static_cast(reader.get_position()); 169 | offset = ~(offset - desc.start); 170 | offset *= desc.hash; 171 | offset -= wz::OffsetKey; 172 | offset = (offset << (offset & 0x1Fu)) | (offset >> (32 - (offset & 0x1Fu))); 173 | u32 encryptedOffset = reader.read(); 174 | offset ^= encryptedOffset; 175 | offset += desc.start * 2; 176 | return offset; 177 | } 178 | 179 | wz::Node *wz::File::get_root() const 180 | { 181 | return root; 182 | } 183 | 184 | void wz::File::init_key() 185 | { 186 | std::vector aes_key_v(32); 187 | memcpy(aes_key_v.data(), wz::AesKey2, 32); 188 | key = MutableKey({iv[0], iv[1], iv[2], iv[3]}, aes_key_v); 189 | } 190 | 191 | wz::Node &wz::File::get_child(const wzstring &name) 192 | { 193 | return *root->get_child(name); 194 | } 195 | -------------------------------------------------------------------------------- /src/Node.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "Node.hpp" 5 | #include "Property.hpp" 6 | #include "File.hpp" 7 | #include "Directory.hpp" 8 | 9 | wz::Node::Node() 10 | : parent(nullptr), file(nullptr), type(Type::NotSet) 11 | { 12 | } 13 | 14 | wz::Node::Node(const Type &new_type, File *root_file) 15 | : parent(nullptr), file(root_file), type(new_type) 16 | { 17 | reader = &file->reader; 18 | } 19 | 20 | wz::Node::~Node() 21 | { 22 | for (auto &[_, nodes] : children) 23 | { 24 | for (auto *node : nodes) 25 | { 26 | delete node; 27 | } 28 | } 29 | } 30 | 31 | void wz::Node::appendChild(const wzstring &name, Node *node) 32 | { 33 | assert(node); 34 | children[name].push_back(node); 35 | node->parent = this; 36 | node->path = this->path + u"/" + name; 37 | return; 38 | } 39 | 40 | const wz::WzMap &wz::Node::get_children() const 41 | { 42 | return children; 43 | } 44 | 45 | wz::Node *wz::Node::get_parent() const 46 | { 47 | return parent; 48 | } 49 | 50 | wz::WzMap::iterator wz::Node::begin() 51 | { 52 | return children.begin(); 53 | } 54 | 55 | wz::WzMap::iterator wz::Node::end() 56 | { 57 | return children.end(); 58 | } 59 | 60 | size_t wz::Node::children_count() const 61 | { 62 | return children.size(); 63 | } 64 | 65 | bool wz::Node::parse_property_list(Node *target, size_t offset) 66 | { 67 | auto entryCount = reader->read_compressed_int(); 68 | 69 | for (i32 i = 0; i < entryCount; i++) 70 | { 71 | auto name = reader->read_string_block(offset); 72 | 73 | auto prop_type = reader->read(); 74 | switch (prop_type) 75 | { 76 | case 0: 77 | { 78 | auto *prop = new wz::Property(Type::Null, file); 79 | prop->path = target->path + u"/" + name; 80 | 81 | target->appendChild(name, prop); 82 | } 83 | break; 84 | case 0x0B: 85 | [[fallthrough]]; 86 | case 2: 87 | { 88 | auto *prop = new wz::Property(Type::UnsignedShort, file, reader->read()); 89 | prop->path = target->path + u"/" + name; 90 | 91 | target->appendChild(name, prop); 92 | } 93 | break; 94 | case 3: 95 | { 96 | auto *prop = new wz::Property(Type::Int, file, reader->read_compressed_int()); 97 | prop->path = target->path + u"/" + name; 98 | 99 | target->appendChild(name, prop); 100 | } 101 | break; 102 | case 4: 103 | { 104 | auto float_type = reader->read(); 105 | if (float_type == 0x80) 106 | { 107 | auto *prop = new wz::Property(Type::Float, file, reader->read()); 108 | prop->path = target->path + u"/" + name; 109 | 110 | target->appendChild(name, prop); 111 | } 112 | else if (float_type == 0) 113 | { 114 | auto *pProp = new wz::Property(Type::Float, file, 0.f); 115 | pProp->path = target->path + u"/" + name; 116 | 117 | target->appendChild(name, pProp); 118 | } 119 | } 120 | break; 121 | case 5: 122 | { 123 | auto *prop = new wz::Property(Type::Double, file, reader->read()); 124 | prop->path = target->path + u"/" + name; 125 | 126 | target->appendChild(name, prop); 127 | } 128 | break; 129 | case 8: 130 | { 131 | auto *prop = new wz::Property(Type::String, file); 132 | prop->path = target->path + u"/" + name; 133 | 134 | auto str = reader->read_string_block(offset); 135 | prop->set(str); 136 | target->appendChild(name, prop); 137 | } 138 | break; 139 | case 9: 140 | { 141 | auto ofs = reader->read(); 142 | auto eob = reader->get_position() + ofs; 143 | parse_extended_prop(name, target, offset); 144 | if (reader->get_position() != eob) 145 | reader->set_position(eob); 146 | } 147 | break; 148 | default: 149 | { 150 | assert(0); 151 | } 152 | } 153 | } 154 | 155 | return true; 156 | } 157 | 158 | void wz::Node::parse_extended_prop(const wzstring &name, wz::Node *target, const size_t &offset) 159 | { 160 | auto strPropName = reader->read_string_block(offset); 161 | 162 | if (strPropName == u"Property") 163 | { 164 | auto *prop = new Property(Type::SubProperty, file); 165 | prop->path = target->path + u"/" + name; 166 | reader->skip(sizeof(u16)); 167 | parse_property_list(prop, offset); 168 | target->appendChild(name, prop); 169 | } 170 | else if (strPropName == u"Canvas") 171 | { 172 | auto *prop = new Property(Type::Canvas, file); 173 | prop->path = target->path + u"/" + name; 174 | reader->skip(sizeof(u8)); 175 | if (reader->read() == 1) 176 | { 177 | reader->skip(sizeof(u16)); 178 | parse_property_list(prop, offset); 179 | } 180 | 181 | prop->set(parse_canvas_property()); 182 | 183 | target->appendChild(name, prop); 184 | } 185 | else if (strPropName == u"Shape2D#Vector2D") 186 | { 187 | auto *prop = new Property(Type::Vector2D, file); 188 | prop->path = target->path + u"/" + name; 189 | 190 | auto x = reader->read_compressed_int(); 191 | auto y = reader->read_compressed_int(); 192 | prop->set({x, y}); 193 | 194 | target->appendChild(name, prop); 195 | } 196 | else if (strPropName == u"Shape2D#Convex2D") 197 | { 198 | auto *prop = new Property(Type::Convex2D, file); 199 | prop->path = target->path + u"/" + name; 200 | 201 | int convexEntryCount = reader->read_compressed_int(); 202 | for (int i = 0; i < convexEntryCount; i++) 203 | { 204 | parse_extended_prop(name, prop, offset); 205 | } 206 | 207 | target->appendChild(name, prop); 208 | } 209 | else if (strPropName == u"Sound_DX8") 210 | { 211 | auto *prop = new Property(Type::Sound, file); 212 | prop->path = target->path + u"/" + name; 213 | 214 | prop->set(parse_sound_property()); 215 | 216 | target->appendChild(name, prop); 217 | } 218 | else if (strPropName == u"UOL") 219 | { 220 | reader->skip(sizeof(u8)); 221 | auto *prop = new Property(Type::UOL, file); 222 | prop->path = target->path + u"/" + name; 223 | 224 | prop->set({reader->read_string_block(offset)}); 225 | target->appendChild(name, prop); 226 | } 227 | else 228 | { 229 | assert(0); 230 | } 231 | } 232 | 233 | wz::WzCanvas wz::Node::parse_canvas_property() 234 | { 235 | WzCanvas canvas; 236 | canvas.width = reader->read_compressed_int(); 237 | canvas.height = reader->read_compressed_int(); 238 | canvas.format = reader->read_compressed_int(); 239 | canvas.format2 = reader->read(); 240 | reader->skip(sizeof(u32)); 241 | canvas.size = reader->read() - 1; 242 | reader->skip(sizeof(u8)); 243 | 244 | canvas.offset = reader->get_position(); 245 | 246 | auto header = reader->read(); 247 | 248 | if (header != 0x9C78 && header != 0xDA78) 249 | { 250 | canvas.is_encrypted = true; 251 | } 252 | 253 | switch (canvas.format + canvas.format2) 254 | { 255 | case 1: 256 | { 257 | canvas.uncompressed_size = canvas.width * canvas.height * 2; 258 | } 259 | break; 260 | case 2: 261 | { 262 | canvas.uncompressed_size = canvas.width * canvas.height * 4; 263 | } 264 | break; 265 | case 513: // Format16bppRgb565 266 | { 267 | canvas.uncompressed_size = canvas.width * canvas.height * 2; 268 | } 269 | break; 270 | case 517: 271 | { 272 | canvas.uncompressed_size = canvas.width * canvas.height / 128; 273 | } 274 | break; 275 | } 276 | 277 | reader->set_position(canvas.offset + canvas.size); 278 | 279 | return canvas; 280 | } 281 | 282 | wz::WzSound wz::Node::parse_sound_property() 283 | { 284 | WzSound sound; 285 | // reader->ReadUInt8(); 286 | reader->skip(sizeof(u8)); 287 | sound.size = reader->read_compressed_int(); 288 | sound.length = reader->read_compressed_int(); 289 | reader->set_position(reader->get_position() + 56); 290 | sound.frequency = reader->read(); 291 | reader->set_position(reader->get_position() + 22); 292 | 293 | sound.offset = reader->get_position(); 294 | 295 | reader->set_position(sound.offset + sound.size); 296 | 297 | return sound; 298 | } 299 | 300 | wz::Type wz::Node::get_type() const 301 | { 302 | return type; 303 | } 304 | 305 | bool wz::Node::is_property() const 306 | { 307 | return (bit(type) & bit(Type::Property)) == bit(Type::Property); 308 | } 309 | 310 | wz::MutableKey &wz::Node::get_key() const 311 | { 312 | return file->key; 313 | } 314 | 315 | u8 *wz::Node::get_iv() const 316 | { 317 | return file->iv; 318 | } 319 | 320 | wz::Node *wz::Node::get_child(const wz::wzstring &name) 321 | { 322 | if (auto it = children.find(name); it != children.end()) 323 | { 324 | return it->second[0]; 325 | } 326 | return nullptr; 327 | } 328 | 329 | wz::Node *wz::Node::get_child(const std::string &name) 330 | { 331 | return get_child(std::u16string{name.begin(), name.end()}); 332 | } 333 | 334 | wz::Node &wz::Node::operator[](const wz::wzstring &name) 335 | { 336 | return *get_child(name); 337 | } 338 | 339 | wz::Node *wz::Node::find_from_path(const std::u16string &path) 340 | { 341 | auto next = std::views::split(std::string{path.begin(), path.end()}, '/') | std::views::common; 342 | wz::Node *node = this; 343 | for (const auto &s : next) 344 | { 345 | auto str = std::string{s.begin(), s.end()}; 346 | if (str == "..") 347 | { 348 | node = node->parent; 349 | continue; 350 | } 351 | else 352 | { 353 | node = node->get_child(str); 354 | if (node != nullptr) 355 | { 356 | // 处理UOL 357 | if (node->type == wz::Type::UOL) 358 | { 359 | node = dynamic_cast *>(node)->get_uol(); 360 | } 361 | if (node->type == wz::Type::Image) 362 | { 363 | static std::map img_map; 364 | if (img_map.contains(node->path)) 365 | { 366 | node = img_map[node->path]; 367 | } 368 | else 369 | { 370 | auto *image = new wz::Node(); 371 | auto *dir = dynamic_cast(node); 372 | dir->parse_image(image); 373 | node = image; 374 | img_map[node->path] = node; 375 | } 376 | continue; 377 | } 378 | } 379 | else 380 | { 381 | return nullptr; 382 | } 383 | } 384 | } 385 | return node; 386 | } 387 | 388 | wz::Node *wz::Node::find_from_path(const std::string &path) 389 | { 390 | return find_from_path(std::u16string{path.begin(), path.end()}); 391 | } --------------------------------------------------------------------------------