diff --git a/.gitignore b/.gitignore index 0faf399..32aedf1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,7 @@ .vscode build cmake-build-* -venv \ No newline at end of file +venv + +# Ignore zlib generated header +/Modules/zlib/zconf.h.included \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index a7e9d5a..c88f143 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,12 @@ [submodule "Modules/zlib"] path = Modules/zlib url = https://github.com/madler/zlib +[submodule "Modules/spdlog"] + path = Modules/spdlog + url = https://github.com/gabime/spdlog +[submodule "Modules/gtest"] + path = Modules/gtest + url = https://github.com/google/googletest +[submodule "Modules/CLI11"] + path = Modules/CLI11 + url = https://github.com/CLIUtils/CLI11.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 0385c29..b418899 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,11 +16,20 @@ SET(JSON_Install OFF CACHE BOOL "Do not generate install target in nlohmann") SET(JSON_BuildTests OFF CACHE BOOL "Do not build tests of nlohmann") SET(FMT_INSTALL OFF CACHE BOOL "Do not generate install targets of fmt") SET(FMT_TEST OFF CACHE BOOL "Do not generate install targets of fmt") +SET(ZLIB_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/Modules/zlib") +SET(SPDLOG_FMT_EXTERNAL_HO ON CACHE BOOL "Do not use own fmt") +SET(BUILD_SHARED_LIBS OFF) # Modules add_subdirectory(Modules/fmt) add_subdirectory(Modules/zlib) +add_subdirectory(Modules/CLI11) +add_subdirectory(Modules/gtest) +add_subdirectory(Modules/spdlog) +add_subdirectory(Modules/minizip) add_subdirectory(Modules/nlohmann) +add_subdirectory(Modules/BMLOC) # Our projects -add_subdirectory(Tools/GMSInfo) \ No newline at end of file +add_subdirectory(Tools/GMSInfo) +add_subdirectory(Tools/LOCC) \ No newline at end of file diff --git a/Modules/BMLOC/CMakeLists.txt b/Modules/BMLOC/CMakeLists.txt new file mode 100644 index 0000000..9b52aa6 --- /dev/null +++ b/Modules/BMLOC/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.16) +project(BMLOC) + +set(CMAKE_CXX_STANDARD 20) + +file(GLOB_RECURSE BMLOC_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp) +add_library(BMLOC STATIC ${BMLOC_SOURCES}) +add_library(BMFormats::Localization ALIAS BMLOC) + +target_compile_definitions(BMLOC PRIVATE -D_CRT_SECURE_NO_WARNINGS=1) +target_include_directories(BMLOC PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_include_directories(BMLOC PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/internal/include) +target_link_libraries(BMLOC PUBLIC nlohmann_json) + +# Tests +enable_testing() + +file(GLOB_RECURSE BLOC_TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cpp) + +add_executable(BMLOC_Tests ${BLOC_TEST_SOURCES}) +target_link_libraries(BMLOC_Tests BMFormats::Localization gtest gmock gtest_main) + +add_test(BMLOC_AllTests BMLOC_Tests) \ No newline at end of file diff --git a/Modules/BMLOC/README.md b/Modules/BMLOC/README.md new file mode 100644 index 0000000..d868159 --- /dev/null +++ b/Modules/BMLOC/README.md @@ -0,0 +1,4 @@ +BMLOC +------ + + Blood Money LOC file support \ No newline at end of file diff --git a/Modules/BMLOC/include/BM/LOC/LOCJson.h b/Modules/BMLOC/include/BM/LOC/LOCJson.h new file mode 100644 index 0000000..66864b6 --- /dev/null +++ b/Modules/BMLOC/include/BM/LOC/LOCJson.h @@ -0,0 +1,195 @@ +#pragma once + + +#include + +#include +#include + +namespace nlohmann +{ + template <> + struct adl_serializer + { + static void to_json(json& j, const BM::LOC::HBM_MissionObjectiveType& type) + { + switch (type) + { + case BM::LOC::HBM_Target: + case BM::LOC::HBM_Retrieve: + case BM::LOC::HBM_Escape: + case BM::LOC::HBM_Dispose: + case BM::LOC::HBM_Protect: + case BM::LOC::HBM_Optional: + j = static_cast(type); + break; + default: + j = static_cast(type); + break; + } + } + + static void from_json(const json& j, BM::LOC::HBM_MissionObjectiveType& r) + { + if (j.is_string()) + { + r = static_cast(j.get()[0]); + } + else + { + r = static_cast(j.get()); + } + } + }; + + template <> + struct adl_serializer + { + static constexpr const char* kValOrData = "data"; + static constexpr const char* kNode = "node"; + + static void to_json(json& j, const BM::LOC::TreeNodeType& type) + { + switch (type) + { + case BM::LOC::TreeNodeType::VALUE_OR_DATA: j = kValOrData; break; + case BM::LOC::TreeNodeType::NODE_WITH_CHILDREN: j = kNode; break; + default: j = static_cast(type); break; + } + } + + static void from_json(const json& j, BM::LOC::TreeNodeType& r) + { + if (j == kValOrData) r = BM::LOC::TreeNodeType::VALUE_OR_DATA; + else if (j == kNode) r = BM::LOC::TreeNodeType::NODE_WITH_CHILDREN; + else r = static_cast(j.get()); + } + }; + + template <> + struct adl_serializer + { + static constexpr const char* kNameToken = "name"; + static constexpr const char* kValueToken = "value"; + static constexpr const char* kTypeToken = "type"; + static constexpr const char* kNumChildrenToken = "numChildren"; + static constexpr const char* kChildrenListToken = "children"; + static constexpr const char* kOriginalTypeByteToken = "org_tbyte"; + + static void to_json(json& j, const BM::LOC::LOCTreeNode* node) + { + adl_serializer::to_json(j[kTypeToken], node->nodeType); + + switch (node->nodeType) + { + case BM::LOC::VALUE_OR_DATA: + j[kNameToken] = node->name; + + if (!node->value.empty()) + { + // If not string rewrite to byte export (calc length between next node in this level or in parent' level) + std::string_view value { node->value }; + j[kValueToken] = value; + } + + if (node->originalTypeRawData.has_value()) // our value was overridden, need to save original byte + { + j[kOriginalTypeByteToken] = node->originalTypeRawData.value(); + } + break; + case BM::LOC::NODE_WITH_CHILDREN: + if (!node->IsRoot()) { j[kNameToken] = node->name; } + assert(node->numChild == node->children.size()); + + j[kNumChildrenToken] = node->numChild; + + for (const auto& child : node->children) + { + json jc; + nlohmann::adl_serializer::to_json(jc, child); + j[kChildrenListToken].push_back(jc); + } + break; + } + } + + static void from_json(const json& j, BM::LOC::LOCTreeNode* node) + { + node->currentBufferPtr = nullptr; + + if (auto nameIterator = j.find(kNameToken); nameIterator != j.end()) + { + node->name = nameIterator->get(); + } + else if (!node->IsRoot()) + { + throw std::exception { "Not allowed to store non-root anonymous node!" }; + } + + if (auto nodeTypeIter = j.find(kTypeToken); nodeTypeIter != j.end()) + { + nlohmann::adl_serializer::from_json(j[kTypeToken], node->nodeType); + } + else + { + throw std::exception { "Each node should contain type!" }; + } + + switch (node->nodeType) + { + case BM::LOC::VALUE_OR_DATA: + if (auto valueIterator = j.find(kValueToken); valueIterator != j.end()) + { + node->value = valueIterator->get(); + } else throw std::exception { "Key 'value' not found for VALUE node!" }; + + if (auto originalByteIterator = j.find(kOriginalTypeByteToken); originalByteIterator != j.end()) + { + node->originalTypeRawData = originalByteIterator->get(); + } + break; + case BM::LOC::NODE_WITH_CHILDREN: + { + if (auto numChildIterator = j.find(kNumChildrenToken); numChildIterator != j.end()) + { + node->numChild = numChildIterator->get(); + node->children.reserve(node->numChild); + } + else throw std::exception { "Key 'numChild' not found for NWC node!" }; + + if (auto childrenIterator = j.find(kChildrenListToken); childrenIterator != j.end()) + { + if (!childrenIterator->is_array()) + { + throw std::exception { "Key 'children' not array for NWC node!" }; + } + + const int requiredNumChild = node->numChild; + + for (int i = 0; i < requiredNumChild; i++) + { + auto child = new BM::LOC::LOCTreeNode(node, nullptr); + + try + { + auto jsonChild = childrenIterator->at(i); + nlohmann::adl_serializer::from_json(jsonChild, child); + node->AddChild(child); + } + catch (const nlohmann::json::out_of_range& outOfRange) + { + throw std::exception { "Bad 'numChild' value in json for NWC node!" }; + } + } + } //Allowed to have 0 child nodes + } + break; + default: + { + throw std::exception { "Unknown node type!" }; + } + break; + } + } + }; +} \ No newline at end of file diff --git a/Modules/BMLOC/include/BM/LOC/LOCSupportMode.h b/Modules/BMLOC/include/BM/LOC/LOCSupportMode.h new file mode 100644 index 0000000..370302a --- /dev/null +++ b/Modules/BMLOC/include/BM/LOC/LOCSupportMode.h @@ -0,0 +1,14 @@ +#pragma once + +namespace BM::LOC +{ + enum class LOCSupportMode + { + Hitman_BloodMoney = 0x0004, + Hitman_Contracts = 0x0003, + Hitman_2SA = 0x0002, + Hitman_A47 = 0x0001, + // Generic mode + Generic = Hitman_BloodMoney + }; +} \ No newline at end of file diff --git a/Modules/BMLOC/include/BM/LOC/LOCTree.h b/Modules/BMLOC/include/BM/LOC/LOCTree.h new file mode 100644 index 0000000..caeb7d0 --- /dev/null +++ b/Modules/BMLOC/include/BM/LOC/LOCTree.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace BM::LOC +{ + struct LOCTreeNode + { + // Editor defs + /** + * @struct MemoryMarkup + * @brief In-memory location of the node. This value used only in the compiler! + */ + struct MemoryMarkup + { + uint32_t StartsAt {0 }; ///< Node starts at + uint32_t EndsAt { 0 }; ///< Node ends at + + MemoryMarkup() = default; + MemoryMarkup(uint32_t o, uint32_t e) : StartsAt(o), EndsAt(e) {} + }; + + // Defs + static constexpr const char* kNoName = ""; + + // Base info + LOCTreeNode* parent {nullptr}; //Pointer to parent node + char* currentBufferPtr {nullptr}; //Available only in decompiler (be aware, if original buffer deallocated this pointer will be broken) + std::string name{kNoName}; //Name of node (could be empty for ROOT node) + std::string value; //Always string? Check it later + TreeNodeType nodeType{0}; //See TreeNodeType for details + std::optional originalTypeRawData; //Original raw data if it was overridden by decompiler (just for reconstruction) + std::optional memoryMarkup; //Only for compiler for fast memory position search + + // Tree data + size_t numChild {0}; //Number of children nodes + std::vector children {}; //List of children nodes (please, add node through AddChild!) + + // Methods + LOCTreeNode(LOCTreeNode* p, char* b); + ~LOCTreeNode(); + void AddChild(LOCTreeNode* node); + void RemoveChild(LOCTreeNode* node); + [[nodiscard]] bool IsRoot() const; + [[nodiscard]] bool IsEmpty() const; + [[nodiscard]] bool IsData() const; + [[nodiscard]] bool IsContainer() const; + + // Parser + static LOCTreeNode* ReadFromMemory(char* buffer, size_t bufferSize, LOCSupportMode supportMode = LOCSupportMode::Generic); + + // Compiler + using CacheDataBase = std::unordered_map; + static void GenerateCacheDataBase(LOCTreeNode* root, CacheDataBase& cache); + static bool Compile(LOCTreeNode* root, std::vector& compiledBuffer); + + // Serializer + static void CompileAndSave(LOCTreeNode* root, std::string_view pathToFile); + + // Comparator + static bool Compare(LOCTreeNode* a, LOCTreeNode* b); + + private: + /** + * @fn SortKeys + * @brief Should be called after AddChild or RemoveChild! + */ + void SortKeys(); + }; +} \ No newline at end of file diff --git a/Modules/BMLOC/include/BM/LOC/LOCTreeCompiler.h b/Modules/BMLOC/include/BM/LOC/LOCTreeCompiler.h new file mode 100644 index 0000000..45ba22c --- /dev/null +++ b/Modules/BMLOC/include/BM/LOC/LOCTreeCompiler.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +#include + +#include + +namespace BM::LOC +{ + class LOCTreeCompiler + { + public: + using Buffer = std::vector; + + static bool Compile(Buffer& buffer, LOCTreeNode* rootNode, LOCSupportMode supportMode = LOCSupportMode::Generic); + static void MarkupTree(LOCTreeNode* rootNode); + }; +} \ No newline at end of file diff --git a/Modules/BMLOC/include/BM/LOC/LOCTreeFactory.h b/Modules/BMLOC/include/BM/LOC/LOCTreeFactory.h new file mode 100644 index 0000000..def35f0 --- /dev/null +++ b/Modules/BMLOC/include/BM/LOC/LOCTreeFactory.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +namespace BM::LOC +{ + struct LOCTreeNode; + + class LOCTreeFactory + { + public: + static LOCTreeNode* Create(); + static LOCTreeNode* Create(const std::string& name, TreeNodeType nodeType, LOCTreeNode* parent = nullptr); + static LOCTreeNode* Create(const std::string& name, const std::string& value, LOCTreeNode* parent = nullptr); + }; +} \ No newline at end of file diff --git a/Modules/BMLOC/include/BM/LOC/LOCTypes.h b/Modules/BMLOC/include/BM/LOC/LOCTypes.h new file mode 100644 index 0000000..ef1f7c3 --- /dev/null +++ b/Modules/BMLOC/include/BM/LOC/LOCTypes.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +namespace BM::LOC +{ + enum TreeNodeType : int8_t { + VALUE_OR_DATA = 0x0, + NODE_WITH_CHILDREN = 0x10 + }; + + enum HBM_MissionObjectiveType : char + { + HBM_Target = 'T', + HBM_Retrieve = 'R', + HBM_Escape = 'E', + HBM_Dispose = 'D', + HBM_Protect = 'P', + HBM_Optional = 'O' + }; +} \ No newline at end of file diff --git a/Modules/BMLOC/internal/include/BM/LOC/Internal/CompilerUtils.h b/Modules/BMLOC/internal/include/BM/LOC/Internal/CompilerUtils.h new file mode 100644 index 0000000..9c43b0a --- /dev/null +++ b/Modules/BMLOC/internal/include/BM/LOC/Internal/CompilerUtils.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include +#include + +namespace BM::LOC::Internal +{ + struct CompilerUtils + { + static size_t GetStringAlignedLength(std::string_view str, int alignOut = 4); + static size_t WriteZStringAligned(std::vector& buffer, size_t offset, std::string_view str); + static size_t WriteZString(std::vector& buffer, size_t offset, const std::string& str); + static void WriteByte(std::vector& buffer, size_t offset, uint8_t b); + static void WriteUInt32(std::vector& buffer, size_t offset, uint32_t v); + }; +} \ No newline at end of file diff --git a/Modules/BMLOC/internal/include/BM/LOC/Internal/LOCCompilerImpl.h b/Modules/BMLOC/internal/include/BM/LOC/Internal/LOCCompilerImpl.h new file mode 100644 index 0000000..da3b06f --- /dev/null +++ b/Modules/BMLOC/internal/include/BM/LOC/Internal/LOCCompilerImpl.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +#include + +namespace BM::LOC::Internal +{ + template + struct LOCCompilerImpl + { + static bool MarkupTree(LOCTreeNode* root); + static bool Compile(std::vector& outputBuffer, LOCTreeNode* root); + }; + + /// ---- IMPL FOR HITMAN BLOOD MONEY ---- + template <> + struct LOCCompilerImpl + { + static bool MarkupTree(LOCTreeNode* root); + static bool Compile(std::vector& outputBuffer, LOCTreeNode* root); + + private: + static size_t CalculateSizeForEntry(LOCTreeNode* node, size_t startPosition); + }; + + /// ---- IMPL FOR HITMAN CONTRACTS ---- + template <> + struct LOCCompilerImpl + { + static bool MarkupTree(LOCTreeNode* root); + static bool Compile(std::vector& outputBuffer, LOCTreeNode* root); + }; +} \ No newline at end of file diff --git a/Modules/BMLOC/internal/include/BM/LOC/Internal/LOCTreeNodeVisitor.h b/Modules/BMLOC/internal/include/BM/LOC/Internal/LOCTreeNodeVisitor.h new file mode 100644 index 0000000..89c71f1 --- /dev/null +++ b/Modules/BMLOC/internal/include/BM/LOC/Internal/LOCTreeNodeVisitor.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + +namespace BM::LOC::Internal +{ + template + struct LOCTreeNodeVisitor + { + static bool Visit(LOCTreeNode* node, size_t bufferSize); + static size_t Markup(LOCTreeNode* node, size_t startPosition); + }; + + // Implementations + template <> + struct LOCTreeNodeVisitor + { + static bool Visit(LOCTreeNode* treeNode, size_t bufferSize); + }; + + template <> + struct LOCTreeNodeVisitor + { + static bool Visit(LOCTreeNode* node, size_t bufferSize); + }; +} \ No newline at end of file diff --git a/Modules/BMLOC/source/LOC/Internal/CompilerUtils.cpp b/Modules/BMLOC/source/LOC/Internal/CompilerUtils.cpp new file mode 100644 index 0000000..7c3444c --- /dev/null +++ b/Modules/BMLOC/source/LOC/Internal/CompilerUtils.cpp @@ -0,0 +1,95 @@ +#include +#include + +namespace BM::LOC::Internal +{ + size_t CompilerUtils::GetStringAlignedLength(std::string_view str, int alignOut) + { + return str.length() + 1 + alignOut; // +1 - zero terminator, +4 alignment + } + + size_t CompilerUtils::WriteZStringAligned(std::vector& buffer, size_t offset, std::string_view str) + { + if (offset > buffer.size()) + { + throw std::out_of_range { + "WriteZStringAligned: Out of range! Offset " + std::to_string(offset) + + " not included in " + std::to_string(buffer.size() - 1) }; + } + + size_t strLen = GetStringAlignedLength(str); + + if (offset + strLen > buffer.size()) + { + throw std::out_of_range { + "WriteZStringAligned: Out of range! String buffer at " + std::to_string(offset) + + " with length " + std::to_string(strLen) + " not included in " + std::to_string(buffer.size() - 1) + }; + } + + std::memset(&buffer[offset], 0x0, strLen); + std::memcpy(&buffer[offset], str.data(), str.length()); + + return offset + strLen; + } + + size_t CompilerUtils::WriteZString(std::vector& buffer, size_t offset, const std::string& str) + { + if (offset >= buffer.size()) + { + throw std::out_of_range { + "WriteZString: Out of range! Offset " + std::to_string(offset) + + " not included in buffer " + std::to_string(buffer.size()) }; + } + + if (offset + str.length() + 1 >= buffer.size()) + { + throw std::out_of_range { + "WriteZString: Out of range! Offset " + std::to_string(offset) + + " with length " + std::to_string(str.length() + 1) + + " not included in " + std::to_string(buffer.size() - 1) }; + return 0; + } + + std::memcpy(&buffer[offset], str.data(), str.length()); + + return offset + str.length() + 1; + } + + void CompilerUtils::WriteByte(std::vector& buffer, size_t offset, uint8_t b) + { + if (offset >= buffer.size()) + { + throw std::out_of_range { + "WriteByte: Out of range! Offset " + std::to_string(offset) + + " with required space 1 (at " + std::to_string(offset + 1) + + ") not included in " + std::to_string(buffer.size() - 1) + }; + } + + buffer[offset] = b; + } + + void CompilerUtils::WriteUInt32(std::vector& buffer, size_t offset, uint32_t v) + { + if (offset >= buffer.size()) + { + throw std::out_of_range { + "WriteUInt32: Out of range! Offset " + std::to_string(offset) + + " not included in " + std::to_string(buffer.size() - 1) + }; + } + + if (offset + sizeof(uint32_t) >= buffer.size()) + { + throw std::out_of_range { + "WriteUInt32: Out of range! Offset " + std::to_string(offset) + + " with required space 4 (at " + std::to_string(offset + sizeof(uint32_t)) + + ") not included in " + std::to_string(buffer.size() - 1) + }; + } + + uint32_t bs = v; + std::memcpy(&buffer[offset], &bs, sizeof(uint32_t)); + } +} \ No newline at end of file diff --git a/Modules/BMLOC/source/LOC/Internal/LOCBloodMoneyCompilerImpl.cpp b/Modules/BMLOC/source/LOC/Internal/LOCBloodMoneyCompilerImpl.cpp new file mode 100644 index 0000000..8976a81 --- /dev/null +++ b/Modules/BMLOC/source/LOC/Internal/LOCBloodMoneyCompilerImpl.cpp @@ -0,0 +1,161 @@ +#include +#include + +namespace BM::LOC::Internal +{ + using Self = LOCCompilerImpl; + + bool Self::MarkupTree(LOCTreeNode* root) + { + CalculateSizeForEntry(root, 0); + return true; + } + + bool Self::Compile(std::vector& buffer, LOCTreeNode* node) + { + if (!node) throw std::exception { "WriteTreeNodesIntoMarkedUpMemory: Bad node pointer" }; + if (!node->memoryMarkup.has_value()) throw std::exception { "WriteTreeNodesIntoMarkedUpMemory: Node was not marked up!" }; + + const auto& markup = node->memoryMarkup.value(); + + if (node->IsContainer()) + { + if (node->IsRoot()) + { + CompilerUtils::WriteByte(buffer, markup.StartsAt, node->numChild); //Count of child nodes + + for (int i = 1; i < node->numChild; i++) //Write offsets from #1 to last node + { + size_t offsetsTableStartPosition = markup.StartsAt + 1; // skip first byte (allocated for num childs) + offsetsTableStartPosition += ((i - 1) * sizeof(uint32_t)); + CompilerUtils::WriteUInt32( + buffer, + offsetsTableStartPosition, + node->children[i]->memoryMarkup.value().StartsAt - node->children[0]->memoryMarkup.value().StartsAt + ); + } + + // Write children nodes + for (int i = 0; i < node->numChild; i++) + { + if (!Self::Compile(buffer, node->children[i])) + { + return false; + } + } + } + else + { + size_t positionAfterString = CompilerUtils::WriteZString(buffer, markup.StartsAt, node->name); //Write name + uint8_t typeByte = node->originalTypeRawData.has_value() ? node->originalTypeRawData.value() : static_cast(node->nodeType); + CompilerUtils::WriteByte(buffer, positionAfterString, typeByte); //Write type byte + CompilerUtils::WriteByte(buffer, positionAfterString + 1, node->numChild); //Write num of child nodes + + for (int i = 1; i < node->numChild; i++) + { + //Write offsets table from #1 to last node + CompilerUtils::WriteUInt32( + buffer, + (positionAfterString + 2) + ((i - 1) * sizeof(uint32_t)), + node->children[i]->memoryMarkup.value().StartsAt - node->children[0]->memoryMarkup.value().StartsAt + ); + } + + // Write children nodes + for (int i = 0; i < node->numChild; i++) + { + if (!Self::Compile(buffer, node->children[i])) + { + return false; + } + } + } + } else if (node->IsData()) + { + size_t positionAfterString = CompilerUtils::WriteZString(buffer, markup.StartsAt, node->name); //Write full string + CompilerUtils::WriteByte(buffer, positionAfterString++, node->originalTypeRawData.has_value() ? node->originalTypeRawData.value() : static_cast(node->nodeType)); //Write leading byte + CompilerUtils::WriteZStringAligned(buffer, positionAfterString, node->value); // Write aligned value + } + + return true; + } + + size_t Self::CalculateSizeForEntry(LOCTreeNode* node, size_t startPosition) + { + if (node->IsContainer()) + { + if (node->IsRoot()) + { + /** + * Root node layout + * + * Count of children Offset table for #1..N chld + * [ 1 byte - child num ][ 4 * (child count - 1) bytes ] + * + * Child #0 stored after root layout + * Child #1 stored after #0 layout + * ... + * Child #(N-1) stored after #(N-2) layout + */ + size_t endPosition = startPosition; + endPosition += 1; // For count of children + if (node->numChild > 0) + { + endPosition += (sizeof(uint32_t) * (node->numChild - 1)); // Space for offsets table without leading entity + endPosition = Self::CalculateSizeForEntry(node->children[0], endPosition); + for (int i = 1; i < node->numChild; i++) + { + endPosition = Self::CalculateSizeForEntry(node->children[i], endPosition); + } + } + node->memoryMarkup = LOCTreeNode::MemoryMarkup { startPosition, endPosition }; + } + else + { + /** + * Generic container node layout + * + * [ Name length + 1 ][ Type Byte ][ 4 * (children nodes count - 1) ] + * + * Child #0 stored after root layout + * Child #1 stored after #0 layout + * ... + * Child #(N-1) stored after #(N-2) layout + */ + size_t endPosition = startPosition; + endPosition += node->name.length() + 1; // For name of the node + endPosition += 1; // For type byte + endPosition += 1; // For num child + if (node->numChild > 0) + { + endPosition += sizeof(uint32_t) * (node->numChild - 1); // For index table + endPosition = Self::CalculateSizeForEntry(node->children[0], endPosition); + for (int i = 1; i < node->numChild; i++) + { + endPosition = Self::CalculateSizeForEntry(node->children[i], endPosition); + } + } + node->memoryMarkup = LOCTreeNode::MemoryMarkup { startPosition, endPosition }; + } + } + else if (node->IsData()) + { + /** + * Data node format + * + * [ Name bytes + 1 ][ type byte ][ Value bytes + 1 ] + */ + size_t endPosition = startPosition; + endPosition += node->name.length() + 1; // For node name + endPosition += 1; // For type byte + endPosition += CompilerUtils::GetStringAlignedLength(node->value); + node->memoryMarkup = LOCTreeNode::MemoryMarkup { startPosition, endPosition }; + } + else + { + throw std::exception { "CalculateUsedMemoryAndMarkLocations: Unknown node type" }; + } + + return node->memoryMarkup.value().EndsAt; + } +} \ No newline at end of file diff --git a/Modules/BMLOC/source/LOC/Internal/LOCBloodMoneyTreeNodeVisitorImpl.cpp b/Modules/BMLOC/source/LOC/Internal/LOCBloodMoneyTreeNodeVisitorImpl.cpp new file mode 100644 index 0000000..6fbe148 --- /dev/null +++ b/Modules/BMLOC/source/LOC/Internal/LOCBloodMoneyTreeNodeVisitorImpl.cpp @@ -0,0 +1,86 @@ +#include + +namespace BM::LOC::Internal +{ + using Self = LOCTreeNodeVisitor; + + bool Self::Visit(LOCTreeNode* treeNode, size_t bufferSize) + { + if (treeNode->parent) + { + treeNode->nodeType = static_cast(treeNode->currentBufferPtr[0]); + treeNode->currentBufferPtr = (char*)&treeNode->currentBufferPtr[1]; // Move caret if we not a root + } + else + { + treeNode->nodeType = TreeNodeType::NODE_WITH_CHILDREN; // Root always contains children nodes, no data inside + } + + const bool isOkNode = treeNode->IsData() || treeNode->IsContainer(); + if (!isOkNode) + { + treeNode->originalTypeRawData = static_cast(treeNode->nodeType); + treeNode->nodeType = TreeNodeType::VALUE_OR_DATA; // We can override type because we save that before this. + } + + if (treeNode->IsData()) + { + if (treeNode->currentBufferPtr) + { + treeNode->value = treeNode->currentBufferPtr; + } + return true; // Do not look for any child here + } + + auto countOfChildNodes = static_cast(*treeNode->currentBufferPtr); + if (countOfChildNodes == 0) + return true; // Orphaned or broken node, not interested for us + + char* offsetsPtr = (char*)&treeNode->currentBufferPtr[1]; + std::vector offsets; + offsets.reserve(countOfChildNodes); + + offsets.push_back(0); // Always our first entity located at +0x0, other located on their own offsets + for (int i = 1; i < countOfChildNodes; i++) + { + uint32_t offset = *reinterpret_cast(&offsetsPtr[0]); + if (offset >= bufferSize) + { + return false; // Out of bounds + } + + offsets.push_back(*reinterpret_cast(&offsetsPtr[0])); + offsetsPtr += sizeof(uint32_t); + } + + treeNode->numChild = static_cast(countOfChildNodes); + + char* baseAddr = treeNode->currentBufferPtr + (sizeof(uint32_t) * (countOfChildNodes - 1)) + 1; + + for (const auto& offset : offsets) + { + if (offset >= bufferSize) + { + return false; + } + + std::string_view name { baseAddr + offset }; + + if (offset + name.length() + 1 >= bufferSize) + { + return false; + } + + auto child = new LOCTreeNode(treeNode, baseAddr + offset + name.length() + 1); + child->name = name; + treeNode->AddChild(child); + + if (!Internal::LOCTreeNodeVisitor::Visit(child, bufferSize)) + { + return false; + } + } + + return true; + } +} \ No newline at end of file diff --git a/Modules/BMLOC/source/LOC/Internal/LOCContractsCompilerImpl.cpp b/Modules/BMLOC/source/LOC/Internal/LOCContractsCompilerImpl.cpp new file mode 100644 index 0000000..24185e2 --- /dev/null +++ b/Modules/BMLOC/source/LOC/Internal/LOCContractsCompilerImpl.cpp @@ -0,0 +1,16 @@ +#include + +namespace BM::LOC::Internal +{ + using Self = LOCCompilerImpl; + + bool Self::Compile(std::vector& outputBuffer, LOCTreeNode* root) + { + return false; + } + + bool Self::MarkupTree(LOCTreeNode* root) + { + return false; + } +} \ No newline at end of file diff --git a/Modules/BMLOC/source/LOC/Internal/LOCContractsTreeNodeVisitorImpl.cpp b/Modules/BMLOC/source/LOC/Internal/LOCContractsTreeNodeVisitorImpl.cpp new file mode 100644 index 0000000..51ff5ad --- /dev/null +++ b/Modules/BMLOC/source/LOC/Internal/LOCContractsTreeNodeVisitorImpl.cpp @@ -0,0 +1,11 @@ +#include + +namespace BM::LOC::Internal +{ + using Self = LOCTreeNodeVisitor; + + bool Self::Visit(LOCTreeNode* treeNode, size_t bufferSize) + { + return false; + } +} \ No newline at end of file diff --git a/Modules/BMLOC/source/LOC/LOCTree.cpp b/Modules/BMLOC/source/LOC/LOCTree.cpp new file mode 100644 index 0000000..9908f73 --- /dev/null +++ b/Modules/BMLOC/source/LOC/LOCTree.cpp @@ -0,0 +1,277 @@ +#include // PRIVATE IMPL +#include +#include +#include + +#include +#include +#include +#include + +namespace BM::LOC +{ + LOCTreeNode::LOCTreeNode(LOCTreeNode* p, char* b) : parent(p), currentBufferPtr(b) {} + + LOCTreeNode::~LOCTreeNode() + { + if (numChild) + { + for (int i = 0; i < numChild; i++) + { + delete children[i]; + } + + numChild = 0; + } + } + + void LOCTreeNode::AddChild(LOCTreeNode* node) + { + if (!node) + { + assert(false); // Bad node here + return; + } + + // Setup us as the parent or don't set up anything into the parent field. + assert(node->parent == this || node->parent == nullptr); + if (node->parent != this) + { + node->parent = this; + } + + children.push_back(node); + numChild = children.size(); + + SortKeys(); + } + + void LOCTreeNode::RemoveChild(LOCTreeNode* node) + { + if (!node) + { + assert(false); // Bad node here + return; + } + + node->parent = nullptr; // Remove us from parent field + // Remove us from vector + auto it = std::find(std::begin(children), std::end(children), node); + if (it != std::end(children)) + { + children.erase(it); + numChild = children.size(); + + SortKeys(); + } + } + + bool LOCTreeNode::IsRoot() const + { + return parent == nullptr; + } + + bool LOCTreeNode::IsEmpty() const + { + if (nodeType == TreeNodeType::VALUE_OR_DATA) + return value.empty(); + + if (nodeType == TreeNodeType::NODE_WITH_CHILDREN) + return numChild == 0; + + // Empty & not inited tree node + return true; + } + + bool LOCTreeNode::IsData() const + { + return nodeType == TreeNodeType::VALUE_OR_DATA; + } + + bool LOCTreeNode::IsContainer() const + { + return nodeType == TreeNodeType::NODE_WITH_CHILDREN; + } + + void LOCTreeNode::SortKeys() + { + if (children.size() <= 1) + { + return; // Nothing to sort here + } + + auto SortPred = [](const LOCTreeNode* first, const LOCTreeNode* second) -> bool { + assert(!first->name.empty()); + assert(!second->name.empty()); + + return first->name < second->name; + }; + + std::sort(std::begin(children), std::end(children), SortPred); + } + + LOCTreeNode* LOCTreeNode::ReadFromMemory(char* buffer, size_t bufferSize, LOCSupportMode supportMode) + { + switch (supportMode) + { + case LOCSupportMode::Hitman_BloodMoney: + { + auto root = new LOCTreeNode(nullptr, buffer); + if (!BM::LOC::Internal::LOCTreeNodeVisitor::Visit(root, bufferSize)) + { + delete root; + return nullptr; + } + return root; + } + break; + case LOCSupportMode::Hitman_Contracts: + { + auto root = LOCTreeFactory::Create(); + if (!BM::LOC::Internal::LOCTreeNodeVisitor::Visit(root, bufferSize)) + { + delete root; + return nullptr; + } + return root; + } + break; + // ---< NOT SUPPORTED YET >--- + case LOCSupportMode::Hitman_2SA: + case LOCSupportMode::Hitman_A47: + default: + return nullptr; + } + } + + static void VisitAndMarkNode(LOCTreeNode* node, LOCTreeNode::CacheDataBase& cache, const std::string& currentKey) // NOLINT(misc-no-recursion) + { + if (node->IsData()) + { + std::string finalKey = currentKey; + + if (finalKey[finalKey.length() - 1] != '/') + finalKey += '/'; + + finalKey += node->name; + cache[finalKey] = node->value; + } + else if (node->IsContainer() && node->numChild > 0) + { + std::string finalKey = currentKey; + + if (!node->IsRoot()) + { + if (finalKey[finalKey.length() - 1] != '/') + finalKey += '/'; + + finalKey += node->name; + } + + for (int i = 0; i < node->numChild; i++) + { + VisitAndMarkNode(node->children[i], cache, finalKey); + } + } + } + + void LOCTreeNode::GenerateCacheDataBase(LOCTreeNode* root, LOCTreeNode::CacheDataBase& cache) + { + VisitAndMarkNode(root, cache, "/"); + } + + bool LOCTreeNode::Compile(LOCTreeNode* root, std::vector& compiledBuffer) + { + if (!root || !root->IsRoot() || root->IsEmpty()) throw std::exception { "Bad root node! It's actually not root or empty" }; + if (root->numChild < 0 || root->numChild > 0xFF) throw std::exception { "To many root nodes! Allowed only 255 (0xFF) max!" }; + + return LOCTreeCompiler::Compile(compiledBuffer, root); + } + + void LOCTreeNode::CompileAndSave(LOCTreeNode* root, std::string_view pathToFile) + { + if (!root || !root->IsRoot() || root->IsEmpty()) throw std::exception { "LOCTreeNode::Save| Bad root node! Unable to serialize it!" }; + if (root->numChild < 0 || root->numChild > 0xFF) throw std::exception { "LOCTreeNode::Save| Too many root nodes! Max allowed 255, min 1" }; + + std::ofstream file(pathToFile.data(), std::ofstream::trunc | std::ofstream::out); + LOCTreeCompiler::Buffer compiledBuffer {}; + + if (!file) + { + throw std::exception { "LOCTreeNode::Save| Unable to create output file!" }; + } + + try + { + bool compileResult = LOCTreeNode::Compile(root, compiledBuffer); + if (!compileResult) + { + file.close(); + throw std::exception { "LOCTreeNode::Save| Unable to compile tree" }; + } + + file.write((char*)compiledBuffer.data(), compiledBuffer.size()); + file.close(); + } catch (...) + { + file.close(); + throw; + } + } + + bool LOCTreeNode::Compare(LOCTreeNode* a, LOCTreeNode* b) + { + if (!a || !b) + { + return false; + } + if (a->IsRoot() != b->IsRoot()) + { + return false; + } + if (!a->IsRoot() && a->name != b->name) + { + return false; + } + if (a->IsData() != b->IsData()) + { + return false; + } + if (a->IsData() && a->value != b->value) + { + return false; + } + if (a->IsContainer() != b->IsContainer()) + { + return false; + } + if (a->originalTypeRawData.has_value() != b->originalTypeRawData.has_value()) + { + return false; + } + if (a->originalTypeRawData.has_value()) + { + const auto& aOrgType = a->originalTypeRawData.value(); + const auto& bOrgType = b->originalTypeRawData.value(); + if (aOrgType != bOrgType) + { + return false; + } + } + if (a->IsContainer()) + { + if (a->numChild != b->numChild) return false; + assert(a->numChild == a->children.size()); + assert(b->numChild == b->children.size()); + + for (int i = 0; i < a->numChild; i++) + { + if (!LOCTreeNode::Compare(a->children[i], b->children[i])) + { + return false; + } + } + } + return true; + } +} \ No newline at end of file diff --git a/Modules/BMLOC/source/LOC/LOCTreeCompiler.cpp b/Modules/BMLOC/source/LOC/LOCTreeCompiler.cpp new file mode 100644 index 0000000..bb15c1d --- /dev/null +++ b/Modules/BMLOC/source/LOC/LOCTreeCompiler.cpp @@ -0,0 +1,49 @@ +#include +#include +#include + +namespace BM::LOC +{ + bool LOCTreeCompiler::Compile(Buffer& buffer, LOCTreeNode* rootNode, LOCSupportMode supportMode) + { + switch (supportMode) + { + case LOCSupportMode::Hitman_BloodMoney: + { + using Impl = BM::LOC::Internal::LOCCompilerImpl; + if (!Impl::MarkupTree(rootNode)) + { + return false; + } + + const auto& memMarkup = rootNode->memoryMarkup.value(); + buffer.resize(memMarkup.EndsAt); + + return Impl::Compile(buffer, rootNode); + } + case LOCSupportMode::Hitman_Contracts: + { + using Impl = BM::LOC::Internal::LOCCompilerImpl; + if (!Impl::MarkupTree(rootNode)) + { + return false; + } + + const auto& memMarkup = rootNode->memoryMarkup.value(); + buffer.resize(memMarkup.EndsAt); + + return Impl::Compile(buffer, rootNode); + } + case LOCSupportMode::Hitman_2SA: + case LOCSupportMode::Hitman_A47: + return false; + } + + return false; + } + + void LOCTreeCompiler::MarkupTree(LOCTreeNode* rootNode) + { + BM::LOC::Internal::LOCCompilerImpl::MarkupTree(rootNode); + } +} \ No newline at end of file diff --git a/Modules/BMLOC/source/LOC/LOCTreeFactory.cpp b/Modules/BMLOC/source/LOC/LOCTreeFactory.cpp new file mode 100644 index 0000000..733ee3e --- /dev/null +++ b/Modules/BMLOC/source/LOC/LOCTreeFactory.cpp @@ -0,0 +1,62 @@ +#include +#include + +#include + +namespace BM::LOC +{ + LOCTreeNode * LOCTreeFactory::Create() + { + auto newNode = new LOCTreeNode(nullptr, nullptr); + newNode->nodeType = TreeNodeType::NODE_WITH_CHILDREN; + + return newNode; + } + + LOCTreeNode* LOCTreeFactory::Create(const std::string& name, TreeNodeType nodeType, LOCTreeNode* parent) + { + if (name.empty() && (parent || nodeType != TreeNodeType::NODE_WITH_CHILDREN)) + { + throw std::runtime_error { "LOC Compiler Error: Empty name allowed only for root node of type TreeNodeType::NODE_WITH_CHILDREN!" }; + } + + if (name.find('/') != std::string::npos) + { + throw std::runtime_error { "LOC Compiler Error: Bad reference '" + name + "'. Special characters are not allowed in keys!" }; + } + + auto newNode = new LOCTreeNode(parent, nullptr); + newNode->nodeType = nodeType; + if (!name.empty()) + { + newNode->name = name; + } + + return newNode; + } + + LOCTreeNode* LOCTreeFactory::Create(const std::string& name, const std::string& value, LOCTreeNode* parent) + { + if (name.empty()) + { + throw std::runtime_error { "LOC Compiler Error: Empty name not allowed for key-value node!" }; + } + + if (value.empty()) + { + throw std::runtime_error { "LOC Compiler Error: Empty value not allowed!" }; + } + + if (name.find('/') != std::string::npos) + { + throw std::runtime_error { "LOC Compiler Error: Bad reference '" + name + "'. Special characters are not allowed in keys!" }; + } + + auto newNode = new LOCTreeNode(parent, nullptr); + newNode->nodeType = TreeNodeType::VALUE_OR_DATA; + newNode->name = name; + newNode->value = value; + + return newNode; + } +} \ No newline at end of file diff --git a/Modules/BMLOC/tests/cases/CheckCompiler_Common_Test.cpp b/Modules/BMLOC/tests/cases/CheckCompiler_Common_Test.cpp new file mode 100644 index 0000000..f76e905 --- /dev/null +++ b/Modules/BMLOC/tests/cases/CheckCompiler_Common_Test.cpp @@ -0,0 +1,520 @@ +#include + +#include + +#include +#include +#include +#include + +#include +#include + +#include + +class ResourceCollection +{ +public: + static char* Lookup(char* key, char* buffer); +}; + +using namespace BM::LOC; + +class LOC_Compiler_Common : public ::testing::Test +{ +protected: + /** + * Check sample localization tree + */ + static void CheckSampleTree(const LOCTreeNode* root); + + /** + * Generate sample localization tree : + * /AllLevels/Actions/OpenDoor == "Open Door" + * /AllLevels/Actions/CloseDoor == "Close Door" + * /M01/Actions/Wakeup == "Wake Up" + * + * @return pointer to allocated tree + */ + static LOCTreeNode* CreateSampleTree(); + + /** + * Check compiled sample tree by dictionary (search keys): + * /AllLevels/Actions/OpenDoor == "Open Door" + * /AllLevels/Actions/CloseDoor == "Close Door" + * /M01/Actions/Wakeup == "Wake Up" + * + * @param buffer pointer to buffer with compiled LOC + */ + static void CheckCompiledSampleTreViaDictionary(char* buffer); +}; + +TEST_F(LOC_Compiler_Common, LocSerialization) +{ + auto root = CreateSampleTree(); + ASSERT_NE(root, nullptr); + + // Check original tree + CheckSampleTree(root); + + // Serialize to JSON + std::string locJsonStr; + { + nlohmann::json locJson; + nlohmann::adl_serializer::to_json(locJson, root); + locJsonStr = locJson.dump(); + } + + // Deserialize + LOCTreeNode* deserializedRoot = nullptr; + { + deserializedRoot = LOCTreeFactory::Create(); + auto locJson = nlohmann::json::parse(locJsonStr); + nlohmann::adl_serializer::from_json(locJson, deserializedRoot); + } + + // Check tree + CheckSampleTree(deserializedRoot); + + delete root; + delete deserializedRoot; +} + +TEST_F(LOC_Compiler_Common, SerializeAndDeSerializeTreeWithDeadEnds) +{ + /* + * Tree: + * /AllLevels + * /Enemies !root node without children! + * /Allies + * /1 - "Diana" + * /2 - "Smith" + * + */ + auto root = LOCTreeFactory::Create(); + auto AllLevels = LOCTreeFactory::Create("AllLevels", TreeNodeType::NODE_WITH_CHILDREN, root); + root->AddChild(AllLevels); + + auto Enemies = LOCTreeFactory::Create("Enemies", TreeNodeType::NODE_WITH_CHILDREN, AllLevels); + AllLevels->AddChild(Enemies); + + auto Allies = LOCTreeFactory::Create("Allies", TreeNodeType::NODE_WITH_CHILDREN, AllLevels); + AllLevels->AddChild(Allies); + + auto AL01 = LOCTreeFactory::Create("1", "Diana", Allies); + Allies->AddChild(AL01); + + auto AL02 = LOCTreeFactory::Create("2", "Smith", Allies); + Allies->AddChild(AL02); + + // Serialize to json + nlohmann::json source; + + ASSERT_NO_THROW(nlohmann::adl_serializer::to_json(source, root)); + + // Deserialize back + LOCTreeNode* newRoot = LOCTreeFactory::Create(); + ASSERT_NO_THROW(nlohmann::adl_serializer::from_json(source, newRoot)); + + // Compare trees + bool equality = false; + ASSERT_NO_THROW((equality = LOCTreeNode::Compare(root, newRoot))); + ASSERT_TRUE(equality); +} + +TEST_F(LOC_Compiler_Common, FullCompilerCheck) +{ + // Generate sample tree + auto root = CreateSampleTree(); + CheckSampleTree(root); + + // Compile root tree + std::vector compiledTreeBuffer {}; + bool compileResult = false; + + ASSERT_NO_THROW((compileResult = LOCTreeNode::Compile(root, compiledTreeBuffer))); + ASSERT_TRUE(compileResult); + + // Decompile tree + LOCTreeNode* newRoot = nullptr; + ASSERT_NO_THROW((newRoot = LOCTreeNode::ReadFromMemory((char*)compiledTreeBuffer.data(), compiledTreeBuffer.size()))); + ASSERT_NE(newRoot, nullptr); + CheckSampleTree(newRoot); + + // Search in binary + CheckCompiledSampleTreViaDictionary((char*)compiledTreeBuffer.data()); + + // Free memory + delete root; + delete newRoot; +} + +TEST_F(LOC_Compiler_Common, ShortCheckPathFinder) +{ + static const char* kChild { "Child" }; + + auto root = LOCTreeFactory::Create(); + auto child = LOCTreeFactory::Create("Child", "Another one", root); + root->AddChild(child); + + // compile + std::vector compiledBuffer {}; + bool compileResult = false; + + ASSERT_NO_THROW((compileResult = LOCTreeNode::Compile(root, compiledBuffer))); + ASSERT_TRUE(compileResult); + + // try to find exists key + auto val = ResourceCollection::Lookup((char*)kChild, (char*)compiledBuffer.data()); + ASSERT_NE(val, nullptr); + ASSERT_EQ(std::string(val + 1), child->value); + + // Free + delete root; +} + +TEST_F(LOC_Compiler_Common, CheckLocFinderInDeepTree) +{ + /** + * Tree: + * /AllLevels + * /Actions + * /OpenDoor = "Open this door!" + * /CloseDoor = "Close this door!" + * /SomeThirdAction = "Some cool action" + * /Dialogs + * /First = "First long text ..." + * /M13 + * /SceneName = "Requiem" + * Valid paths: + * /AllLevels/Actions/OpenDoor + * /AllLevels/Actions/CloseDoor + * /AllLevels/Actions/SomeThirdAction + * /AllLevels/Dialogs/First + */ + auto root = LOCTreeFactory::Create(); + + auto AllLevels = LOCTreeFactory::Create("AllLevels", TreeNodeType::NODE_WITH_CHILDREN, root); + root->AddChild(AllLevels); + + auto Actions = LOCTreeFactory::Create("Actions", TreeNodeType::NODE_WITH_CHILDREN, AllLevels); + AllLevels->AddChild(Actions); + + auto OpenDoor = LOCTreeFactory::Create("OpenDoor", "Open this door!", Actions); + Actions->AddChild(OpenDoor); + + auto CloseDoor = LOCTreeFactory::Create("CloseDoor", "Close this door!", Actions); + Actions->AddChild(CloseDoor); + + auto SomeThirdAction = LOCTreeFactory::Create("SomeThirdAction", "Some cool action", Actions); + Actions->AddChild(SomeThirdAction); + + auto Dialogs = LOCTreeFactory::Create("Dialogs", TreeNodeType::NODE_WITH_CHILDREN, AllLevels); + AllLevels->AddChild(Dialogs); + + auto First = LOCTreeFactory::Create("First", "First long text ...", Dialogs); + Dialogs->AddChild(First); + + auto M13 = LOCTreeFactory::Create("M13", TreeNodeType::NODE_WITH_CHILDREN, root); + root->AddChild(M13); + + auto SceneName = LOCTreeFactory::Create("SceneName", "Requiem", M13); + M13->AddChild(SceneName); + + // Compile + std::vector compiledBuffer {}; + bool compileResult = false; + + ASSERT_NO_THROW((compileResult = LOCTreeNode::Compile(root, compiledBuffer))); + ASSERT_TRUE(compileResult); + + static const char* kOpenDoorPath = "/AllLevels/Actions/OpenDoor"; + static const char* kOpenDoorValue = "Open this door!"; + + static const char* kCloseDoorPath = "/AllLevels/Actions/CloseDoor"; + static const char* kCloseDoorValue = "Close this door!"; + + static const char* kSomeThirdActionPath = "/AllLevels/Actions/SomeThirdAction"; + static const char* kSomeThirdActionValue = "Some cool action"; + + static const char* kDialogsFirstPath = "/AllLevels/Dialogs/First"; + static const char* kDialogsFirstValue = "First long text ..."; + + { + char* result = ResourceCollection::Lookup((char*)kOpenDoorPath, (char*)compiledBuffer.data()); //OK + EXPECT_NE(result, nullptr); + if (result != nullptr) + { + EXPECT_EQ(std::string(result + 1), std::string(kOpenDoorValue)); + } + } +// + { + char* result = ResourceCollection::Lookup((char*)kCloseDoorPath, (char*)compiledBuffer.data()); //OK + EXPECT_NE(result, nullptr); + if (result != nullptr) + { + EXPECT_EQ(std::string(result + 1), std::string(kCloseDoorValue)); + } + } + + { + char* result = ResourceCollection::Lookup((char*)kSomeThirdActionPath, (char*)compiledBuffer.data()); //OK + EXPECT_NE(result, nullptr); + if (result != nullptr) + { + EXPECT_EQ(std::string(result + 1), std::string(kSomeThirdActionValue)); + } + } + + { + char* result = ResourceCollection::Lookup((char*)kDialogsFirstPath, (char*)compiledBuffer.data()); //OK + EXPECT_NE(result, nullptr); + if (result != nullptr) + { + EXPECT_EQ(std::string(result + 1), std::string(kDialogsFirstValue)); + } + } + + delete root; +} + +void LOC_Compiler_Common::CheckSampleTree(const LOCTreeNode* root) +{ + // First checks + ASSERT_EQ(root->numChild, 2); + ASSERT_EQ(root->nodeType, TreeNodeType::NODE_WITH_CHILDREN); + ASSERT_EQ(root->children.size(), root->numChild); + + // Check root nodes + ASSERT_EQ(root->children[0]->name, "AllLevels"); + ASSERT_EQ(root->children[1]->name, "M01"); + ASSERT_EQ(root->children[0]->parent, root); + ASSERT_EQ(root->children[1]->parent, root); + + // Check AllLevels + ASSERT_EQ(root->children[0]->nodeType, TreeNodeType::NODE_WITH_CHILDREN); + ASSERT_EQ(root->children[0]->numChild, 1); + + // Check AllLevels/Actions + ASSERT_EQ(root->children[0]->children[0]->nodeType, TreeNodeType::NODE_WITH_CHILDREN); + ASSERT_EQ(root->children[0]->children[0]->name, "Actions"); + ASSERT_EQ(root->children[0]->children[0]->numChild, 2); + ASSERT_EQ(root->children[0]->children[0]->children.size(), root->children[0]->children[0]->numChild); + ASSERT_EQ(root->children[0]->children[0]->parent, root->children[0]); + + // AllLevels/Actions/CloseDoor + ASSERT_EQ(root->children[0]->children[0]->children[0]->nodeType, TreeNodeType::VALUE_OR_DATA); + ASSERT_EQ(root->children[0]->children[0]->children[0]->name, "CloseDoor"); + ASSERT_EQ(root->children[0]->children[0]->children[0]->value, "Close Door"); + ASSERT_EQ(root->children[0]->children[0]->children[0]->parent, root->children[0]->children[0]); + + // AllLevels/Actions/OpenDoor + ASSERT_EQ(root->children[0]->children[0]->children[1]->nodeType, TreeNodeType::VALUE_OR_DATA); + ASSERT_EQ(root->children[0]->children[0]->children[1]->name, "OpenDoor"); + ASSERT_EQ(root->children[0]->children[0]->children[1]->value, "Open Door"); + ASSERT_EQ(root->children[0]->children[0]->children[1]->parent, root->children[0]->children[0]); + + // M01 + ASSERT_EQ(root->children[1]->nodeType, TreeNodeType::NODE_WITH_CHILDREN); + ASSERT_EQ(root->children[1]->numChild, 1); + ASSERT_EQ(root->children[1]->name, "M01"); + ASSERT_EQ(root->children[1]->children.size(), root->children[1]->numChild); + ASSERT_EQ(root->children[1]->parent, root); + + // M01/Actions + ASSERT_EQ(root->children[1]->children[0]->nodeType, TreeNodeType::NODE_WITH_CHILDREN); + ASSERT_EQ(root->children[1]->children[0]->numChild, 1); + ASSERT_EQ(root->children[1]->children[0]->name, "Actions"); + ASSERT_EQ(root->children[1]->children[0]->children.size(), root->children[1]->children[0]->numChild); + ASSERT_EQ(root->children[1]->children[0]->parent, root->children[1]); + + // M01/Actions/Wakeup + ASSERT_EQ(root->children[1]->children[0]->children[0]->nodeType, TreeNodeType::VALUE_OR_DATA); + ASSERT_EQ(root->children[1]->children[0]->children[0]->name, "Wakeup"); + ASSERT_EQ(root->children[1]->children[0]->children[0]->value, "Wake Up"); + ASSERT_EQ(root->children[1]->children[0]->children[0]->parent, root->children[1]->children[0]); +} + +LOCTreeNode* LOC_Compiler_Common::CreateSampleTree() +{ + auto root = LOCTreeFactory::Create(); + + // All levels + { + // First child + auto allLevels = LOCTreeFactory::Create("AllLevels", TreeNodeType::NODE_WITH_CHILDREN, root); + { + //Actions tree + auto actions = LOCTreeFactory::Create("Actions", TreeNodeType::NODE_WITH_CHILDREN, allLevels); + // Actions + { + auto openDoor = LOCTreeFactory::Create("OpenDoor", "Open Door", actions); + actions->AddChild(openDoor); + + auto closeDoor = LOCTreeFactory::Create("CloseDoor", "Close Door", actions); + actions->AddChild(closeDoor); + } + allLevels->AddChild(actions); + } + root->AddChild(allLevels); + } + + // M01 + { + auto m01 = LOCTreeFactory::Create("M01", TreeNodeType::NODE_WITH_CHILDREN, root); + // Actions + { + auto actions = LOCTreeFactory::Create("Actions", TreeNodeType::NODE_WITH_CHILDREN, m01); + + // Wake Up + { + auto wakeUp = LOCTreeFactory::Create("Wakeup", "Wake Up", actions); + actions->AddChild(wakeUp); + } + m01->AddChild(actions); + } + + root->AddChild(m01); + } + + return root; +} + +void LOC_Compiler_Common::CheckCompiledSampleTreViaDictionary(char* buffer) +{ + static const char* kPath1 = "/AllLevels/Actions/OpenDoor"; + static const char* kPath2 = "/AllLevels/Actions/CloseDoor"; + static const char* kPath3 = "/M01/Actions/Wakeup"; + + const char* kValue1 = ResourceCollection::Lookup((char*)kPath1, buffer); + EXPECT_NE(kValue1, nullptr); + if (kValue1 != nullptr) + { + EXPECT_EQ(std::string(kValue1 + 1), std::string("Open Door")); + } + + const char* kValue2 = ResourceCollection::Lookup((char*)kPath2, buffer); + EXPECT_NE(kValue2, nullptr); + if (kValue2 != nullptr) + { + EXPECT_EQ(std::string(kValue2 + 1), std::string("Close Door")); + } + + const char* kValue3 = ResourceCollection::Lookup((char*)kPath3, buffer); + EXPECT_NE(kValue3, nullptr); + if (kValue3 != nullptr) + { + EXPECT_EQ(std::string(kValue3 + 1), std::string("Wake Up")); + } +} + +char* ResourceCollection::Lookup(char* key, char* buffer) +{ + // This is result of reverse engineering of function at 0x00464FF0 aka ResourceCollection::Lookup + char *keyWithoutLeadingSlash; // edx + char currentChar; // al + char currentCharInKeyWithoutLeadingSlash; // al + size_t newIndex; // ecx + int numChild; // ebx + int keyIndex; // ebp + int v8; // edi + int v9; // eax + int v10; // eax + int v11; // esi + int v12; // esi + char *valuePtr; // edx + size_t index; // [esp+10h] [ebp-Ch] + int numChildOrg; // [esp+14h] [ebp-8h] + char *pChunkName; // [esp+18h] [ebp-4h] + + while (true) + { + /// KEY NORMALISATION + keyWithoutLeadingSlash = key; + if ( *key == '/' ) /// Search place where our key starts not from / + { + do + currentChar = (keyWithoutLeadingSlash++)[1]; + while (currentChar == '/' ); + key = keyWithoutLeadingSlash; + } + + currentCharInKeyWithoutLeadingSlash = *keyWithoutLeadingSlash; + newIndex = 0; + index = 0; + + if (*keyWithoutLeadingSlash != '/' ) + { + do + { + if ( !currentCharInKeyWithoutLeadingSlash ) // If we have zero terminator -> break + break; + + currentCharInKeyWithoutLeadingSlash = keyWithoutLeadingSlash[newIndex++ + 1]; // save current char and increment newIndex + } + while (currentCharInKeyWithoutLeadingSlash != '/' ); // if our new char not slash -> continue + + index = newIndex; + } + + /// KEY SEARCH AT THE CURRENT BRANCH + numChild = (unsigned __int8)*buffer; + keyIndex = 0; + numChildOrg = (unsigned __int8)*buffer; + if (numChild <= 0 ) + goto OnOrphanedTreeNodeDetected; + + pChunkName = &buffer[4 * numChild - 3]; + do + { + v8 = (numChild >> 1) + keyIndex; + if ( v8 ) + v9 = *(int *)&buffer[4 * v8 - 3]; + else + v9 = 0; + + int ret = 0; + if ((ret = strnicmp(&pChunkName[v9], keyWithoutLeadingSlash, newIndex)) >= 0) // if value of first group greater or equal to our key + { + numChild >>= 1; // Divide by two + } + else // Group name is less than our key + { + keyIndex = v8 + 1; + numChild += -1 - (numChild >> 1); + } + + newIndex = index; + keyWithoutLeadingSlash = key; + } + while (numChild > 0); + + /// VALUE RESOLVING + numChild = numChildOrg; //Restore back? o_0 + if ( keyIndex ) + v10 = *(int*)&buffer[4 * keyIndex - 3]; + else + OnOrphanedTreeNodeDetected: + v10 = 0; + v11 = v10 + 4 * numChild - 3; + + int ret = 0; + if (keyIndex >= numChild || (ret = strnicmp(&buffer[v11], keyWithoutLeadingSlash, newIndex))) + { + return nullptr; + } + + /// ITERATION OVER TREE + v12 = index + v11; + valuePtr = &buffer[v12 + 2]; + buffer += v12 + 2; + + if ( !key[index] ) + { + return valuePtr - 1; + } + + key += index + 1; + } +} \ No newline at end of file diff --git a/Modules/BMLOC/tests/cases/CheckCompiler_Linker_Test.cpp b/Modules/BMLOC/tests/cases/CheckCompiler_Linker_Test.cpp new file mode 100644 index 0000000..7059cfd --- /dev/null +++ b/Modules/BMLOC/tests/cases/CheckCompiler_Linker_Test.cpp @@ -0,0 +1,404 @@ +#include + +#include +#include +#include +#include + +using namespace BM::LOC; + +TEST(CheckCompiler_Linker, LinkerMemoryMarkupGenericNodePlacement) +{ + /** + * Simple case: + * Generate tree + * + * /First + * /Second + * + * Compile it, check index table, check values, try to decompile, check the tree state + */ + + auto root = LOCTreeFactory::Create(); + + { + { + auto firstChild = LOCTreeFactory::Create("First", "FirstResult", root); + root->AddChild(firstChild); + } + { + auto secondChild = LOCTreeFactory::Create("Second", "SecondResult", root); + root->AddChild(secondChild); + } + } + + // Check tree structure + ASSERT_EQ(root->numChild, 2); + ASSERT_EQ(root->numChild, root->children.size()); + + // Check that our nodes are not marked up already + ASSERT_FALSE(root->memoryMarkup.has_value()); + ASSERT_FALSE(root->children[0]->memoryMarkup.has_value()); + ASSERT_FALSE(root->children[1]->memoryMarkup.has_value()); + + // Generate markup + LOCTreeCompiler::MarkupTree(root); + + ASSERT_TRUE(root->memoryMarkup.has_value()); + ASSERT_TRUE(root->children[0]->memoryMarkup.has_value()); + ASSERT_TRUE(root->children[1]->memoryMarkup.has_value()); + + // Lookup generated markup + ASSERT_TRUE(root->memoryMarkup.value().StartsAt < root->memoryMarkup.value().EndsAt); + + const auto& rootMarkup = root->memoryMarkup.value(); + const auto& firstMarkup = root->children[0]->memoryMarkup.value(); + const auto& secondMarkup = root->children[1]->memoryMarkup.value(); + + ASSERT_TRUE(firstMarkup.EndsAt - 1 < secondMarkup.StartsAt); + ASSERT_TRUE(secondMarkup.EndsAt - 1 < rootMarkup.EndsAt); + + // Compile placement into raw buffer + bool compileResult = false; + LOCTreeCompiler::Buffer compiledBuffer; + ASSERT_NO_THROW((compileResult = LOCTreeCompiler::Compile(compiledBuffer, root))); + ASSERT_TRUE(compileResult); + + // Check through decompiler + LOCTreeNode* decompiledRoot = nullptr; + { + // Try to decompile + ASSERT_NO_THROW((decompiledRoot = LOCTreeNode::ReadFromMemory((char*)compiledBuffer.data(), compiledBuffer.size()))); + ASSERT_NE(decompiledRoot, nullptr); + + // If ok - check tree + ASSERT_EQ(decompiledRoot->nodeType, root->nodeType); + ASSERT_EQ(decompiledRoot->numChild, root->numChild); + ASSERT_EQ(decompiledRoot->children.size(), root->children.size()); + + for (int i = 0; i < decompiledRoot->numChild; i++) + { + auto decompiledChild = decompiledRoot->children[i]; + auto originalChild = root->children[i]; + + ASSERT_EQ(decompiledChild->nodeType, originalChild->nodeType); + ASSERT_EQ(decompiledChild->name, originalChild->name); + ASSERT_EQ(decompiledChild->value, originalChild->value); + } + } // All ok for simple case + + // Free + delete root; + delete decompiledRoot; +} + +static LOCTreeNode* CreateSampleTreeWithMultipleSubtrees() +{ + /** + * Generate Tree Like M13 + * + * /AllLevels + * /Actions + * /ActionKill - "Kill actor" + * /ActionOpenDoor - "Open Door" + * /ActionCloseDoor - "Close Door" + * /Dialogs + * /Talk + * /01 - "It's just a long-long string, nothing more" + * /02 - "Hello world!" + * /Random + * /01 - "It's just a random phrase!" + * /FMVSubtitles + * /Intro + * /1018-1171 - "Welcome, Mr. Johnson, welcome. May I take your briefcase?" + * /1180-1220 - "No! I'll keep it!" + * /1250-1507 - "I've heard interesting things about your establishment... I'd like to see what you have to offer...Preferably in the back.." + * /1523-1634 - "Of course, sir. Right this way. . ." + * /Outro + * /1823-1889 - "We admire your objectivity, Rick." + * /1904-1933 - "Thank you" + * /1938-2272 - "Don't mention it. We need to get to the chaplain - 47’s ashes are going to take pride of place on my mantel. Chaplain! Chaplain!" + * /M13 + * /MissionObjectives + * /1 + * /Objective - "Leave NO witnesses" + * /ObjectiveDetail - "Everyone attending the funeral, must be eliminated" + * /Type - "T" + * /old + * /1 + * /Age - "47" + * /Hair - "Grey" + * /Height - "5'11 ft" + * /Objective - "Kill Head of Alpha Zerox" + * /ObjectiveDetail - "Cayne is from an extremely wealthy and influential family with a dynastic lineage tracing back to the Mayflower..." + * /Weight - "116 lbs" + * /WhoOrWhat - "Alexander Leland Cayne" + * /2 + * /Objective - "Retrieve journalist tape recorder" + * /Type - "R" + * /WhoOrWhat - "Tape recorder" + * /3 + * /Objective - "Escape in limo" + * /Type - "E" + * /WhoOrWhat - "Limo" + */ + auto root = LOCTreeFactory::Create(); + + auto AllLevels = LOCTreeFactory::Create("AllLevels", TreeNodeType::NODE_WITH_CHILDREN, root); + auto FMVSubtitles = LOCTreeFactory::Create("FMVSubtitles", TreeNodeType::NODE_WITH_CHILDREN, root); + auto M13 = LOCTreeFactory::Create("M13", TreeNodeType::NODE_WITH_CHILDREN, root); + + { + auto Actions = LOCTreeFactory::Create("Actions", TreeNodeType::NODE_WITH_CHILDREN, AllLevels); + { + auto ActionKill = LOCTreeFactory::Create("ActionKill", "Kill actor", Actions); + Actions->AddChild(ActionKill); + } + { + auto ActionOpenDoor = LOCTreeFactory::Create("ActionOpenDoor", "Open Door", Actions); + Actions->AddChild(ActionOpenDoor); + } + { + auto ActionCloseDoor = LOCTreeFactory::Create("ActionCloseDoor", "Close Door", Actions); + Actions->AddChild(ActionCloseDoor); + } + AllLevels->AddChild(Actions); + + auto Dialogs = LOCTreeFactory::Create("Dialogs", TreeNodeType::NODE_WITH_CHILDREN, AllLevels); + { + auto Talk = LOCTreeFactory::Create("Talk", TreeNodeType::NODE_WITH_CHILDREN, Dialogs); + { + auto T01 = LOCTreeFactory::Create("01", "It's just a long-long string, nothing more", Talk); + Talk->AddChild(T01); + } + { + auto T02 = LOCTreeFactory::Create("02", "Hello world!", Talk); + Talk->AddChild(T02); + } + Dialogs->AddChild(Talk); + } + { + auto Random = LOCTreeFactory::Create("Random", TreeNodeType::NODE_WITH_CHILDREN, Dialogs); + { + auto T01 = LOCTreeFactory::Create("01", "It's just a random phrase!", Random); + Random->AddChild(T01); + } + Dialogs->AddChild(Random); + } + AllLevels->AddChild(Dialogs); + } + root->AddChild(AllLevels); + + { + { + auto Intro = LOCTreeFactory::Create("Intro", TreeNodeType::NODE_WITH_CHILDREN, FMVSubtitles); + { + auto T01 = LOCTreeFactory::Create("1018-1171", "Welcome, Mr. Johnson, welcome. May I take your briefcase?", Intro); + Intro->AddChild(T01); + } + { + auto T02 = LOCTreeFactory::Create("1180-1220", "No! I'll keep it!", Intro); + Intro->AddChild(T02); + } + { + auto T03 = LOCTreeFactory::Create( + "1250-1507", + "I've heard interesting things about your establishment... I'd like to see what you have to offer...Preferably in the back..", + Intro); + Intro->AddChild(T03); + } + { + auto T04 = LOCTreeFactory::Create("1523-1634", "Of course, sir. Right this way. . .", Intro); + Intro->AddChild(T04); + } + FMVSubtitles->AddChild(Intro); + } + { + auto Outro = LOCTreeFactory::Create("Outro", TreeNodeType::NODE_WITH_CHILDREN, FMVSubtitles); + { + auto T01 = LOCTreeFactory::Create("1823-1889", "We admire your objectivity, Rick.", Outro); + Outro->AddChild(T01); + } + { + auto T02 = LOCTreeFactory::Create("1904-1933", "Thank you", Outro); + Outro->AddChild(T02); + } + { + auto T03 = LOCTreeFactory::Create( + "1938-2272", + "Don't mention it. We need to get to the chaplain - 47’s ashes are going to take pride of place on my mantel. Chaplain! Chaplain!", + Outro); + Outro->AddChild(T03); + } + FMVSubtitles->AddChild(Outro); + } + } + root->AddChild(FMVSubtitles); + + { + { + auto MO1 = LOCTreeFactory::Create("1", TreeNodeType::NODE_WITH_CHILDREN, M13); + { + auto Objective = LOCTreeFactory::Create("Objective", "Leave NO witnesses", MO1); + MO1->AddChild(Objective); + } + { + auto ObjectiveDetail = LOCTreeFactory::Create( + "ObjectiveDetail", + "Everyone attending the funeral, must be eliminated", + MO1); + MO1->AddChild(ObjectiveDetail); + } + { + auto Type = LOCTreeFactory::Create("Type", "T", MO1); + MO1->AddChild(Type); + } + M13->AddChild(MO1); + } + { + auto Old = LOCTreeFactory::Create("old", TreeNodeType::NODE_WITH_CHILDREN, M13); + { + auto O1 = LOCTreeFactory::Create("1", TreeNodeType::NODE_WITH_CHILDREN, Old); + { + auto Age = LOCTreeFactory::Create("Age", "47", O1); + O1->AddChild(Age); + } + { + auto Hair = LOCTreeFactory::Create("Hair", "Grey", O1); + O1->AddChild(Hair); + } + { + auto Height = LOCTreeFactory::Create("Height", "5'11 ft", O1); + O1->AddChild(Height); + } + { + auto Objective = LOCTreeFactory::Create("Objective", "Kill Head of Alpha Zerox", O1); + O1->AddChild(Objective); + } + { + auto ObjectiveDetail = LOCTreeFactory::Create( + "ObjectiveDetail", + "Cayne is from an extremely wealthy and influential family with a dynastic lineage tracing back to the Mayflower...", + O1); + O1->AddChild(ObjectiveDetail); + } + { + auto Weight = LOCTreeFactory::Create("Weight", "116 lbs", O1); + O1->AddChild(Weight); + } + { + auto WhoOrWhat = LOCTreeFactory::Create("WhoOrWhat", "Alexander Leland Cayne", O1); + O1->AddChild(WhoOrWhat); + } + Old->AddChild(O1); + } + { + auto O2 = LOCTreeFactory::Create("2", TreeNodeType::NODE_WITH_CHILDREN, Old); + { + auto Objective = LOCTreeFactory::Create("Objective", "Retrieve journalist tape recorder", O2); + O2->AddChild(Objective); + } + { + auto Type = LOCTreeFactory::Create("Type", "R", O2); + O2->AddChild(Type); + } + { + auto WhoOrWhat = LOCTreeFactory::Create("WhoOrWhat", "Tape recorder", O2); + O2->AddChild(WhoOrWhat); + } + Old->AddChild(O2); + } + { + auto O3 = LOCTreeFactory::Create("3", TreeNodeType::NODE_WITH_CHILDREN, Old); + { + auto Objective = LOCTreeFactory::Create("Objective", "Escape in limo", O3); + O3->AddChild(Objective); + } + { + auto Type = LOCTreeFactory::Create("Type", "E", O3); + O3->AddChild(Type); + } + { + auto WhoOrWhat = LOCTreeFactory::Create("WhoOrWhat", "Limo", O3); + O3->AddChild(WhoOrWhat); + } + Old->AddChild(O3); + } + M13->AddChild(Old); + } + } + root->AddChild(M13); + + return root; +} + +static void CompareTreeWithOriginal(LOCTreeNode* r1, LOCTreeNode* r2) +{ + // Check + ASSERT_NE(r1, nullptr); + ASSERT_NE(r2, nullptr); + ASSERT_NE(r1, r2); + + // Compare node types + ASSERT_EQ(r1->nodeType, r2->nodeType); + ASSERT_EQ(r1->IsRoot(), r2->IsRoot()); + if (!r1->IsRoot()) + { + ASSERT_EQ(r1->name, r2->name); + } + + ASSERT_EQ(r1->originalTypeRawData.has_value(), r2->originalTypeRawData.has_value()); + if (r1->originalTypeRawData.has_value()) + { + const auto& otrdv1 = r1->originalTypeRawData.value(); + const auto& otrdv2 = r2->originalTypeRawData.value(); + ASSERT_EQ(otrdv1, otrdv2); + } + + if (r1->IsData()) + { + ASSERT_EQ(r1->IsData(), r2->IsData()); // It's actually not required because internal types should be equal, but why not? + // Compare end points + + ASSERT_EQ(r1->name, r2->name); + ASSERT_EQ(r1->value, r2->value); + } + else if (r1->IsContainer()) + { + ASSERT_EQ(r1->IsContainer(), r2->IsContainer()); // Not required too but why not? + + // Compare names and iterate over endpoints + ASSERT_EQ(r1->name, r2->name); + ASSERT_EQ(r1->numChild, r2->numChild); + ASSERT_EQ(r1->children.size(), r1->numChild); + ASSERT_EQ(r2->children.size(), r2->numChild); + + for (int entityIndex = 0; entityIndex < r1->numChild; entityIndex++) + { + CompareTreeWithOriginal(r1->children[entityIndex], r2->children[entityIndex]); + } + } +} + +TEST(CheckCompiler_Linker, SubTreeMultiTableLinking) +{ + auto root = CreateSampleTreeWithMultipleSubtrees(); + ASSERT_NE(root, nullptr); + + // Compile it + LOCTreeCompiler::Buffer compiledTreeBuffer; + bool compileResult = false; + ASSERT_NO_THROW((compileResult = LOCTreeCompiler::Compile(compiledTreeBuffer, root))); + ASSERT_TRUE(compileResult); + + // Decompile it + LOCTreeNode* decompiledRoot = nullptr; + ASSERT_NO_THROW((decompiledRoot = LOCTreeNode::ReadFromMemory((char*)compiledTreeBuffer.data(), compiledTreeBuffer.size()))); + ASSERT_NE(decompiledRoot, nullptr); + + // Check it + CompareTreeWithOriginal(root, decompiledRoot); + + delete root; + delete decompiledRoot; +} \ No newline at end of file diff --git a/Modules/BMLOC/tests/main.cpp b/Modules/BMLOC/tests/main.cpp new file mode 100644 index 0000000..e59bea0 --- /dev/null +++ b/Modules/BMLOC/tests/main.cpp @@ -0,0 +1,7 @@ +#include + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/Modules/CLI11 b/Modules/CLI11 new file mode 160000 index 0000000..3bf24c5 --- /dev/null +++ b/Modules/CLI11 @@ -0,0 +1 @@ +Subproject commit 3bf24c51646eaf625d4f218b9324fb661574927e diff --git a/Modules/gtest b/Modules/gtest new file mode 160000 index 0000000..9dce5e5 --- /dev/null +++ b/Modules/gtest @@ -0,0 +1 @@ +Subproject commit 9dce5e5d878176dc0054ef381f5c6e705f43ef99 diff --git a/Modules/minizip/CMakeLists.txt b/Modules/minizip/CMakeLists.txt new file mode 100644 index 0000000..4279b29 --- /dev/null +++ b/Modules/minizip/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.16) +project(minizip C) + +file(GLOB MINIZIP_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/source/*.c) + +add_library(minizip STATIC ${MINIZIP_SOURCES}) +target_include_directories( + minizip PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include + $/.. + $ + $ +) +target_link_libraries(minizip PUBLIC zlibstatic) \ No newline at end of file diff --git a/Modules/minizip/include/crypt.h b/Modules/minizip/include/crypt.h new file mode 100644 index 0000000..622f4bc --- /dev/null +++ b/Modules/minizip/include/crypt.h @@ -0,0 +1,132 @@ +/* crypt.h -- base code for crypt/uncrypt ZIPfile + + + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant + + This code is a modified version of crypting code in Infozip distribution + + The encryption/decryption parts of this source code (as opposed to the + non-echoing password parts) were originally written in Europe. The + whole source package can be freely distributed, including from the USA. + (Prior to January 2000, re-export from the US was a violation of US law.) + + This encryption code is a direct transcription of the algorithm from + Roger Schlafly, described by Phil Katz in the file appnote.txt. This + file (appnote.txt) is distributed with the PKZIP program (even in the + version without encryption capabilities). + + If you don't need crypting in your application, just define symbols + NOCRYPT and NOUNCRYPT. + + This code support the "Traditional PKWARE Encryption". + + The new AES encryption added on Zip format by Winzip (see the page + http://www.winzip.com/aes_info.htm ) and PKWare PKZip 5.x Strong + Encryption is not supported. +*/ + +#define CRC32(c, b) ((*(pcrc_32_tab+(((int)(c) ^ (b)) & 0xff))) ^ ((c) >> 8)) + +/*********************************************************************** + * Return the next byte in the pseudo-random sequence + */ +static int decrypt_byte(unsigned long* pkeys, const unsigned long* pcrc_32_tab) +{ + unsigned temp; /* POTENTIAL BUG: temp*(temp^1) may overflow in an + * unpredictable manner on 16-bit systems; not a problem + * with any known compiler so far, though */ + + temp = ((unsigned)(*(pkeys+2)) & 0xffff) | 2; + return (int)(((temp * (temp ^ 1)) >> 8) & 0xff); +} + +/*********************************************************************** + * Update the encryption keys with the next byte of plain text + */ +static int update_keys(unsigned long* pkeys,const unsigned long* pcrc_32_tab,int c) +{ + (*(pkeys+0)) = CRC32((*(pkeys+0)), c); + (*(pkeys+1)) += (*(pkeys+0)) & 0xff; + (*(pkeys+1)) = (*(pkeys+1)) * 134775813L + 1; + { + register int keyshift = (int)((*(pkeys+1)) >> 24); + (*(pkeys+2)) = CRC32((*(pkeys+2)), keyshift); + } + return c; +} + + +/*********************************************************************** + * Initialize the encryption keys and the random header according to + * the given password. + */ +static void init_keys(const char* passwd,unsigned long* pkeys,const unsigned long* pcrc_32_tab) +{ + *(pkeys+0) = 305419896L; + *(pkeys+1) = 591751049L; + *(pkeys+2) = 878082192L; + while (*passwd != '\0') { + update_keys(pkeys,pcrc_32_tab,(int)*passwd); + passwd++; + } +} + +#define zdecode(pkeys,pcrc_32_tab,c) \ + (update_keys(pkeys,pcrc_32_tab,c ^= decrypt_byte(pkeys,pcrc_32_tab))) + +#define zencode(pkeys,pcrc_32_tab,c,t) \ + (t=decrypt_byte(pkeys,pcrc_32_tab), update_keys(pkeys,pcrc_32_tab,c), t^(c)) + +#ifdef INCLUDECRYPTINGCODE_IFCRYPTALLOWED + +#define RAND_HEAD_LEN 12 + /* "last resort" source for second part of crypt seed pattern */ +# ifndef ZCR_SEED2 +# define ZCR_SEED2 3141592654UL /* use PI as default pattern */ +# endif + +static int crypthead(passwd, buf, bufSize, pkeys, pcrc_32_tab, crcForCrypting) + const char *passwd; /* password string */ + unsigned char *buf; /* where to write header */ + int bufSize; + unsigned long* pkeys; + const unsigned long* pcrc_32_tab; + unsigned long crcForCrypting; +{ + int n; /* index in random header */ + int t; /* temporary */ + int c; /* random byte */ + unsigned char header[RAND_HEAD_LEN-2]; /* random header */ + static unsigned calls = 0; /* ensure different random header each time */ + + if (bufSize> 7) & 0xff; + header[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, c, t); + } + /* Encrypt random header (last two bytes is high word of crc) */ + init_keys(passwd, pkeys, pcrc_32_tab); + for (n = 0; n < RAND_HEAD_LEN-2; n++) + { + buf[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, header[n], t); + } + buf[n++] = zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 16) & 0xff, t); + buf[n++] = zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 24) & 0xff, t); + return n; +} + +#endif diff --git a/Modules/minizip/include/ioapi.h b/Modules/minizip/include/ioapi.h new file mode 100644 index 0000000..7d457ba --- /dev/null +++ b/Modules/minizip/include/ioapi.h @@ -0,0 +1,75 @@ +/* ioapi.h -- IO base function header for compress/uncompress .zip + files using zlib + zip or unzip API + + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant +*/ + +#ifndef _ZLIBIOAPI_H +#define _ZLIBIOAPI_H + + +#define ZLIB_FILEFUNC_SEEK_CUR (1) +#define ZLIB_FILEFUNC_SEEK_END (2) +#define ZLIB_FILEFUNC_SEEK_SET (0) + +#define ZLIB_FILEFUNC_MODE_READ (1) +#define ZLIB_FILEFUNC_MODE_WRITE (2) +#define ZLIB_FILEFUNC_MODE_READWRITEFILTER (3) + +#define ZLIB_FILEFUNC_MODE_EXISTING (4) +#define ZLIB_FILEFUNC_MODE_CREATE (8) + + +#ifndef ZCALLBACK + +#if (defined(WIN32) || defined (WINDOWS) || defined (_WINDOWS)) && defined(CALLBACK) && defined (USEWINDOWS_CALLBACK) +#define ZCALLBACK CALLBACK +#else +#define ZCALLBACK +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef voidpf (ZCALLBACK *open_file_func) OF((voidpf opaque, const char* filename, int mode)); +typedef uLong (ZCALLBACK *read_file_func) OF((voidpf opaque, voidpf stream, void* buf, uLong size)); +typedef uLong (ZCALLBACK *write_file_func) OF((voidpf opaque, voidpf stream, const void* buf, uLong size)); +typedef long (ZCALLBACK *tell_file_func) OF((voidpf opaque, voidpf stream)); +typedef long (ZCALLBACK *seek_file_func) OF((voidpf opaque, voidpf stream, uLong offset, int origin)); +typedef int (ZCALLBACK *close_file_func) OF((voidpf opaque, voidpf stream)); +typedef int (ZCALLBACK *testerror_file_func) OF((voidpf opaque, voidpf stream)); + +typedef struct zlib_filefunc_def_s +{ + open_file_func zopen_file; + read_file_func zread_file; + write_file_func zwrite_file; + tell_file_func ztell_file; + seek_file_func zseek_file; + close_file_func zclose_file; + testerror_file_func zerror_file; + voidpf opaque; +} zlib_filefunc_def; + + + +void fill_fopen_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def)); + +#define ZREAD(filefunc,filestream,buf,size) ((*((filefunc).zread_file))((filefunc).opaque,filestream,buf,size)) +#define ZWRITE(filefunc,filestream,buf,size) ((*((filefunc).zwrite_file))((filefunc).opaque,filestream,buf,size)) +#define ZTELL(filefunc,filestream) ((*((filefunc).ztell_file))((filefunc).opaque,filestream)) +#define ZSEEK(filefunc,filestream,pos,mode) ((*((filefunc).zseek_file))((filefunc).opaque,filestream,pos,mode)) +#define ZCLOSE(filefunc,filestream) ((*((filefunc).zclose_file))((filefunc).opaque,filestream)) +#define ZERROR(filefunc,filestream) ((*((filefunc).zerror_file))((filefunc).opaque,filestream)) + + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/Modules/minizip/include/iowin32.h b/Modules/minizip/include/iowin32.h new file mode 100644 index 0000000..a3a437a --- /dev/null +++ b/Modules/minizip/include/iowin32.h @@ -0,0 +1,21 @@ +/* iowin32.h -- IO base function header for compress/uncompress .zip + files using zlib + zip or unzip API + This IO API version uses the Win32 API (for Microsoft Windows) + + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant +*/ + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + +void fill_win32_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def)); + +#ifdef __cplusplus +} +#endif diff --git a/Modules/minizip/include/mztools.h b/Modules/minizip/include/mztools.h new file mode 100644 index 0000000..eee78dc --- /dev/null +++ b/Modules/minizip/include/mztools.h @@ -0,0 +1,31 @@ +/* + Additional tools for Minizip + Code: Xavier Roche '2004 + License: Same as ZLIB (www.gzip.org) +*/ + +#ifndef _zip_tools_H +#define _zip_tools_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _ZLIB_H +#include "zlib.h" +#endif + +#include "unzip.h" + +/* Repair a ZIP file (missing central directory) + file: file to recover + fileOut: output file after recovery + fileOutTmp: temporary file name used for recovery +*/ +extern int ZEXPORT unzRepair(const char* file, + const char* fileOut, + const char* fileOutTmp, + uLong* nRecovered, + uLong* bytesRecovered); + +#endif diff --git a/Modules/minizip/include/unzip.h b/Modules/minizip/include/unzip.h new file mode 100644 index 0000000..b247937 --- /dev/null +++ b/Modules/minizip/include/unzip.h @@ -0,0 +1,354 @@ +/* unzip.h -- IO for uncompress .zip files using zlib + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant + + This unzip package allow extract file from .ZIP file, compatible with PKZip 2.04g + WinZip, InfoZip tools and compatible. + + Multi volume ZipFile (span) are not supported. + Encryption compatible with pkzip 2.04g only supported + Old compressions used by old PKZip 1.x are not supported + + + I WAIT FEEDBACK at mail info@winimage.com + Visit also http://www.winimage.com/zLibDll/unzip.htm for evolution + + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + +*/ + +/* for more info about .ZIP format, see + http://www.info-zip.org/pub/infozip/doc/appnote-981119-iz.zip + http://www.info-zip.org/pub/infozip/doc/ + PkWare has also a specification at : + ftp://ftp.pkware.com/probdesc.zip +*/ + +#ifndef _unz_H +#define _unz_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _ZLIB_H +#include "zlib.h" +#endif + +#ifndef _ZLIBIOAPI_H +#include "ioapi.h" +#endif + +#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagunzFile__ { int unused; } unzFile__; +typedef unzFile__ *unzFile; +#else +typedef voidp unzFile; +#endif + + +#define UNZ_OK (0) +#define UNZ_END_OF_LIST_OF_FILE (-100) +#define UNZ_ERRNO (Z_ERRNO) +#define UNZ_EOF (0) +#define UNZ_PARAMERROR (-102) +#define UNZ_BADZIPFILE (-103) +#define UNZ_INTERNALERROR (-104) +#define UNZ_CRCERROR (-105) + +/* tm_unz contain date/time info */ +typedef struct tm_unz_s +{ + uInt tm_sec; /* seconds after the minute - [0,59] */ + uInt tm_min; /* minutes after the hour - [0,59] */ + uInt tm_hour; /* hours since midnight - [0,23] */ + uInt tm_mday; /* day of the month - [1,31] */ + uInt tm_mon; /* months since January - [0,11] */ + uInt tm_year; /* years - [1980..2044] */ +} tm_unz; + +/* unz_global_info structure contain global data about the ZIPfile + These data comes from the end of central dir */ +typedef struct unz_global_info_s +{ + uLong number_entry; /* total number of entries in + the central dir on this disk */ + uLong size_comment; /* size of the global comment of the zipfile */ +} unz_global_info; + + +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_info_s +{ + uLong version; /* version made by 2 bytes */ + uLong version_needed; /* version needed to extract 2 bytes */ + uLong flag; /* general purpose bit flag 2 bytes */ + uLong compression_method; /* compression method 2 bytes */ + uLong dosDate; /* last mod file date in Dos fmt 4 bytes */ + uLong crc; /* crc-32 4 bytes */ + uLong compressed_size; /* compressed size 4 bytes */ + uLong uncompressed_size; /* uncompressed size 4 bytes */ + uLong size_filename; /* filename length 2 bytes */ + uLong size_file_extra; /* extra field length 2 bytes */ + uLong size_file_comment; /* file comment length 2 bytes */ + + uLong disk_num_start; /* disk number start 2 bytes */ + uLong internal_fa; /* internal file attributes 2 bytes */ + uLong external_fa; /* external file attributes 4 bytes */ + + tm_unz tmu_date; +} unz_file_info; + +extern int ZEXPORT unzStringFileNameCompare OF ((const char* fileName1, + const char* fileName2, + int iCaseSensitivity)); +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) +*/ + + +extern unzFile ZEXPORT unzOpen OF((const char *path)); +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows XP computer "c:\\zlib\\zlib113.zip" or on an Unix computer + "zlib/zlib113.zip". + If the zipfile cannot be opened (file don't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ + +extern unzFile ZEXPORT unzOpen2 OF((const char *path, + zlib_filefunc_def* pzlib_filefunc_def)); +/* + Open a Zip file, like unzOpen, but provide a set of file low level API + for read/write the zip file (see ioapi.h) +*/ + +extern int ZEXPORT unzClose OF((unzFile file)); +/* + Close a ZipFile opened with unzipOpen. + If there is files inside the .Zip opened with unzOpenCurrentFile (see later), + these files MUST be closed with unzipCloseCurrentFile before call unzipClose. + return UNZ_OK if there is no problem. */ + +extern int ZEXPORT unzGetGlobalInfo OF((unzFile file, + unz_global_info *pglobal_info)); +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ + + +extern int ZEXPORT unzGetGlobalComment OF((unzFile file, + char *szComment, + uLong uSizeBuf)); +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 +*/ + + +/***************************************************************************/ +/* Unzip package allow you browse the directory of the zipfile */ + +extern int ZEXPORT unzGoToFirstFile OF((unzFile file)); +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ + +extern int ZEXPORT unzGoToNextFile OF((unzFile file)); +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ + +extern int ZEXPORT unzLocateFile OF((unzFile file, + const char *szFileName, + int iCaseSensitivity)); +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ + + +/* ****************************************** */ +/* Ryan supplied functions */ +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_pos_s +{ + uLong pos_in_zip_directory; /* offset in zip file directory */ + uLong num_of_file; /* # of file */ +} unz_file_pos; + +extern int ZEXPORT unzGetFilePos( + unzFile file, + unz_file_pos* file_pos); + +extern int ZEXPORT unzGoToFilePos( + unzFile file, + unz_file_pos* file_pos); + +/* ****************************************** */ + +extern int ZEXPORT unzGetCurrentFileInfo OF((unzFile file, + unz_file_info *pfile_info, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize)); +/* + Get Info about the current file + if pfile_info!=NULL, the *pfile_info structure will contain somes info about + the current file + if szFileName!=NULL, the filemane string will be copied in szFileName + (fileNameBufferSize is the size of the buffer) + if extraField!=NULL, the extra field information will be copied in extraField + (extraFieldBufferSize is the size of the buffer). + This is the Central-header version of the extra field + if szComment!=NULL, the comment string of the file will be copied in szComment + (commentBufferSize is the size of the buffer) +*/ + +/***************************************************************************/ +/* for reading the content of the current zipfile, you can open it, read data + from it, and close it (you can close it before reading all the file) + */ + +extern int ZEXPORT unzOpenCurrentFile OF((unzFile file)); +/* + Open for reading data the current file in the zipfile. + If there is no error, the return value is UNZ_OK. +*/ + +extern int ZEXPORT unzOpenCurrentFilePassword OF((unzFile file, + const char* password)); +/* + Open for reading data the current file in the zipfile. + password is a crypting password + If there is no error, the return value is UNZ_OK. +*/ + +extern int ZEXPORT unzOpenCurrentFile2 OF((unzFile file, + int* method, + int* level, + int raw)); +/* + Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) + if raw==1 + *method will receive method of compression, *level will receive level of + compression + note : you can set level parameter as NULL (if you did not want known level, + but you CANNOT set method parameter as NULL +*/ + +extern int ZEXPORT unzOpenCurrentFile3 OF((unzFile file, + int* method, + int* level, + int raw, + const char* password)); +/* + Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) + if raw==1 + *method will receive method of compression, *level will receive level of + compression + note : you can set level parameter as NULL (if you did not want known level, + but you CANNOT set method parameter as NULL +*/ + + +extern int ZEXPORT unzCloseCurrentFile OF((unzFile file)); +/* + Close the file in zip opened with unzOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ + +extern int ZEXPORT unzReadCurrentFile OF((unzFile file, + voidp buf, + unsigned len)); +/* + Read bytes from the current file (opened by unzOpenCurrentFile) + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ + +extern z_off_t ZEXPORT unztell OF((unzFile file)); +/* + Give the current position in uncompressed data +*/ + +extern int ZEXPORT unzeof OF((unzFile file)); +/* + return 1 if the end of file was reached, 0 elsewhere +*/ + +extern int ZEXPORT unzGetLocalExtrafield OF((unzFile file, + voidp buf, + unsigned len)); +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the local-header version of the extra field (sometimes, there is + more info in the local-header version than in the central-header) + + if buf==NULL, it return the size of the local extra field + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of bytes copied in buf, or (if <0) + the error code +*/ + +/***************************************************************************/ + +/* Get the current file offset */ +extern uLong ZEXPORT unzGetOffset (unzFile file); + +/* Set the current file offset */ +extern int ZEXPORT unzSetOffset (unzFile file, uLong pos); + + + +#ifdef __cplusplus +} +#endif + +#endif /* _unz_H */ diff --git a/Modules/minizip/include/zip.h b/Modules/minizip/include/zip.h new file mode 100644 index 0000000..acacce8 --- /dev/null +++ b/Modules/minizip/include/zip.h @@ -0,0 +1,235 @@ +/* zip.h -- IO for compress .zip files using zlib + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant + + This unzip package allow creates .ZIP file, compatible with PKZip 2.04g + WinZip, InfoZip tools and compatible. + Multi volume ZipFile (span) are not supported. + Encryption compatible with pkzip 2.04g only supported + Old compressions used by old PKZip 1.x are not supported + + For uncompress .zip file, look at unzip.h + + + I WAIT FEEDBACK at mail info@winimage.com + Visit also http://www.winimage.com/zLibDll/unzip.html for evolution + + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + +*/ + +/* for more info about .ZIP format, see + http://www.info-zip.org/pub/infozip/doc/appnote-981119-iz.zip + http://www.info-zip.org/pub/infozip/doc/ + PkWare has also a specification at : + ftp://ftp.pkware.com/probdesc.zip +*/ + +#ifndef _zip_H +#define _zip_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _ZLIB_H +#include "zlib.h" +#endif + +#ifndef _ZLIBIOAPI_H +#include "ioapi.h" +#endif + +#if defined(STRICTZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagzipFile__ { int unused; } zipFile__; +typedef zipFile__ *zipFile; +#else +typedef voidp zipFile; +#endif + +#define ZIP_OK (0) +#define ZIP_EOF (0) +#define ZIP_ERRNO (Z_ERRNO) +#define ZIP_PARAMERROR (-102) +#define ZIP_BADZIPFILE (-103) +#define ZIP_INTERNALERROR (-104) + +#ifndef DEF_MEM_LEVEL +# if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +# else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +# endif +#endif +/* default memLevel */ + +/* tm_zip contain date/time info */ +typedef struct tm_zip_s +{ + uInt tm_sec; /* seconds after the minute - [0,59] */ + uInt tm_min; /* minutes after the hour - [0,59] */ + uInt tm_hour; /* hours since midnight - [0,23] */ + uInt tm_mday; /* day of the month - [1,31] */ + uInt tm_mon; /* months since January - [0,11] */ + uInt tm_year; /* years - [1980..2044] */ +} tm_zip; + +typedef struct +{ + tm_zip tmz_date; /* date in understandable format */ + uLong dosDate; /* if dos_date == 0, tmu_date is used */ +/* uLong flag; */ /* general purpose bit flag 2 bytes */ + + uLong internal_fa; /* internal file attributes 2 bytes */ + uLong external_fa; /* external file attributes 4 bytes */ +} zip_fileinfo; + +typedef const char* zipcharpc; + + +#define APPEND_STATUS_CREATE (0) +#define APPEND_STATUS_CREATEAFTER (1) +#define APPEND_STATUS_ADDINZIP (2) + +extern zipFile ZEXPORT zipOpen OF((const char *pathname, int append)); +/* + Create a zipfile. + pathname contain on Windows XP a filename like "c:\\zlib\\zlib113.zip" or on + an Unix computer "zlib/zlib113.zip". + if the file pathname exist and append==APPEND_STATUS_CREATEAFTER, the zip + will be created at the end of the file. + (useful if the file contain a self extractor code) + if the file pathname exist and append==APPEND_STATUS_ADDINZIP, we will + add files in existing zip (be sure you don't add file that doesn't exist) + If the zipfile cannot be opened, the return value is NULL. + Else, the return value is a zipFile Handle, usable with other function + of this zip package. +*/ + +/* Note : there is no delete function into a zipfile. + If you want delete file into a zipfile, you must open a zipfile, and create another + Of couse, you can use RAW reading and writing to copy the file you did not want delte +*/ + +extern zipFile ZEXPORT zipOpen2 OF((const char *pathname, + int append, + zipcharpc* globalcomment, + zlib_filefunc_def* pzlib_filefunc_def)); + +extern int ZEXPORT zipOpenNewFileInZip OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level)); +/* + Open a file in the ZIP for writing. + filename : the filename in zip (if NULL, '-' without quote will be used + *zipfi contain supplemental information + if extrafield_local!=NULL and size_extrafield_local>0, extrafield_local + contains the extrafield data the the local header + if extrafield_global!=NULL and size_extrafield_global>0, extrafield_global + contains the extrafield data the the local header + if comment != NULL, comment contain the comment string + method contain the compression method (0 for store, Z_DEFLATED for deflate) + level contain the level of compression (can be Z_DEFAULT_COMPRESSION) +*/ + + +extern int ZEXPORT zipOpenNewFileInZip2 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw)); + +/* + Same than zipOpenNewFileInZip, except if raw=1, we write raw file + */ + +extern int ZEXPORT zipOpenNewFileInZip3 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw, + int windowBits, + int memLevel, + int strategy, + const char* password, + uLong crcForCtypting)); + +/* + Same than zipOpenNewFileInZip2, except + windowBits,memLevel,,strategy : see parameter strategy in deflateInit2 + password : crypting password (NULL for no crypting) + crcForCtypting : crc of file to compress (needed for crypting) + */ + + +extern int ZEXPORT zipWriteInFileInZip OF((zipFile file, + const void* buf, + unsigned len)); +/* + Write data in the zipfile +*/ + +extern int ZEXPORT zipCloseFileInZip OF((zipFile file)); +/* + Close the current file in the zipfile +*/ + +extern int ZEXPORT zipCloseFileInZipRaw OF((zipFile file, + uLong uncompressed_size, + uLong crc32)); +/* + Close the current file in the zipfile, for fiel opened with + parameter raw=1 in zipOpenNewFileInZip2 + uncompressed_size and crc32 are value for the uncompressed size +*/ + +extern int ZEXPORT zipClose OF((zipFile file, + const char* global_comment)); +/* + Close the zipfile +*/ + +#ifdef __cplusplus +} +#endif + +#endif /* _zip_H */ diff --git a/Modules/minizip/source/ioapi.c b/Modules/minizip/source/ioapi.c new file mode 100644 index 0000000..f1bee23 --- /dev/null +++ b/Modules/minizip/source/ioapi.c @@ -0,0 +1,177 @@ +/* ioapi.c -- IO base function header for compress/uncompress .zip + files using zlib + zip or unzip API + + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant +*/ + +#include +#include +#include + +#include "zlib.h" +#include "ioapi.h" + + + +/* I've found an old Unix (a SunOS 4.1.3_U1) without all SEEK_* defined.... */ + +#ifndef SEEK_CUR +#define SEEK_CUR 1 +#endif + +#ifndef SEEK_END +#define SEEK_END 2 +#endif + +#ifndef SEEK_SET +#define SEEK_SET 0 +#endif + +voidpf ZCALLBACK fopen_file_func OF(( + voidpf opaque, + const char* filename, + int mode)); + +uLong ZCALLBACK fread_file_func OF(( + voidpf opaque, + voidpf stream, + void* buf, + uLong size)); + +uLong ZCALLBACK fwrite_file_func OF(( + voidpf opaque, + voidpf stream, + const void* buf, + uLong size)); + +long ZCALLBACK ftell_file_func OF(( + voidpf opaque, + voidpf stream)); + +long ZCALLBACK fseek_file_func OF(( + voidpf opaque, + voidpf stream, + uLong offset, + int origin)); + +int ZCALLBACK fclose_file_func OF(( + voidpf opaque, + voidpf stream)); + +int ZCALLBACK ferror_file_func OF(( + voidpf opaque, + voidpf stream)); + + +voidpf ZCALLBACK fopen_file_func (opaque, filename, mode) + voidpf opaque; + const char* filename; + int mode; +{ + FILE* file = NULL; + const char* mode_fopen = NULL; + if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) + mode_fopen = "rb"; + else + if (mode & ZLIB_FILEFUNC_MODE_EXISTING) + mode_fopen = "r+b"; + else + if (mode & ZLIB_FILEFUNC_MODE_CREATE) + mode_fopen = "wb"; + + if ((filename!=NULL) && (mode_fopen != NULL)) + file = fopen(filename, mode_fopen); + return file; +} + + +uLong ZCALLBACK fread_file_func (opaque, stream, buf, size) + voidpf opaque; + voidpf stream; + void* buf; + uLong size; +{ + uLong ret; + ret = (uLong)fread(buf, 1, (size_t)size, (FILE *)stream); + return ret; +} + + +uLong ZCALLBACK fwrite_file_func (opaque, stream, buf, size) + voidpf opaque; + voidpf stream; + const void* buf; + uLong size; +{ + uLong ret; + ret = (uLong)fwrite(buf, 1, (size_t)size, (FILE *)stream); + return ret; +} + +long ZCALLBACK ftell_file_func (opaque, stream) + voidpf opaque; + voidpf stream; +{ + long ret; + ret = ftell((FILE *)stream); + return ret; +} + +long ZCALLBACK fseek_file_func (opaque, stream, offset, origin) + voidpf opaque; + voidpf stream; + uLong offset; + int origin; +{ + int fseek_origin=0; + long ret; + switch (origin) + { + case ZLIB_FILEFUNC_SEEK_CUR : + fseek_origin = SEEK_CUR; + break; + case ZLIB_FILEFUNC_SEEK_END : + fseek_origin = SEEK_END; + break; + case ZLIB_FILEFUNC_SEEK_SET : + fseek_origin = SEEK_SET; + break; + default: return -1; + } + ret = 0; + fseek((FILE *)stream, offset, fseek_origin); + return ret; +} + +int ZCALLBACK fclose_file_func (opaque, stream) + voidpf opaque; + voidpf stream; +{ + int ret; + ret = fclose((FILE *)stream); + return ret; +} + +int ZCALLBACK ferror_file_func (opaque, stream) + voidpf opaque; + voidpf stream; +{ + int ret; + ret = ferror((FILE *)stream); + return ret; +} + +void fill_fopen_filefunc (pzlib_filefunc_def) + zlib_filefunc_def* pzlib_filefunc_def; +{ + pzlib_filefunc_def->zopen_file = fopen_file_func; + pzlib_filefunc_def->zread_file = fread_file_func; + pzlib_filefunc_def->zwrite_file = fwrite_file_func; + pzlib_filefunc_def->ztell_file = ftell_file_func; + pzlib_filefunc_def->zseek_file = fseek_file_func; + pzlib_filefunc_def->zclose_file = fclose_file_func; + pzlib_filefunc_def->zerror_file = ferror_file_func; + pzlib_filefunc_def->opaque = NULL; +} diff --git a/Modules/minizip/source/iowin32.c b/Modules/minizip/source/iowin32.c new file mode 100644 index 0000000..a9b5f78 --- /dev/null +++ b/Modules/minizip/source/iowin32.c @@ -0,0 +1,270 @@ +/* iowin32.c -- IO base function header for compress/uncompress .zip + files using zlib + zip or unzip API + This IO API version uses the Win32 API (for Microsoft Windows) + + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant +*/ + +#include + +#include "zlib.h" +#include "ioapi.h" +#include "iowin32.h" + +#ifndef INVALID_HANDLE_VALUE +#define INVALID_HANDLE_VALUE (0xFFFFFFFF) +#endif + +#ifndef INVALID_SET_FILE_POINTER +#define INVALID_SET_FILE_POINTER ((DWORD)-1) +#endif + +voidpf ZCALLBACK win32_open_file_func OF(( + voidpf opaque, + const char* filename, + int mode)); + +uLong ZCALLBACK win32_read_file_func OF(( + voidpf opaque, + voidpf stream, + void* buf, + uLong size)); + +uLong ZCALLBACK win32_write_file_func OF(( + voidpf opaque, + voidpf stream, + const void* buf, + uLong size)); + +long ZCALLBACK win32_tell_file_func OF(( + voidpf opaque, + voidpf stream)); + +long ZCALLBACK win32_seek_file_func OF(( + voidpf opaque, + voidpf stream, + uLong offset, + int origin)); + +int ZCALLBACK win32_close_file_func OF(( + voidpf opaque, + voidpf stream)); + +int ZCALLBACK win32_error_file_func OF(( + voidpf opaque, + voidpf stream)); + +typedef struct +{ + HANDLE hf; + int error; +} WIN32FILE_IOWIN; + +voidpf ZCALLBACK win32_open_file_func (opaque, filename, mode) + voidpf opaque; + const char* filename; + int mode; +{ + const char* mode_fopen = NULL; + DWORD dwDesiredAccess,dwCreationDisposition,dwShareMode,dwFlagsAndAttributes ; + HANDLE hFile = 0; + voidpf ret=NULL; + + dwDesiredAccess = dwShareMode = dwFlagsAndAttributes = 0; + + if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) + { + dwDesiredAccess = GENERIC_READ; + dwCreationDisposition = OPEN_EXISTING; + dwShareMode = FILE_SHARE_READ; + } + else + if (mode & ZLIB_FILEFUNC_MODE_EXISTING) + { + dwDesiredAccess = GENERIC_WRITE | GENERIC_READ; + dwCreationDisposition = OPEN_EXISTING; + } + else + if (mode & ZLIB_FILEFUNC_MODE_CREATE) + { + dwDesiredAccess = GENERIC_WRITE | GENERIC_READ; + dwCreationDisposition = CREATE_ALWAYS; + } + + if ((filename!=NULL) && (dwDesiredAccess != 0)) + hFile = CreateFile((LPCTSTR)filename, dwDesiredAccess, dwShareMode, NULL, + dwCreationDisposition, dwFlagsAndAttributes, NULL); + + if (hFile == INVALID_HANDLE_VALUE) + hFile = NULL; + + if (hFile != NULL) + { + WIN32FILE_IOWIN w32fiow; + w32fiow.hf = hFile; + w32fiow.error = 0; + ret = malloc(sizeof(WIN32FILE_IOWIN)); + if (ret==NULL) + CloseHandle(hFile); + else *((WIN32FILE_IOWIN*)ret) = w32fiow; + } + return ret; +} + + +uLong ZCALLBACK win32_read_file_func (opaque, stream, buf, size) + voidpf opaque; + voidpf stream; + void* buf; + uLong size; +{ + uLong ret=0; + HANDLE hFile = NULL; + if (stream!=NULL) + hFile = ((WIN32FILE_IOWIN*)stream) -> hf; + if (hFile != NULL) + if (!ReadFile(hFile, buf, size, &ret, NULL)) + { + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_HANDLE_EOF) + dwErr = 0; + ((WIN32FILE_IOWIN*)stream) -> error=(int)dwErr; + } + + return ret; +} + + +uLong ZCALLBACK win32_write_file_func (opaque, stream, buf, size) + voidpf opaque; + voidpf stream; + const void* buf; + uLong size; +{ + uLong ret=0; + HANDLE hFile = NULL; + if (stream!=NULL) + hFile = ((WIN32FILE_IOWIN*)stream) -> hf; + + if (hFile !=NULL) + if (!WriteFile(hFile, buf, size, &ret, NULL)) + { + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_HANDLE_EOF) + dwErr = 0; + ((WIN32FILE_IOWIN*)stream) -> error=(int)dwErr; + } + + return ret; +} + +long ZCALLBACK win32_tell_file_func (opaque, stream) + voidpf opaque; + voidpf stream; +{ + long ret=-1; + HANDLE hFile = NULL; + if (stream!=NULL) + hFile = ((WIN32FILE_IOWIN*)stream) -> hf; + if (hFile != NULL) + { + DWORD dwSet = SetFilePointer(hFile, 0, NULL, FILE_CURRENT); + if (dwSet == INVALID_SET_FILE_POINTER) + { + DWORD dwErr = GetLastError(); + ((WIN32FILE_IOWIN*)stream) -> error=(int)dwErr; + ret = -1; + } + else + ret=(long)dwSet; + } + return ret; +} + +long ZCALLBACK win32_seek_file_func (opaque, stream, offset, origin) + voidpf opaque; + voidpf stream; + uLong offset; + int origin; +{ + DWORD dwMoveMethod=0xFFFFFFFF; + HANDLE hFile = NULL; + + long ret=-1; + if (stream!=NULL) + hFile = ((WIN32FILE_IOWIN*)stream) -> hf; + switch (origin) + { + case ZLIB_FILEFUNC_SEEK_CUR : + dwMoveMethod = FILE_CURRENT; + break; + case ZLIB_FILEFUNC_SEEK_END : + dwMoveMethod = FILE_END; + break; + case ZLIB_FILEFUNC_SEEK_SET : + dwMoveMethod = FILE_BEGIN; + break; + default: return -1; + } + + if (hFile != NULL) + { + DWORD dwSet = SetFilePointer(hFile, offset, NULL, dwMoveMethod); + if (dwSet == INVALID_SET_FILE_POINTER) + { + DWORD dwErr = GetLastError(); + ((WIN32FILE_IOWIN*)stream) -> error=(int)dwErr; + ret = -1; + } + else + ret=0; + } + return ret; +} + +int ZCALLBACK win32_close_file_func (opaque, stream) + voidpf opaque; + voidpf stream; +{ + int ret=-1; + + if (stream!=NULL) + { + HANDLE hFile; + hFile = ((WIN32FILE_IOWIN*)stream) -> hf; + if (hFile != NULL) + { + CloseHandle(hFile); + ret=0; + } + free(stream); + } + return ret; +} + +int ZCALLBACK win32_error_file_func (opaque, stream) + voidpf opaque; + voidpf stream; +{ + int ret=-1; + if (stream!=NULL) + { + ret = ((WIN32FILE_IOWIN*)stream) -> error; + } + return ret; +} + +void fill_win32_filefunc (pzlib_filefunc_def) + zlib_filefunc_def* pzlib_filefunc_def; +{ + pzlib_filefunc_def->zopen_file = win32_open_file_func; + pzlib_filefunc_def->zread_file = win32_read_file_func; + pzlib_filefunc_def->zwrite_file = win32_write_file_func; + pzlib_filefunc_def->ztell_file = win32_tell_file_func; + pzlib_filefunc_def->zseek_file = win32_seek_file_func; + pzlib_filefunc_def->zclose_file = win32_close_file_func; + pzlib_filefunc_def->zerror_file = win32_error_file_func; + pzlib_filefunc_def->opaque=NULL; +} diff --git a/Modules/minizip/source/mztools.c b/Modules/minizip/source/mztools.c new file mode 100644 index 0000000..8a50ee4 --- /dev/null +++ b/Modules/minizip/source/mztools.c @@ -0,0 +1,281 @@ +/* + Additional tools for Minizip + Code: Xavier Roche '2004 + License: Same as ZLIB (www.gzip.org) +*/ + +/* Code */ +#include +#include +#include +#include "zlib.h" +#include "unzip.h" + +#define READ_8(adr) ((unsigned char)*(adr)) +#define READ_16(adr) ( READ_8(adr) | (READ_8(adr+1) << 8) ) +#define READ_32(adr) ( READ_16(adr) | (READ_16((adr)+2) << 16) ) + +#define WRITE_8(buff, n) do { \ + *((unsigned char*)(buff)) = (unsigned char) ((n) & 0xff); \ +} while(0) +#define WRITE_16(buff, n) do { \ + WRITE_8((unsigned char*)(buff), n); \ + WRITE_8(((unsigned char*)(buff)) + 1, (n) >> 8); \ +} while(0) +#define WRITE_32(buff, n) do { \ + WRITE_16((unsigned char*)(buff), (n) & 0xffff); \ + WRITE_16((unsigned char*)(buff) + 2, (n) >> 16); \ +} while(0) + +extern int ZEXPORT unzRepair(file, fileOut, fileOutTmp, nRecovered, bytesRecovered) +const char* file; +const char* fileOut; +const char* fileOutTmp; +uLong* nRecovered; +uLong* bytesRecovered; +{ + int err = Z_OK; + FILE* fpZip = fopen(file, "rb"); + FILE* fpOut = fopen(fileOut, "wb"); + FILE* fpOutCD = fopen(fileOutTmp, "wb"); + if (fpZip != NULL && fpOut != NULL) { + int entries = 0; + uLong totalBytes = 0; + char header[30]; + char filename[256]; + char extra[1024]; + int offset = 0; + int offsetCD = 0; + while ( fread(header, 1, 30, fpZip) == 30 ) { + int currentOffset = offset; + + /* File entry */ + if (READ_32(header) == 0x04034b50) { + unsigned int version = READ_16(header + 4); + unsigned int gpflag = READ_16(header + 6); + unsigned int method = READ_16(header + 8); + unsigned int filetime = READ_16(header + 10); + unsigned int filedate = READ_16(header + 12); + unsigned int crc = READ_32(header + 14); /* crc */ + unsigned int cpsize = READ_32(header + 18); /* compressed size */ + unsigned int uncpsize = READ_32(header + 22); /* uncompressed sz */ + unsigned int fnsize = READ_16(header + 26); /* file name length */ + unsigned int extsize = READ_16(header + 28); /* extra field length */ + filename[0] = extra[0] = '\0'; + + /* Header */ + if (fwrite(header, 1, 30, fpOut) == 30) { + offset += 30; + } else { + err = Z_ERRNO; + break; + } + + /* Filename */ + if (fnsize > 0) { + if (fread(filename, 1, fnsize, fpZip) == fnsize) { + if (fwrite(filename, 1, fnsize, fpOut) == fnsize) { + offset += fnsize; + } else { + err = Z_ERRNO; + break; + } + } else { + err = Z_ERRNO; + break; + } + } else { + err = Z_STREAM_ERROR; + break; + } + + /* Extra field */ + if (extsize > 0) { + if (fread(extra, 1, extsize, fpZip) == extsize) { + if (fwrite(extra, 1, extsize, fpOut) == extsize) { + offset += extsize; + } else { + err = Z_ERRNO; + break; + } + } else { + err = Z_ERRNO; + break; + } + } + + /* Data */ + { + int dataSize = cpsize; + if (dataSize == 0) { + dataSize = uncpsize; + } + if (dataSize > 0) { + char* data = malloc(dataSize); + if (data != NULL) { + if ((int)fread(data, 1, dataSize, fpZip) == dataSize) { + if ((int)fwrite(data, 1, dataSize, fpOut) == dataSize) { + offset += dataSize; + totalBytes += dataSize; + } else { + err = Z_ERRNO; + } + } else { + err = Z_ERRNO; + } + free(data); + if (err != Z_OK) { + break; + } + } else { + err = Z_MEM_ERROR; + break; + } + } + } + + /* Central directory entry */ + { + char header[46]; + char* comment = ""; + int comsize = (int) strlen(comment); + WRITE_32(header, 0x02014b50); + WRITE_16(header + 4, version); + WRITE_16(header + 6, version); + WRITE_16(header + 8, gpflag); + WRITE_16(header + 10, method); + WRITE_16(header + 12, filetime); + WRITE_16(header + 14, filedate); + WRITE_32(header + 16, crc); + WRITE_32(header + 20, cpsize); + WRITE_32(header + 24, uncpsize); + WRITE_16(header + 28, fnsize); + WRITE_16(header + 30, extsize); + WRITE_16(header + 32, comsize); + WRITE_16(header + 34, 0); /* disk # */ + WRITE_16(header + 36, 0); /* int attrb */ + WRITE_32(header + 38, 0); /* ext attrb */ + WRITE_32(header + 42, currentOffset); + /* Header */ + if (fwrite(header, 1, 46, fpOutCD) == 46) { + offsetCD += 46; + + /* Filename */ + if (fnsize > 0) { + if (fwrite(filename, 1, fnsize, fpOutCD) == fnsize) { + offsetCD += fnsize; + } else { + err = Z_ERRNO; + break; + } + } else { + err = Z_STREAM_ERROR; + break; + } + + /* Extra field */ + if (extsize > 0) { + if (fwrite(extra, 1, extsize, fpOutCD) == extsize) { + offsetCD += extsize; + } else { + err = Z_ERRNO; + break; + } + } + + /* Comment field */ + if (comsize > 0) { + if ((int)fwrite(comment, 1, comsize, fpOutCD) == comsize) { + offsetCD += comsize; + } else { + err = Z_ERRNO; + break; + } + } + + + } else { + err = Z_ERRNO; + break; + } + } + + /* Success */ + entries++; + + } else { + break; + } + } + + /* Final central directory */ + { + int entriesZip = entries; + char header[22]; + char* comment = ""; // "ZIP File recovered by zlib/minizip/mztools"; + int comsize = (int) strlen(comment); + if (entriesZip > 0xffff) { + entriesZip = 0xffff; + } + WRITE_32(header, 0x06054b50); + WRITE_16(header + 4, 0); /* disk # */ + WRITE_16(header + 6, 0); /* disk # */ + WRITE_16(header + 8, entriesZip); /* hack */ + WRITE_16(header + 10, entriesZip); /* hack */ + WRITE_32(header + 12, offsetCD); /* size of CD */ + WRITE_32(header + 16, offset); /* offset to CD */ + WRITE_16(header + 20, comsize); /* comment */ + + /* Header */ + if (fwrite(header, 1, 22, fpOutCD) == 22) { + + /* Comment field */ + if (comsize > 0) { + if ((int)fwrite(comment, 1, comsize, fpOutCD) != comsize) { + err = Z_ERRNO; + } + } + + } else { + err = Z_ERRNO; + } + } + + /* Final merge (file + central directory) */ + fclose(fpOutCD); + if (err == Z_OK) { + fpOutCD = fopen(fileOutTmp, "rb"); + if (fpOutCD != NULL) { + int nRead; + char buffer[8192]; + while ( (nRead = (int)fread(buffer, 1, sizeof(buffer), fpOutCD)) > 0) { + if ((int)fwrite(buffer, 1, nRead, fpOut) != nRead) { + err = Z_ERRNO; + break; + } + } + fclose(fpOutCD); + } + } + + /* Close */ + fclose(fpZip); + fclose(fpOut); + + /* Wipe temporary file */ + (void)remove(fileOutTmp); + + /* Number of recovered entries */ + if (err == Z_OK) { + if (nRecovered != NULL) { + *nRecovered = entries; + } + if (bytesRecovered != NULL) { + *bytesRecovered = totalBytes; + } + } + } else { + err = Z_STREAM_ERROR; + } + return err; +} diff --git a/Modules/minizip/source/unzip.c b/Modules/minizip/source/unzip.c new file mode 100644 index 0000000..9ad4766 --- /dev/null +++ b/Modules/minizip/source/unzip.c @@ -0,0 +1,1598 @@ +/* unzip.c -- IO for uncompress .zip files using zlib + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant + + Read unzip.h for more info +*/ + +/* Decryption code comes from crypt.c by Info-ZIP but has been greatly reduced in terms of +compatibility with older software. The following is from the original crypt.c. Code +woven in by Terry Thorsen 1/2003. +*/ +/* + Copyright (c) 1990-2000 Info-ZIP. All rights reserved. + + See the accompanying file LICENSE, version 2000-Apr-09 or later + (the contents of which are also included in zip.h) for terms of use. + If, for some reason, all these files are missing, the Info-ZIP license + also may be found at: ftp://ftp.info-zip.org/pub/infozip/license.html +*/ +/* + crypt.c (full version) by Info-ZIP. Last revised: [see crypt.h] + + The encryption/decryption parts of this source code (as opposed to the + non-echoing password parts) were originally written in Europe. The + whole source package can be freely distributed, including from the USA. + (Prior to January 2000, re-export from the US was a violation of US law.) + */ + +/* + This encryption code is a direct transcription of the algorithm from + Roger Schlafly, described by Phil Katz in the file appnote.txt. This + file (appnote.txt) is distributed with the PKZIP program (even in the + version without encryption capabilities). + */ + + +#include +#include +#include +#include "zlib.h" +#include "unzip.h" + +#ifdef STDC +# include +# include +# include +#endif +#ifdef NO_ERRNO_H + extern int errno; +#else +# include +#endif + + +#ifndef local +# define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + + +#ifndef CASESENSITIVITYDEFAULT_NO +# if !defined(unix) && !defined(CASESENSITIVITYDEFAULT_YES) +# define CASESENSITIVITYDEFAULT_NO +# endif +#endif + + +#ifndef UNZ_BUFSIZE +#define UNZ_BUFSIZE (16384) +#endif + +#ifndef UNZ_MAXFILENAMEINZIP +#define UNZ_MAXFILENAMEINZIP (256) +#endif + +#ifndef ALLOC +# define ALLOC(size) (malloc(size)) +#endif +#ifndef TRYFREE +# define TRYFREE(p) {if (p) free(p);} +#endif + +#define SIZECENTRALDIRITEM (0x2e) +#define SIZEZIPLOCALHEADER (0x1e) + + + + +const char unz_copyright[] = + " unzip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; + +/* unz_file_info_interntal contain internal info about a file in zipfile*/ +typedef struct unz_file_info_internal_s +{ + uLong offset_curfile;/* relative offset of local header 4 bytes */ +} unz_file_info_internal; + + +/* file_in_zip_read_info_s contain internal information about a file in zipfile, + when reading and decompress it */ +typedef struct +{ + char *read_buffer; /* internal buffer for compressed data */ + z_stream stream; /* zLib stream structure for inflate */ + + uLong pos_in_zipfile; /* position in byte on the zipfile, for fseek*/ + uLong stream_initialised; /* flag set if stream structure is initialised*/ + + uLong offset_local_extrafield;/* offset of the local extra field */ + uInt size_local_extrafield;/* size of the local extra field */ + uLong pos_local_extrafield; /* position in the local extra field in read*/ + + uLong crc32; /* crc32 of all data uncompressed */ + uLong crc32_wait; /* crc32 we must obtain after decompress all */ + uLong rest_read_compressed; /* number of byte to be decompressed */ + uLong rest_read_uncompressed;/*number of byte to be obtained after decomp*/ + zlib_filefunc_def z_filefunc; + voidpf filestream; /* io structore of the zipfile */ + uLong compression_method; /* compression method (0==store) */ + uLong byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ + int raw; +} file_in_zip_read_info_s; + + +/* unz_s contain internal information about the zipfile +*/ +typedef struct +{ + zlib_filefunc_def z_filefunc; + voidpf filestream; /* io structore of the zipfile */ + unz_global_info gi; /* public global information */ + uLong byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ + uLong num_file; /* number of the current file in the zipfile*/ + uLong pos_in_central_dir; /* pos of the current file in the central dir*/ + uLong current_file_ok; /* flag about the usability of the current file*/ + uLong central_pos; /* position of the beginning of the central dir*/ + + uLong size_central_dir; /* size of the central directory */ + uLong offset_central_dir; /* offset of start of central directory with + respect to the starting disk number */ + + unz_file_info cur_file_info; /* public info about the current file in zip*/ + unz_file_info_internal cur_file_info_internal; /* private info about it*/ + file_in_zip_read_info_s* pfile_in_zip_read; /* structure about the current + file if we are decompressing it */ + int encrypted; +# ifndef NOUNCRYPT + unsigned long keys[3]; /* keys defining the pseudo-random sequence */ + const unsigned long* pcrc_32_tab; +# endif +} unz_s; + + +#ifndef NOUNCRYPT +#include "crypt.h" +#endif + +/* =========================================================================== + Read a byte from a gz_stream; update next_in and avail_in. Return EOF + for end of file. + IN assertion: the stream s has been sucessfully opened for reading. +*/ + + +local int unzlocal_getByte OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream, + int *pi)); + +local int unzlocal_getByte(pzlib_filefunc_def,filestream,pi) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; + int *pi; +{ + unsigned char c; + int err = (int)ZREAD(*pzlib_filefunc_def,filestream,&c,1); + if (err==1) + { + *pi = (int)c; + return UNZ_OK; + } + else + { + if (ZERROR(*pzlib_filefunc_def,filestream)) + return UNZ_ERRNO; + else + return UNZ_EOF; + } +} + + +/* =========================================================================== + Reads a long in LSB order from the given gz_stream. Sets +*/ +local int unzlocal_getShort OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX)); + +local int unzlocal_getShort (pzlib_filefunc_def,filestream,pX) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; + uLong *pX; +{ + uLong x ; + int i; + int err; + + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==UNZ_OK) + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<8; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +} + +local int unzlocal_getLong OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX)); + +local int unzlocal_getLong (pzlib_filefunc_def,filestream,pX) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; + uLong *pX; +{ + uLong x ; + int i; + int err; + + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==UNZ_OK) + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<8; + + if (err==UNZ_OK) + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<16; + + if (err==UNZ_OK) + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<24; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +} + + +/* My own strcmpi / strcasecmp */ +local int strcmpcasenosensitive_internal (fileName1,fileName2) + const char* fileName1; + const char* fileName2; +{ + for (;;) + { + char c1=*(fileName1++); + char c2=*(fileName2++); + if ((c1>='a') && (c1<='z')) + c1 -= 0x20; + if ((c2>='a') && (c2<='z')) + c2 -= 0x20; + if (c1=='\0') + return ((c2=='\0') ? 0 : -1); + if (c2=='\0') + return 1; + if (c1c2) + return 1; + } +} + + +#ifdef CASESENSITIVITYDEFAULT_NO +#define CASESENSITIVITYDEFAULTVALUE 2 +#else +#define CASESENSITIVITYDEFAULTVALUE 1 +#endif + +#ifndef STRCMPCASENOSENTIVEFUNCTION +#define STRCMPCASENOSENTIVEFUNCTION strcmpcasenosensitive_internal +#endif + +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) + +*/ +extern int ZEXPORT unzStringFileNameCompare (fileName1,fileName2,iCaseSensitivity) + const char* fileName1; + const char* fileName2; + int iCaseSensitivity; +{ + if (iCaseSensitivity==0) + iCaseSensitivity=CASESENSITIVITYDEFAULTVALUE; + + if (iCaseSensitivity==1) + return strcmp(fileName1,fileName2); + + return STRCMPCASENOSENTIVEFUNCTION(fileName1,fileName2); +} + +#ifndef BUFREADCOMMENT +#define BUFREADCOMMENT (0x400) +#endif + +/* + Locate the Central directory of a zipfile (at the end, just before + the global comment) +*/ +local uLong unzlocal_SearchCentralDir OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream)); + +local uLong unzlocal_SearchCentralDir(pzlib_filefunc_def,filestream) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; +{ + unsigned char* buf; + uLong uSizeFile; + uLong uBackRead; + uLong uMaxBack=0xffff; /* maximum size of global comment */ + uLong uPosFound=0; + + if (ZSEEK(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) + return 0; + + + uSizeFile = ZTELL(*pzlib_filefunc_def,filestream); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + if (buf==NULL) + return 0; + + uBackRead = 4; + while (uBackReaduMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uSizeFile-uReadPos); + if (ZSEEK(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) + break; + + if (ZREAD(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && + ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) + { + uPosFound = uReadPos+i; + break; + } + + if (uPosFound!=0) + break; + } + TRYFREE(buf); + return uPosFound; +} + +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows NT computer "c:\\test\\zlib114.zip" or on an Unix computer + "zlib/zlib114.zip". + If the zipfile cannot be opened (file doesn't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ +extern unzFile ZEXPORT unzOpen2 (path, pzlib_filefunc_def) + const char *path; + zlib_filefunc_def* pzlib_filefunc_def; +{ + unz_s us; + unz_s *s; + uLong central_pos,uL; + + uLong number_disk; /* number of the current dist, used for + spaning ZIP, unsupported, always 0*/ + uLong number_disk_with_CD; /* number the the disk with central dir, used + for spaning ZIP, unsupported, always 0*/ + uLong number_entry_CD; /* total number of entries in + the central dir + (same than number_entry on nospan) */ + + int err=UNZ_OK; + + if (unz_copyright[0]!=' ') + return NULL; + + if (pzlib_filefunc_def==NULL) + fill_fopen_filefunc(&us.z_filefunc); + else + us.z_filefunc = *pzlib_filefunc_def; + + us.filestream= (*(us.z_filefunc.zopen_file))(us.z_filefunc.opaque, + path, + ZLIB_FILEFUNC_MODE_READ | + ZLIB_FILEFUNC_MODE_EXISTING); + if (us.filestream==NULL) + return NULL; + + central_pos = unzlocal_SearchCentralDir(&us.z_filefunc,us.filestream); + if (central_pos==0) + err=UNZ_ERRNO; + + if (ZSEEK(us.z_filefunc, us.filestream, + central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) + err=UNZ_ERRNO; + + /* the signature, already checked */ + if (unzlocal_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of this disk */ + if (unzlocal_getShort(&us.z_filefunc, us.filestream,&number_disk)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of the disk with the start of the central directory */ + if (unzlocal_getShort(&us.z_filefunc, us.filestream,&number_disk_with_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir on this disk */ + if (unzlocal_getShort(&us.z_filefunc, us.filestream,&us.gi.number_entry)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir */ + if (unzlocal_getShort(&us.z_filefunc, us.filestream,&number_entry_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((number_entry_CD!=us.gi.number_entry) || + (number_disk_with_CD!=0) || + (number_disk!=0)) + err=UNZ_BADZIPFILE; + + /* size of the central directory */ + if (unzlocal_getLong(&us.z_filefunc, us.filestream,&us.size_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* offset of start of central directory with respect to the + starting disk number */ + if (unzlocal_getLong(&us.z_filefunc, us.filestream,&us.offset_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* zipfile comment length */ + if (unzlocal_getShort(&us.z_filefunc, us.filestream,&us.gi.size_comment)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((central_pospfile_in_zip_read!=NULL) + unzCloseCurrentFile(file); + + ZCLOSE(s->z_filefunc, s->filestream); + TRYFREE(s); + return UNZ_OK; +} + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ +extern int ZEXPORT unzGetGlobalInfo (file,pglobal_info) + unzFile file; + unz_global_info *pglobal_info; +{ + unz_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + *pglobal_info=s->gi; + return UNZ_OK; +} + + +/* + Translate date/time from Dos format to tm_unz (readable more easilty) +*/ +local void unzlocal_DosDateToTmuDate (ulDosDate, ptm) + uLong ulDosDate; + tm_unz* ptm; +{ + uLong uDate; + uDate = (uLong)(ulDosDate>>16); + ptm->tm_mday = (uInt)(uDate&0x1f) ; + ptm->tm_mon = (uInt)((((uDate)&0x1E0)/0x20)-1) ; + ptm->tm_year = (uInt)(((uDate&0x0FE00)/0x0200)+1980) ; + + ptm->tm_hour = (uInt) ((ulDosDate &0xF800)/0x800); + ptm->tm_min = (uInt) ((ulDosDate&0x7E0)/0x20) ; + ptm->tm_sec = (uInt) (2*(ulDosDate&0x1f)) ; +} + +/* + Get Info about the current file in the zipfile, with internal only info +*/ +local int unzlocal_GetCurrentFileInfoInternal OF((unzFile file, + unz_file_info *pfile_info, + unz_file_info_internal + *pfile_info_internal, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize)); + +local int unzlocal_GetCurrentFileInfoInternal (file, + pfile_info, + pfile_info_internal, + szFileName, fileNameBufferSize, + extraField, extraFieldBufferSize, + szComment, commentBufferSize) + unzFile file; + unz_file_info *pfile_info; + unz_file_info_internal *pfile_info_internal; + char *szFileName; + uLong fileNameBufferSize; + void *extraField; + uLong extraFieldBufferSize; + char *szComment; + uLong commentBufferSize; +{ + unz_s* s; + unz_file_info file_info; + unz_file_info_internal file_info_internal; + int err=UNZ_OK; + uLong uMagic; + long lSeek=0; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (ZSEEK(s->z_filefunc, s->filestream, + s->pos_in_central_dir+s->byte_before_the_zipfile, + ZLIB_FILEFUNC_SEEK_SET)!=0) + err=UNZ_ERRNO; + + + /* we check the magic */ + if (err==UNZ_OK) + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x02014b50) + err=UNZ_BADZIPFILE; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.version) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.version_needed) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.flag) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.compression_method) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.dosDate) != UNZ_OK) + err=UNZ_ERRNO; + + unzlocal_DosDateToTmuDate(file_info.dosDate,&file_info.tmu_date); + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.crc) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.compressed_size) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.uncompressed_size) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.size_filename) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_extra) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_comment) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.disk_num_start) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.internal_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.external_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info_internal.offset_curfile) != UNZ_OK) + err=UNZ_ERRNO; + + lSeek+=file_info.size_filename; + if ((err==UNZ_OK) && (szFileName!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_filename0) && (fileNameBufferSize>0)) + if (ZREAD(s->z_filefunc, s->filestream,szFileName,uSizeRead)!=uSizeRead) + err=UNZ_ERRNO; + lSeek -= uSizeRead; + } + + + if ((err==UNZ_OK) && (extraField!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_extraz_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + if ((file_info.size_file_extra>0) && (extraFieldBufferSize>0)) + if (ZREAD(s->z_filefunc, s->filestream,extraField,uSizeRead)!=uSizeRead) + err=UNZ_ERRNO; + lSeek += file_info.size_file_extra - uSizeRead; + } + else + lSeek+=file_info.size_file_extra; + + + if ((err==UNZ_OK) && (szComment!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_commentz_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + if ((file_info.size_file_comment>0) && (commentBufferSize>0)) + if (ZREAD(s->z_filefunc, s->filestream,szComment,uSizeRead)!=uSizeRead) + err=UNZ_ERRNO; + lSeek+=file_info.size_file_comment - uSizeRead; + } + else + lSeek+=file_info.size_file_comment; + + if ((err==UNZ_OK) && (pfile_info!=NULL)) + *pfile_info=file_info; + + if ((err==UNZ_OK) && (pfile_info_internal!=NULL)) + *pfile_info_internal=file_info_internal; + + return err; +} + + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. +*/ +extern int ZEXPORT unzGetCurrentFileInfo (file, + pfile_info, + szFileName, fileNameBufferSize, + extraField, extraFieldBufferSize, + szComment, commentBufferSize) + unzFile file; + unz_file_info *pfile_info; + char *szFileName; + uLong fileNameBufferSize; + void *extraField; + uLong extraFieldBufferSize; + char *szComment; + uLong commentBufferSize; +{ + return unzlocal_GetCurrentFileInfoInternal(file,pfile_info,NULL, + szFileName,fileNameBufferSize, + extraField,extraFieldBufferSize, + szComment,commentBufferSize); +} + +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ +extern int ZEXPORT unzGoToFirstFile (file) + unzFile file; +{ + int err=UNZ_OK; + unz_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + s->pos_in_central_dir=s->offset_central_dir; + s->num_file=0; + err=unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ +extern int ZEXPORT unzGoToNextFile (file) + unzFile file; +{ + unz_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + if (s->gi.number_entry != 0xffff) /* 2^16 files overflow hack */ + if (s->num_file+1==s->gi.number_entry) + return UNZ_END_OF_LIST_OF_FILE; + + s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename + + s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment ; + s->num_file++; + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + + +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzipStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ +extern int ZEXPORT unzLocateFile (file, szFileName, iCaseSensitivity) + unzFile file; + const char *szFileName; + int iCaseSensitivity; +{ + unz_s* s; + int err; + + /* We remember the 'current' position in the file so that we can jump + * back there if we fail. + */ + unz_file_info cur_file_infoSaved; + unz_file_info_internal cur_file_info_internalSaved; + uLong num_fileSaved; + uLong pos_in_central_dirSaved; + + + if (file==NULL) + return UNZ_PARAMERROR; + + if (strlen(szFileName)>=UNZ_MAXFILENAMEINZIP) + return UNZ_PARAMERROR; + + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + /* Save the current state */ + num_fileSaved = s->num_file; + pos_in_central_dirSaved = s->pos_in_central_dir; + cur_file_infoSaved = s->cur_file_info; + cur_file_info_internalSaved = s->cur_file_info_internal; + + err = unzGoToFirstFile(file); + + while (err == UNZ_OK) + { + char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1]; + err = unzGetCurrentFileInfo(file,NULL, + szCurrentFileName,sizeof(szCurrentFileName)-1, + NULL,0,NULL,0); + if (err == UNZ_OK) + { + if (unzStringFileNameCompare(szCurrentFileName, + szFileName,iCaseSensitivity)==0) + return UNZ_OK; + err = unzGoToNextFile(file); + } + } + + /* We failed, so restore the state of the 'current file' to where we + * were. + */ + s->num_file = num_fileSaved ; + s->pos_in_central_dir = pos_in_central_dirSaved ; + s->cur_file_info = cur_file_infoSaved; + s->cur_file_info_internal = cur_file_info_internalSaved; + return err; +} + + +/* +/////////////////////////////////////////// +// Contributed by Ryan Haksi (mailto://cryogen@infoserve.net) +// I need random access +// +// Further optimization could be realized by adding an ability +// to cache the directory in memory. The goal being a single +// comprehensive file read to put the file I need in a memory. +*/ + +/* +typedef struct unz_file_pos_s +{ + uLong pos_in_zip_directory; // offset in file + uLong num_of_file; // # of file +} unz_file_pos; +*/ + +extern int ZEXPORT unzGetFilePos(file, file_pos) + unzFile file; + unz_file_pos* file_pos; +{ + unz_s* s; + + if (file==NULL || file_pos==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + file_pos->pos_in_zip_directory = s->pos_in_central_dir; + file_pos->num_of_file = s->num_file; + + return UNZ_OK; +} + +extern int ZEXPORT unzGoToFilePos(file, file_pos) + unzFile file; + unz_file_pos* file_pos; +{ + unz_s* s; + int err; + + if (file==NULL || file_pos==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + /* jump to the right spot */ + s->pos_in_central_dir = file_pos->pos_in_zip_directory; + s->num_file = file_pos->num_of_file; + + /* set the current file */ + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + /* return results */ + s->current_file_ok = (err == UNZ_OK); + return err; +} + +/* +// Unzip Helper Functions - should be here? +/////////////////////////////////////////// +*/ + +/* + Read the local header of the current zipfile + Check the coherency of the local header and info in the end of central + directory about this file + store in *piSizeVar the size of extra info in local header + (filename and size of extra field data) +*/ +local int unzlocal_CheckCurrentFileCoherencyHeader (s,piSizeVar, + poffset_local_extrafield, + psize_local_extrafield) + unz_s* s; + uInt* piSizeVar; + uLong *poffset_local_extrafield; + uInt *psize_local_extrafield; +{ + uLong uMagic,uData,uFlags; + uLong size_filename; + uLong size_extra_field; + int err=UNZ_OK; + + *piSizeVar = 0; + *poffset_local_extrafield = 0; + *psize_local_extrafield = 0; + + if (ZSEEK(s->z_filefunc, s->filestream,s->cur_file_info_internal.offset_curfile + + s->byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + + + if (err==UNZ_OK) + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x04034b50) + err=UNZ_BADZIPFILE; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) + err=UNZ_ERRNO; +/* + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.wVersion)) + err=UNZ_BADZIPFILE; +*/ + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&uFlags) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compression_method)) + err=UNZ_BADZIPFILE; + + if ((err==UNZ_OK) && (s->cur_file_info.compression_method!=0) && + (s->cur_file_info.compression_method!=Z_DEFLATED)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* date/time */ + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* crc */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.crc) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* size compr */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compressed_size) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* size uncompr */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.uncompressed_size) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&size_filename) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (size_filename!=s->cur_file_info.size_filename)) + err=UNZ_BADZIPFILE; + + *piSizeVar += (uInt)size_filename; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&size_extra_field) != UNZ_OK) + err=UNZ_ERRNO; + *poffset_local_extrafield= s->cur_file_info_internal.offset_curfile + + SIZEZIPLOCALHEADER + size_filename; + *psize_local_extrafield = (uInt)size_extra_field; + + *piSizeVar += (uInt)size_extra_field; + + return err; +} + +/* + Open for reading data the current file in the zipfile. + If there is no error and the file is opened, the return value is UNZ_OK. +*/ +extern int ZEXPORT unzOpenCurrentFile3 (file, method, level, raw, password) + unzFile file; + int* method; + int* level; + int raw; + const char* password; +{ + int err=UNZ_OK; + uInt iSizeVar; + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + uLong offset_local_extrafield; /* offset of the local extra field */ + uInt size_local_extrafield; /* size of the local extra field */ +# ifndef NOUNCRYPT + char source[12]; +# else + if (password != NULL) + return UNZ_PARAMERROR; +# endif + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_PARAMERROR; + + if (s->pfile_in_zip_read != NULL) + unzCloseCurrentFile(file); + + if (unzlocal_CheckCurrentFileCoherencyHeader(s,&iSizeVar, + &offset_local_extrafield,&size_local_extrafield)!=UNZ_OK) + return UNZ_BADZIPFILE; + + pfile_in_zip_read_info = (file_in_zip_read_info_s*) + ALLOC(sizeof(file_in_zip_read_info_s)); + if (pfile_in_zip_read_info==NULL) + return UNZ_INTERNALERROR; + + pfile_in_zip_read_info->read_buffer=(char*)ALLOC(UNZ_BUFSIZE); + pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield; + pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield; + pfile_in_zip_read_info->pos_local_extrafield=0; + pfile_in_zip_read_info->raw=raw; + + if (pfile_in_zip_read_info->read_buffer==NULL) + { + TRYFREE(pfile_in_zip_read_info); + return UNZ_INTERNALERROR; + } + + pfile_in_zip_read_info->stream_initialised=0; + + if (method!=NULL) + *method = (int)s->cur_file_info.compression_method; + + if (level!=NULL) + { + *level = 6; + switch (s->cur_file_info.flag & 0x06) + { + case 6 : *level = 1; break; + case 4 : *level = 2; break; + case 2 : *level = 9; break; + } + } + + if ((s->cur_file_info.compression_method!=0) && + (s->cur_file_info.compression_method!=Z_DEFLATED)) + err=UNZ_BADZIPFILE; + + pfile_in_zip_read_info->crc32_wait=s->cur_file_info.crc; + pfile_in_zip_read_info->crc32=0; + pfile_in_zip_read_info->compression_method = + s->cur_file_info.compression_method; + pfile_in_zip_read_info->filestream=s->filestream; + pfile_in_zip_read_info->z_filefunc=s->z_filefunc; + pfile_in_zip_read_info->byte_before_the_zipfile=s->byte_before_the_zipfile; + + pfile_in_zip_read_info->stream.total_out = 0; + + if ((s->cur_file_info.compression_method==Z_DEFLATED) && + (!raw)) + { + pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; + pfile_in_zip_read_info->stream.zfree = (free_func)0; + pfile_in_zip_read_info->stream.opaque = (voidpf)0; + pfile_in_zip_read_info->stream.next_in = (voidpf)0; + pfile_in_zip_read_info->stream.avail_in = 0; + + err=inflateInit2(&pfile_in_zip_read_info->stream, -MAX_WBITS); + if (err == Z_OK) + pfile_in_zip_read_info->stream_initialised=1; + else + { + TRYFREE(pfile_in_zip_read_info); + return err; + } + /* windowBits is passed < 0 to tell that there is no zlib header. + * Note that in this case inflate *requires* an extra "dummy" byte + * after the compressed stream in order to complete decompression and + * return Z_STREAM_END. + * In unzip, i don't wait absolutely Z_STREAM_END because I known the + * size of both compressed and uncompressed data + */ + } + pfile_in_zip_read_info->rest_read_compressed = + s->cur_file_info.compressed_size ; + pfile_in_zip_read_info->rest_read_uncompressed = + s->cur_file_info.uncompressed_size ; + + + pfile_in_zip_read_info->pos_in_zipfile = + s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + + iSizeVar; + + pfile_in_zip_read_info->stream.avail_in = (uInt)0; + + s->pfile_in_zip_read = pfile_in_zip_read_info; + +# ifndef NOUNCRYPT + if (password != NULL) + { + int i; + s->pcrc_32_tab = get_crc_table(); + init_keys(password,s->keys,s->pcrc_32_tab); + if (ZSEEK(s->z_filefunc, s->filestream, + s->pfile_in_zip_read->pos_in_zipfile + + s->pfile_in_zip_read->byte_before_the_zipfile, + SEEK_SET)!=0) + return UNZ_INTERNALERROR; + if(ZREAD(s->z_filefunc, s->filestream,source, 12)<12) + return UNZ_INTERNALERROR; + + for (i = 0; i<12; i++) + zdecode(s->keys,s->pcrc_32_tab,source[i]); + + s->pfile_in_zip_read->pos_in_zipfile+=12; + s->encrypted=1; + } +# endif + + + return UNZ_OK; +} + +extern int ZEXPORT unzOpenCurrentFile (file) + unzFile file; +{ + return unzOpenCurrentFile3(file, NULL, NULL, 0, NULL); +} + +extern int ZEXPORT unzOpenCurrentFilePassword (file, password) + unzFile file; + const char* password; +{ + return unzOpenCurrentFile3(file, NULL, NULL, 0, password); +} + +extern int ZEXPORT unzOpenCurrentFile2 (file,method,level,raw) + unzFile file; + int* method; + int* level; + int raw; +{ + return unzOpenCurrentFile3(file, method, level, raw, NULL); +} + +/* + Read bytes from the current file. + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ +extern int ZEXPORT unzReadCurrentFile (file, buf, len) + unzFile file; + voidp buf; + unsigned len; +{ + int err=UNZ_OK; + uInt iRead = 0; + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if ((pfile_in_zip_read_info->read_buffer == NULL)) + return UNZ_END_OF_LIST_OF_FILE; + if (len==0) + return 0; + + pfile_in_zip_read_info->stream.next_out = (Bytef*)buf; + + pfile_in_zip_read_info->stream.avail_out = (uInt)len; + + if ((len>pfile_in_zip_read_info->rest_read_uncompressed) && + (!(pfile_in_zip_read_info->raw))) + pfile_in_zip_read_info->stream.avail_out = + (uInt)pfile_in_zip_read_info->rest_read_uncompressed; + + if ((len>pfile_in_zip_read_info->rest_read_compressed+ + pfile_in_zip_read_info->stream.avail_in) && + (pfile_in_zip_read_info->raw)) + pfile_in_zip_read_info->stream.avail_out = + (uInt)pfile_in_zip_read_info->rest_read_compressed+ + pfile_in_zip_read_info->stream.avail_in; + + while (pfile_in_zip_read_info->stream.avail_out>0) + { + if ((pfile_in_zip_read_info->stream.avail_in==0) && + (pfile_in_zip_read_info->rest_read_compressed>0)) + { + uInt uReadThis = UNZ_BUFSIZE; + if (pfile_in_zip_read_info->rest_read_compressedrest_read_compressed; + if (uReadThis == 0) + return UNZ_EOF; + if (ZSEEK(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + pfile_in_zip_read_info->pos_in_zipfile + + pfile_in_zip_read_info->byte_before_the_zipfile, + ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + if (ZREAD(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + pfile_in_zip_read_info->read_buffer, + uReadThis)!=uReadThis) + return UNZ_ERRNO; + + +# ifndef NOUNCRYPT + if(s->encrypted) + { + uInt i; + for(i=0;iread_buffer[i] = + zdecode(s->keys,s->pcrc_32_tab, + pfile_in_zip_read_info->read_buffer[i]); + } +# endif + + + pfile_in_zip_read_info->pos_in_zipfile += uReadThis; + + pfile_in_zip_read_info->rest_read_compressed-=uReadThis; + + pfile_in_zip_read_info->stream.next_in = + (Bytef*)pfile_in_zip_read_info->read_buffer; + pfile_in_zip_read_info->stream.avail_in = (uInt)uReadThis; + } + + if ((pfile_in_zip_read_info->compression_method==0) || (pfile_in_zip_read_info->raw)) + { + uInt uDoCopy,i ; + + if ((pfile_in_zip_read_info->stream.avail_in == 0) && + (pfile_in_zip_read_info->rest_read_compressed == 0)) + return (iRead==0) ? UNZ_EOF : iRead; + + if (pfile_in_zip_read_info->stream.avail_out < + pfile_in_zip_read_info->stream.avail_in) + uDoCopy = pfile_in_zip_read_info->stream.avail_out ; + else + uDoCopy = pfile_in_zip_read_info->stream.avail_in ; + + for (i=0;istream.next_out+i) = + *(pfile_in_zip_read_info->stream.next_in+i); + + pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32, + pfile_in_zip_read_info->stream.next_out, + uDoCopy); + pfile_in_zip_read_info->rest_read_uncompressed-=uDoCopy; + pfile_in_zip_read_info->stream.avail_in -= uDoCopy; + pfile_in_zip_read_info->stream.avail_out -= uDoCopy; + pfile_in_zip_read_info->stream.next_out += uDoCopy; + pfile_in_zip_read_info->stream.next_in += uDoCopy; + pfile_in_zip_read_info->stream.total_out += uDoCopy; + iRead += uDoCopy; + } + else + { + uLong uTotalOutBefore,uTotalOutAfter; + const Bytef *bufBefore; + uLong uOutThis; + int flush=Z_SYNC_FLUSH; + + uTotalOutBefore = pfile_in_zip_read_info->stream.total_out; + bufBefore = pfile_in_zip_read_info->stream.next_out; + + /* + if ((pfile_in_zip_read_info->rest_read_uncompressed == + pfile_in_zip_read_info->stream.avail_out) && + (pfile_in_zip_read_info->rest_read_compressed == 0)) + flush = Z_FINISH; + */ + err=inflate(&pfile_in_zip_read_info->stream,flush); + + if ((err>=0) && (pfile_in_zip_read_info->stream.msg!=NULL)) + err = Z_DATA_ERROR; + + uTotalOutAfter = pfile_in_zip_read_info->stream.total_out; + uOutThis = uTotalOutAfter-uTotalOutBefore; + + pfile_in_zip_read_info->crc32 = + crc32(pfile_in_zip_read_info->crc32,bufBefore, + (uInt)(uOutThis)); + + pfile_in_zip_read_info->rest_read_uncompressed -= + uOutThis; + + iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); + + if (err==Z_STREAM_END) + return (iRead==0) ? UNZ_EOF : iRead; + if (err!=Z_OK) + break; + } + } + + if (err==Z_OK) + return iRead; + return err; +} + + +/* + Give the current position in uncompressed data +*/ +extern z_off_t ZEXPORT unztell (file) + unzFile file; +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + return (z_off_t)pfile_in_zip_read_info->stream.total_out; +} + + +/* + return 1 if the end of file was reached, 0 elsewhere +*/ +extern int ZEXPORT unzeof (file) + unzFile file; +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + if (pfile_in_zip_read_info->rest_read_uncompressed == 0) + return 1; + else + return 0; +} + + + +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the local-header version of the extra field (sometimes, there is + more info in the local-header version than in the central-header) + + if buf==NULL, it return the size of the local extra field that can be read + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of bytes copied in buf, or (if <0) + the error code +*/ +extern int ZEXPORT unzGetLocalExtrafield (file,buf,len) + unzFile file; + voidp buf; + unsigned len; +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + uInt read_now; + uLong size_to_read; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + size_to_read = (pfile_in_zip_read_info->size_local_extrafield - + pfile_in_zip_read_info->pos_local_extrafield); + + if (buf==NULL) + return (int)size_to_read; + + if (len>size_to_read) + read_now = (uInt)size_to_read; + else + read_now = (uInt)len ; + + if (read_now==0) + return 0; + + if (ZSEEK(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + pfile_in_zip_read_info->offset_local_extrafield + + pfile_in_zip_read_info->pos_local_extrafield, + ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + + if (ZREAD(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + buf,read_now)!=read_now) + return UNZ_ERRNO; + + return (int)read_now; +} + +/* + Close the file in zip opened with unzipOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ +extern int ZEXPORT unzCloseCurrentFile (file) + unzFile file; +{ + int err=UNZ_OK; + + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if ((pfile_in_zip_read_info->rest_read_uncompressed == 0) && + (!pfile_in_zip_read_info->raw)) + { + if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait) + err=UNZ_CRCERROR; + } + + + TRYFREE(pfile_in_zip_read_info->read_buffer); + pfile_in_zip_read_info->read_buffer = NULL; + if (pfile_in_zip_read_info->stream_initialised) + inflateEnd(&pfile_in_zip_read_info->stream); + + pfile_in_zip_read_info->stream_initialised = 0; + TRYFREE(pfile_in_zip_read_info); + + s->pfile_in_zip_read=NULL; + + return err; +} + + +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 +*/ +extern int ZEXPORT unzGetGlobalComment (file, szComment, uSizeBuf) + unzFile file; + char *szComment; + uLong uSizeBuf; +{ + int err=UNZ_OK; + unz_s* s; + uLong uReadThis ; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + uReadThis = uSizeBuf; + if (uReadThis>s->gi.size_comment) + uReadThis = s->gi.size_comment; + + if (ZSEEK(s->z_filefunc,s->filestream,s->central_pos+22,ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + + if (uReadThis>0) + { + *szComment='\0'; + if (ZREAD(s->z_filefunc,s->filestream,szComment,uReadThis)!=uReadThis) + return UNZ_ERRNO; + } + + if ((szComment != NULL) && (uSizeBuf > s->gi.size_comment)) + *(szComment+s->gi.size_comment)='\0'; + return (int)uReadThis; +} + +/* Additions by RX '2004 */ +extern uLong ZEXPORT unzGetOffset (file) + unzFile file; +{ + unz_s* s; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return 0; + if (s->gi.number_entry != 0 && s->gi.number_entry != 0xffff) + if (s->num_file==s->gi.number_entry) + return 0; + return s->pos_in_central_dir; +} + +extern int ZEXPORT unzSetOffset (file, pos) + unzFile file; + uLong pos; +{ + unz_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + s->pos_in_central_dir = pos; + s->num_file = s->gi.number_entry; /* hack */ + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} diff --git a/Modules/minizip/source/zip.c b/Modules/minizip/source/zip.c new file mode 100644 index 0000000..7fbe002 --- /dev/null +++ b/Modules/minizip/source/zip.c @@ -0,0 +1,1219 @@ +/* zip.c -- IO on .zip files using zlib + Version 1.01e, February 12th, 2005 + + 27 Dec 2004 Rolf Kalbermatter + Modification to zipOpen2 to support globalComment retrieval. + + Copyright (C) 1998-2005 Gilles Vollant + + Read zip.h for more info +*/ + + +#include +#include +#include +#include +#include "zlib.h" +#include "zip.h" + +#ifdef STDC +# include +# include +# include +#endif +#ifdef NO_ERRNO_H + extern int errno; +#else +# include +#endif + + +#ifndef local +# define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + +#ifndef VERSIONMADEBY +# define VERSIONMADEBY (0x0) /* platform depedent */ +#endif + +#ifndef Z_BUFSIZE +#define Z_BUFSIZE (16384) +#endif + +#ifndef Z_MAXFILENAMEINZIP +#define Z_MAXFILENAMEINZIP (256) +#endif + +#ifndef ALLOC +# define ALLOC(size) (malloc(size)) +#endif +#ifndef TRYFREE +# define TRYFREE(p) {if (p) free(p);} +#endif + +/* +#define SIZECENTRALDIRITEM (0x2e) +#define SIZEZIPLOCALHEADER (0x1e) +*/ + +/* I've found an old Unix (a SunOS 4.1.3_U1) without all SEEK_* defined.... */ + +#ifndef SEEK_CUR +#define SEEK_CUR 1 +#endif + +#ifndef SEEK_END +#define SEEK_END 2 +#endif + +#ifndef SEEK_SET +#define SEEK_SET 0 +#endif + +#ifndef DEF_MEM_LEVEL +#if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +#else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif +#endif +const char zip_copyright[] = + " zip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; + + +#define SIZEDATA_INDATABLOCK (4096-(4*4)) + +#define LOCALHEADERMAGIC (0x04034b50) +#define CENTRALHEADERMAGIC (0x02014b50) +#define ENDHEADERMAGIC (0x06054b50) + +#define FLAG_LOCALHEADER_OFFSET (0x06) +#define CRC_LOCALHEADER_OFFSET (0x0e) + +#define SIZECENTRALHEADER (0x2e) /* 46 */ + +typedef struct linkedlist_datablock_internal_s +{ + struct linkedlist_datablock_internal_s* next_datablock; + uLong avail_in_this_block; + uLong filled_in_this_block; + uLong unused; /* for future use and alignement */ + unsigned char data[SIZEDATA_INDATABLOCK]; +} linkedlist_datablock_internal; + +typedef struct linkedlist_data_s +{ + linkedlist_datablock_internal* first_block; + linkedlist_datablock_internal* last_block; +} linkedlist_data; + + +typedef struct +{ + z_stream stream; /* zLib stream structure for inflate */ + int stream_initialised; /* 1 is stream is initialised */ + uInt pos_in_buffered_data; /* last written byte in buffered_data */ + + uLong pos_local_header; /* offset of the local header of the file + currenty writing */ + char* central_header; /* central header data for the current file */ + uLong size_centralheader; /* size of the central header for cur file */ + uLong flag; /* flag of the file currently writing */ + + int method; /* compression method of file currenty wr.*/ + int raw; /* 1 for directly writing raw data */ + Byte buffered_data[Z_BUFSIZE];/* buffer contain compressed data to be writ*/ + uLong dosDate; + uLong crc32; + int encrypt; +#ifndef NOCRYPT + unsigned long keys[3]; /* keys defining the pseudo-random sequence */ + const unsigned long* pcrc_32_tab; + int crypt_header_size; +#endif +} curfile_info; + +typedef struct +{ + zlib_filefunc_def z_filefunc; + voidpf filestream; /* io structore of the zipfile */ + linkedlist_data central_dir;/* datablock with central dir in construction*/ + int in_opened_file_inzip; /* 1 if a file in the zip is currently writ.*/ + curfile_info ci; /* info on the file curretly writing */ + + uLong begin_pos; /* position of the beginning of the zipfile */ + uLong add_position_when_writting_offset; + uLong number_entry; +#ifndef NO_ADDFILEINEXISTINGZIP + char *globalcomment; +#endif +} zip_internal; + + + +#ifndef NOCRYPT +#define INCLUDECRYPTINGCODE_IFCRYPTALLOWED +#include "crypt.h" +#endif + +local linkedlist_datablock_internal* allocate_new_datablock() +{ + linkedlist_datablock_internal* ldi; + ldi = (linkedlist_datablock_internal*) + ALLOC(sizeof(linkedlist_datablock_internal)); + if (ldi!=NULL) + { + ldi->next_datablock = NULL ; + ldi->filled_in_this_block = 0 ; + ldi->avail_in_this_block = SIZEDATA_INDATABLOCK ; + } + return ldi; +} + +local void free_datablock(ldi) + linkedlist_datablock_internal* ldi; +{ + while (ldi!=NULL) + { + linkedlist_datablock_internal* ldinext = ldi->next_datablock; + TRYFREE(ldi); + ldi = ldinext; + } +} + +local void init_linkedlist(ll) + linkedlist_data* ll; +{ + ll->first_block = ll->last_block = NULL; +} + +local void free_linkedlist(ll) + linkedlist_data* ll; +{ + free_datablock(ll->first_block); + ll->first_block = ll->last_block = NULL; +} + + +local int add_data_in_datablock(ll,buf,len) + linkedlist_data* ll; + const void* buf; + uLong len; +{ + linkedlist_datablock_internal* ldi; + const unsigned char* from_copy; + + if (ll==NULL) + return ZIP_INTERNALERROR; + + if (ll->last_block == NULL) + { + ll->first_block = ll->last_block = allocate_new_datablock(); + if (ll->first_block == NULL) + return ZIP_INTERNALERROR; + } + + ldi = ll->last_block; + from_copy = (unsigned char*)buf; + + while (len>0) + { + uInt copy_this; + uInt i; + unsigned char* to_copy; + + if (ldi->avail_in_this_block==0) + { + ldi->next_datablock = allocate_new_datablock(); + if (ldi->next_datablock == NULL) + return ZIP_INTERNALERROR; + ldi = ldi->next_datablock ; + ll->last_block = ldi; + } + + if (ldi->avail_in_this_block < len) + copy_this = (uInt)ldi->avail_in_this_block; + else + copy_this = (uInt)len; + + to_copy = &(ldi->data[ldi->filled_in_this_block]); + + for (i=0;ifilled_in_this_block += copy_this; + ldi->avail_in_this_block -= copy_this; + from_copy += copy_this ; + len -= copy_this; + } + return ZIP_OK; +} + + + +/****************************************************************************/ + +#ifndef NO_ADDFILEINEXISTINGZIP +/* =========================================================================== + Inputs a long in LSB order to the given file + nbByte == 1, 2 or 4 (byte, short or long) +*/ + +local int ziplocal_putValue OF((const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream, uLong x, int nbByte)); +local int ziplocal_putValue (pzlib_filefunc_def, filestream, x, nbByte) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; + uLong x; + int nbByte; +{ + unsigned char buf[4]; + int n; + for (n = 0; n < nbByte; n++) + { + buf[n] = (unsigned char)(x & 0xff); + x >>= 8; + } + if (x != 0) + { /* data overflow - hack for ZIP64 (X Roche) */ + for (n = 0; n < nbByte; n++) + { + buf[n] = 0xff; + } + } + + if (ZWRITE(*pzlib_filefunc_def,filestream,buf,nbByte)!=(uLong)nbByte) + return ZIP_ERRNO; + else + return ZIP_OK; +} + +local void ziplocal_putValue_inmemory OF((void* dest, uLong x, int nbByte)); +local void ziplocal_putValue_inmemory (dest, x, nbByte) + void* dest; + uLong x; + int nbByte; +{ + unsigned char* buf=(unsigned char*)dest; + int n; + for (n = 0; n < nbByte; n++) { + buf[n] = (unsigned char)(x & 0xff); + x >>= 8; + } + + if (x != 0) + { /* data overflow - hack for ZIP64 */ + for (n = 0; n < nbByte; n++) + { + buf[n] = 0xff; + } + } +} + +/****************************************************************************/ + + +local uLong ziplocal_TmzDateToDosDate(ptm,dosDate) + const tm_zip* ptm; + uLong dosDate; +{ + uLong year = (uLong)ptm->tm_year; + if (year>1980) + year-=1980; + else if (year>80) + year-=80; + return + (uLong) (((ptm->tm_mday) + (32 * (ptm->tm_mon+1)) + (512 * year)) << 16) | + ((ptm->tm_sec/2) + (32* ptm->tm_min) + (2048 * (uLong)ptm->tm_hour)); +} + + +/****************************************************************************/ + +local int ziplocal_getByte OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream, + int *pi)); + +local int ziplocal_getByte(pzlib_filefunc_def,filestream,pi) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; + int *pi; +{ + unsigned char c; + int err = (int)ZREAD(*pzlib_filefunc_def,filestream,&c,1); + if (err==1) + { + *pi = (int)c; + return ZIP_OK; + } + else + { + if (ZERROR(*pzlib_filefunc_def,filestream)) + return ZIP_ERRNO; + else + return ZIP_EOF; + } +} + + +/* =========================================================================== + Reads a long in LSB order from the given gz_stream. Sets +*/ +local int ziplocal_getShort OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX)); + +local int ziplocal_getShort (pzlib_filefunc_def,filestream,pX) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; + uLong *pX; +{ + uLong x ; + int i; + int err; + + err = ziplocal_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==ZIP_OK) + err = ziplocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<8; + + if (err==ZIP_OK) + *pX = x; + else + *pX = 0; + return err; +} + +local int ziplocal_getLong OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX)); + +local int ziplocal_getLong (pzlib_filefunc_def,filestream,pX) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; + uLong *pX; +{ + uLong x ; + int i; + int err; + + err = ziplocal_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==ZIP_OK) + err = ziplocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<8; + + if (err==ZIP_OK) + err = ziplocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<16; + + if (err==ZIP_OK) + err = ziplocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<24; + + if (err==ZIP_OK) + *pX = x; + else + *pX = 0; + return err; +} + +#ifndef BUFREADCOMMENT +#define BUFREADCOMMENT (0x400) +#endif +/* + Locate the Central directory of a zipfile (at the end, just before + the global comment) +*/ +local uLong ziplocal_SearchCentralDir OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream)); + +local uLong ziplocal_SearchCentralDir(pzlib_filefunc_def,filestream) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; +{ + unsigned char* buf; + uLong uSizeFile; + uLong uBackRead; + uLong uMaxBack=0xffff; /* maximum size of global comment */ + uLong uPosFound=0; + + if (ZSEEK(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) + return 0; + + + uSizeFile = ZTELL(*pzlib_filefunc_def,filestream); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + if (buf==NULL) + return 0; + + uBackRead = 4; + while (uBackReaduMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uSizeFile-uReadPos); + if (ZSEEK(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) + break; + + if (ZREAD(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && + ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) + { + uPosFound = uReadPos+i; + break; + } + + if (uPosFound!=0) + break; + } + TRYFREE(buf); + return uPosFound; +} +#endif /* !NO_ADDFILEINEXISTINGZIP*/ + +/************************************************************/ +extern zipFile ZEXPORT zipOpen2 (pathname, append, globalcomment, pzlib_filefunc_def) + const char *pathname; + int append; + zipcharpc* globalcomment; + zlib_filefunc_def* pzlib_filefunc_def; +{ + zip_internal ziinit; + zip_internal* zi; + int err=ZIP_OK; + + + if (pzlib_filefunc_def==NULL) + fill_fopen_filefunc(&ziinit.z_filefunc); + else + ziinit.z_filefunc = *pzlib_filefunc_def; + + ziinit.filestream = (*(ziinit.z_filefunc.zopen_file)) + (ziinit.z_filefunc.opaque, + pathname, + (append == APPEND_STATUS_CREATE) ? + (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_CREATE) : + (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_EXISTING)); + + if (ziinit.filestream == NULL) + return NULL; + ziinit.begin_pos = ZTELL(ziinit.z_filefunc,ziinit.filestream); + ziinit.in_opened_file_inzip = 0; + ziinit.ci.stream_initialised = 0; + ziinit.number_entry = 0; + ziinit.add_position_when_writting_offset = 0; + init_linkedlist(&(ziinit.central_dir)); + + + zi = (zip_internal*)ALLOC(sizeof(zip_internal)); + if (zi==NULL) + { + ZCLOSE(ziinit.z_filefunc,ziinit.filestream); + return NULL; + } + + /* now we add file in a zipfile */ +# ifndef NO_ADDFILEINEXISTINGZIP + ziinit.globalcomment = NULL; + if (append == APPEND_STATUS_ADDINZIP) + { + uLong byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ + + uLong size_central_dir; /* size of the central directory */ + uLong offset_central_dir; /* offset of start of central directory */ + uLong central_pos,uL; + + uLong number_disk; /* number of the current dist, used for + spaning ZIP, unsupported, always 0*/ + uLong number_disk_with_CD; /* number the the disk with central dir, used + for spaning ZIP, unsupported, always 0*/ + uLong number_entry; + uLong number_entry_CD; /* total number of entries in + the central dir + (same than number_entry on nospan) */ + uLong size_comment; + + central_pos = ziplocal_SearchCentralDir(&ziinit.z_filefunc,ziinit.filestream); + if (central_pos==0) + err=ZIP_ERRNO; + + if (ZSEEK(ziinit.z_filefunc, ziinit.filestream, + central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) + err=ZIP_ERRNO; + + /* the signature, already checked */ + if (ziplocal_getLong(&ziinit.z_filefunc, ziinit.filestream,&uL)!=ZIP_OK) + err=ZIP_ERRNO; + + /* number of this disk */ + if (ziplocal_getShort(&ziinit.z_filefunc, ziinit.filestream,&number_disk)!=ZIP_OK) + err=ZIP_ERRNO; + + /* number of the disk with the start of the central directory */ + if (ziplocal_getShort(&ziinit.z_filefunc, ziinit.filestream,&number_disk_with_CD)!=ZIP_OK) + err=ZIP_ERRNO; + + /* total number of entries in the central dir on this disk */ + if (ziplocal_getShort(&ziinit.z_filefunc, ziinit.filestream,&number_entry)!=ZIP_OK) + err=ZIP_ERRNO; + + /* total number of entries in the central dir */ + if (ziplocal_getShort(&ziinit.z_filefunc, ziinit.filestream,&number_entry_CD)!=ZIP_OK) + err=ZIP_ERRNO; + + if ((number_entry_CD!=number_entry) || + (number_disk_with_CD!=0) || + (number_disk!=0)) + err=ZIP_BADZIPFILE; + + /* size of the central directory */ + if (ziplocal_getLong(&ziinit.z_filefunc, ziinit.filestream,&size_central_dir)!=ZIP_OK) + err=ZIP_ERRNO; + + /* offset of start of central directory with respect to the + starting disk number */ + if (ziplocal_getLong(&ziinit.z_filefunc, ziinit.filestream,&offset_central_dir)!=ZIP_OK) + err=ZIP_ERRNO; + + /* zipfile global comment length */ + if (ziplocal_getShort(&ziinit.z_filefunc, ziinit.filestream,&size_comment)!=ZIP_OK) + err=ZIP_ERRNO; + + if ((central_pos0) + { + ziinit.globalcomment = ALLOC(size_comment+1); + if (ziinit.globalcomment) + { + size_comment = ZREAD(ziinit.z_filefunc, ziinit.filestream,ziinit.globalcomment,size_comment); + ziinit.globalcomment[size_comment]=0; + } + } + + byte_before_the_zipfile = central_pos - + (offset_central_dir+size_central_dir); + ziinit.add_position_when_writting_offset = byte_before_the_zipfile; + + { + uLong size_central_dir_to_read = size_central_dir; + size_t buf_size = SIZEDATA_INDATABLOCK; + void* buf_read = (void*)ALLOC(buf_size); + if (ZSEEK(ziinit.z_filefunc, ziinit.filestream, + offset_central_dir + byte_before_the_zipfile, + ZLIB_FILEFUNC_SEEK_SET) != 0) + err=ZIP_ERRNO; + + while ((size_central_dir_to_read>0) && (err==ZIP_OK)) + { + uLong read_this = SIZEDATA_INDATABLOCK; + if (read_this > size_central_dir_to_read) + read_this = size_central_dir_to_read; + if (ZREAD(ziinit.z_filefunc, ziinit.filestream,buf_read,read_this) != read_this) + err=ZIP_ERRNO; + + if (err==ZIP_OK) + err = add_data_in_datablock(&ziinit.central_dir,buf_read, + (uLong)read_this); + size_central_dir_to_read-=read_this; + } + TRYFREE(buf_read); + } + ziinit.begin_pos = byte_before_the_zipfile; + ziinit.number_entry = number_entry_CD; + + if (ZSEEK(ziinit.z_filefunc, ziinit.filestream, + offset_central_dir+byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET)!=0) + err=ZIP_ERRNO; + } + + if (globalcomment) + { + *globalcomment = ziinit.globalcomment; + } +# endif /* !NO_ADDFILEINEXISTINGZIP*/ + + if (err != ZIP_OK) + { +# ifndef NO_ADDFILEINEXISTINGZIP + TRYFREE(ziinit.globalcomment); +# endif /* !NO_ADDFILEINEXISTINGZIP*/ + TRYFREE(zi); + return NULL; + } + else + { + *zi = ziinit; + return (zipFile)zi; + } +} + +extern zipFile ZEXPORT zipOpen (pathname, append) + const char *pathname; + int append; +{ + return zipOpen2(pathname,append,NULL,NULL); +} + +extern int ZEXPORT zipOpenNewFileInZip3 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, raw, + windowBits, memLevel, strategy, + password, crcForCrypting) + zipFile file; + const char* filename; + const zip_fileinfo* zipfi; + const void* extrafield_local; + uInt size_extrafield_local; + const void* extrafield_global; + uInt size_extrafield_global; + const char* comment; + int method; + int level; + int raw; + int windowBits; + int memLevel; + int strategy; + const char* password; + uLong crcForCrypting; +{ + zip_internal* zi; + uInt size_filename; + uInt size_comment; + uInt i; + int err = ZIP_OK; + +# ifdef NOCRYPT + if (password != NULL) + return ZIP_PARAMERROR; +# endif + + if (file == NULL) + return ZIP_PARAMERROR; + if ((method!=0) && (method!=Z_DEFLATED)) + return ZIP_PARAMERROR; + + zi = (zip_internal*)file; + + if (zi->in_opened_file_inzip == 1) + { + err = zipCloseFileInZip (file); + if (err != ZIP_OK) + return err; + } + + + if (filename==NULL) + filename="-"; + + if (comment==NULL) + size_comment = 0; + else + size_comment = (uInt)strlen(comment); + + size_filename = (uInt)strlen(filename); + + if (zipfi == NULL) + zi->ci.dosDate = 0; + else + { + if (zipfi->dosDate != 0) + zi->ci.dosDate = zipfi->dosDate; + else zi->ci.dosDate = ziplocal_TmzDateToDosDate(&zipfi->tmz_date,zipfi->dosDate); + } + + zi->ci.flag = 0; + if ((level==8) || (level==9)) + zi->ci.flag |= 2; + if ((level==2)) + zi->ci.flag |= 4; + if ((level==1)) + zi->ci.flag |= 6; + if (password != NULL) + zi->ci.flag |= 1; + + zi->ci.crc32 = 0; + zi->ci.method = method; + zi->ci.encrypt = 0; + zi->ci.stream_initialised = 0; + zi->ci.pos_in_buffered_data = 0; + zi->ci.raw = raw; + zi->ci.pos_local_header = ZTELL(zi->z_filefunc,zi->filestream) ; + zi->ci.size_centralheader = SIZECENTRALHEADER + size_filename + + size_extrafield_global + size_comment; + zi->ci.central_header = (char*)ALLOC((uInt)zi->ci.size_centralheader); + + ziplocal_putValue_inmemory(zi->ci.central_header,(uLong)CENTRALHEADERMAGIC,4); + /* version info */ + ziplocal_putValue_inmemory(zi->ci.central_header+4,(uLong)VERSIONMADEBY,2); + ziplocal_putValue_inmemory(zi->ci.central_header+6,(uLong)20,2); + ziplocal_putValue_inmemory(zi->ci.central_header+8,(uLong)zi->ci.flag,2); + ziplocal_putValue_inmemory(zi->ci.central_header+10,(uLong)zi->ci.method,2); + ziplocal_putValue_inmemory(zi->ci.central_header+12,(uLong)zi->ci.dosDate,4); + ziplocal_putValue_inmemory(zi->ci.central_header+16,(uLong)0,4); /*crc*/ + ziplocal_putValue_inmemory(zi->ci.central_header+20,(uLong)0,4); /*compr size*/ + ziplocal_putValue_inmemory(zi->ci.central_header+24,(uLong)0,4); /*uncompr size*/ + ziplocal_putValue_inmemory(zi->ci.central_header+28,(uLong)size_filename,2); + ziplocal_putValue_inmemory(zi->ci.central_header+30,(uLong)size_extrafield_global,2); + ziplocal_putValue_inmemory(zi->ci.central_header+32,(uLong)size_comment,2); + ziplocal_putValue_inmemory(zi->ci.central_header+34,(uLong)0,2); /*disk nm start*/ + + if (zipfi==NULL) + ziplocal_putValue_inmemory(zi->ci.central_header+36,(uLong)0,2); + else + ziplocal_putValue_inmemory(zi->ci.central_header+36,(uLong)zipfi->internal_fa,2); + + if (zipfi==NULL) + ziplocal_putValue_inmemory(zi->ci.central_header+38,(uLong)0,4); + else + ziplocal_putValue_inmemory(zi->ci.central_header+38,(uLong)zipfi->external_fa,4); + + ziplocal_putValue_inmemory(zi->ci.central_header+42,(uLong)zi->ci.pos_local_header- zi->add_position_when_writting_offset,4); + + for (i=0;ici.central_header+SIZECENTRALHEADER+i) = *(filename+i); + + for (i=0;ici.central_header+SIZECENTRALHEADER+size_filename+i) = + *(((const char*)extrafield_global)+i); + + for (i=0;ici.central_header+SIZECENTRALHEADER+size_filename+ + size_extrafield_global+i) = *(comment+i); + if (zi->ci.central_header == NULL) + return ZIP_INTERNALERROR; + + /* write the local header */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)LOCALHEADERMAGIC,4); + + if (err==ZIP_OK) + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)20,2);/* version needed to extract */ + if (err==ZIP_OK) + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.flag,2); + + if (err==ZIP_OK) + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.method,2); + + if (err==ZIP_OK) + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.dosDate,4); + + if (err==ZIP_OK) + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* crc 32, unknown */ + if (err==ZIP_OK) + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* compressed size, unknown */ + if (err==ZIP_OK) + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* uncompressed size, unknown */ + + if (err==ZIP_OK) + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_filename,2); + + if (err==ZIP_OK) + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_extrafield_local,2); + + if ((err==ZIP_OK) && (size_filename>0)) + if (ZWRITE(zi->z_filefunc,zi->filestream,filename,size_filename)!=size_filename) + err = ZIP_ERRNO; + + if ((err==ZIP_OK) && (size_extrafield_local>0)) + if (ZWRITE(zi->z_filefunc,zi->filestream,extrafield_local,size_extrafield_local) + !=size_extrafield_local) + err = ZIP_ERRNO; + + zi->ci.stream.avail_in = (uInt)0; + zi->ci.stream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.stream.next_out = zi->ci.buffered_data; + zi->ci.stream.total_in = 0; + zi->ci.stream.total_out = 0; + + if ((err==ZIP_OK) && (zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) + { + zi->ci.stream.zalloc = (alloc_func)0; + zi->ci.stream.zfree = (free_func)0; + zi->ci.stream.opaque = (voidpf)0; + + if (windowBits>0) + windowBits = -windowBits; + + err = deflateInit2(&zi->ci.stream, level, + Z_DEFLATED, windowBits, memLevel, strategy); + + if (err==Z_OK) + zi->ci.stream_initialised = 1; + } +# ifndef NOCRYPT + zi->ci.crypt_header_size = 0; + if ((err==Z_OK) && (password != NULL)) + { + unsigned char bufHead[RAND_HEAD_LEN]; + unsigned int sizeHead; + zi->ci.encrypt = 1; + zi->ci.pcrc_32_tab = get_crc_table(); + /*init_keys(password,zi->ci.keys,zi->ci.pcrc_32_tab);*/ + + sizeHead=crypthead(password,bufHead,RAND_HEAD_LEN,zi->ci.keys,zi->ci.pcrc_32_tab,crcForCrypting); + zi->ci.crypt_header_size = sizeHead; + + if (ZWRITE(zi->z_filefunc,zi->filestream,bufHead,sizeHead) != sizeHead) + err = ZIP_ERRNO; + } +# endif + + if (err==Z_OK) + zi->in_opened_file_inzip = 1; + return err; +} + +extern int ZEXPORT zipOpenNewFileInZip2(file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, raw) + zipFile file; + const char* filename; + const zip_fileinfo* zipfi; + const void* extrafield_local; + uInt size_extrafield_local; + const void* extrafield_global; + uInt size_extrafield_global; + const char* comment; + int method; + int level; + int raw; +{ + return zipOpenNewFileInZip3 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, raw, + -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, + NULL, 0); +} + +extern int ZEXPORT zipOpenNewFileInZip (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level) + zipFile file; + const char* filename; + const zip_fileinfo* zipfi; + const void* extrafield_local; + uInt size_extrafield_local; + const void* extrafield_global; + uInt size_extrafield_global; + const char* comment; + int method; + int level; +{ + return zipOpenNewFileInZip2 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, 0); +} + +local int zipFlushWriteBuffer(zi) + zip_internal* zi; +{ + int err=ZIP_OK; + + if (zi->ci.encrypt != 0) + { +#ifndef NOCRYPT + uInt i; + int t; + for (i=0;ici.pos_in_buffered_data;i++) + zi->ci.buffered_data[i] = zencode(zi->ci.keys, zi->ci.pcrc_32_tab, + zi->ci.buffered_data[i],t); +#endif + } + if (ZWRITE(zi->z_filefunc,zi->filestream,zi->ci.buffered_data,zi->ci.pos_in_buffered_data) + !=zi->ci.pos_in_buffered_data) + err = ZIP_ERRNO; + zi->ci.pos_in_buffered_data = 0; + return err; +} + +extern int ZEXPORT zipWriteInFileInZip (file, buf, len) + zipFile file; + const void* buf; + unsigned len; +{ + zip_internal* zi; + int err=ZIP_OK; + + if (file == NULL) + return ZIP_PARAMERROR; + zi = (zip_internal*)file; + + if (zi->in_opened_file_inzip == 0) + return ZIP_PARAMERROR; + + zi->ci.stream.next_in = (void*)buf; + zi->ci.stream.avail_in = len; + zi->ci.crc32 = crc32(zi->ci.crc32,buf,len); + + while ((err==ZIP_OK) && (zi->ci.stream.avail_in>0)) + { + if (zi->ci.stream.avail_out == 0) + { + if (zipFlushWriteBuffer(zi) == ZIP_ERRNO) + err = ZIP_ERRNO; + zi->ci.stream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.stream.next_out = zi->ci.buffered_data; + } + + + if(err != ZIP_OK) + break; + + if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) + { + uLong uTotalOutBefore = zi->ci.stream.total_out; + err=deflate(&zi->ci.stream, Z_NO_FLUSH); + zi->ci.pos_in_buffered_data += (uInt)(zi->ci.stream.total_out - uTotalOutBefore) ; + + } + else + { + uInt copy_this,i; + if (zi->ci.stream.avail_in < zi->ci.stream.avail_out) + copy_this = zi->ci.stream.avail_in; + else + copy_this = zi->ci.stream.avail_out; + for (i=0;ici.stream.next_out)+i) = + *(((const char*)zi->ci.stream.next_in)+i); + { + zi->ci.stream.avail_in -= copy_this; + zi->ci.stream.avail_out-= copy_this; + zi->ci.stream.next_in+= copy_this; + zi->ci.stream.next_out+= copy_this; + zi->ci.stream.total_in+= copy_this; + zi->ci.stream.total_out+= copy_this; + zi->ci.pos_in_buffered_data += copy_this; + } + } + } + + return err; +} + +extern int ZEXPORT zipCloseFileInZipRaw (file, uncompressed_size, crc32) + zipFile file; + uLong uncompressed_size; + uLong crc32; +{ + zip_internal* zi; + uLong compressed_size; + int err=ZIP_OK; + + if (file == NULL) + return ZIP_PARAMERROR; + zi = (zip_internal*)file; + + if (zi->in_opened_file_inzip == 0) + return ZIP_PARAMERROR; + zi->ci.stream.avail_in = 0; + + if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) + while (err==ZIP_OK) + { + uLong uTotalOutBefore; + if (zi->ci.stream.avail_out == 0) + { + if (zipFlushWriteBuffer(zi) == ZIP_ERRNO) + err = ZIP_ERRNO; + zi->ci.stream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.stream.next_out = zi->ci.buffered_data; + } + uTotalOutBefore = zi->ci.stream.total_out; + err=deflate(&zi->ci.stream, Z_FINISH); + zi->ci.pos_in_buffered_data += (uInt)(zi->ci.stream.total_out - uTotalOutBefore) ; + } + + if (err==Z_STREAM_END) + err=ZIP_OK; /* this is normal */ + + if ((zi->ci.pos_in_buffered_data>0) && (err==ZIP_OK)) + if (zipFlushWriteBuffer(zi)==ZIP_ERRNO) + err = ZIP_ERRNO; + + if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) + { + err=deflateEnd(&zi->ci.stream); + zi->ci.stream_initialised = 0; + } + + if (!zi->ci.raw) + { + crc32 = (uLong)zi->ci.crc32; + uncompressed_size = (uLong)zi->ci.stream.total_in; + } + compressed_size = (uLong)zi->ci.stream.total_out; +# ifndef NOCRYPT + compressed_size += zi->ci.crypt_header_size; +# endif + + ziplocal_putValue_inmemory(zi->ci.central_header+16,crc32,4); /*crc*/ + ziplocal_putValue_inmemory(zi->ci.central_header+20, + compressed_size,4); /*compr size*/ + if (zi->ci.stream.data_type == Z_ASCII) + ziplocal_putValue_inmemory(zi->ci.central_header+36,(uLong)Z_ASCII,2); + ziplocal_putValue_inmemory(zi->ci.central_header+24, + uncompressed_size,4); /*uncompr size*/ + + if (err==ZIP_OK) + err = add_data_in_datablock(&zi->central_dir,zi->ci.central_header, + (uLong)zi->ci.size_centralheader); + free(zi->ci.central_header); + + if (err==ZIP_OK) + { + long cur_pos_inzip = ZTELL(zi->z_filefunc,zi->filestream); + if (ZSEEK(zi->z_filefunc,zi->filestream, + zi->ci.pos_local_header + 14,ZLIB_FILEFUNC_SEEK_SET)!=0) + err = ZIP_ERRNO; + + if (err==ZIP_OK) + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,crc32,4); /* crc 32, unknown */ + + if (err==ZIP_OK) /* compressed size, unknown */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,compressed_size,4); + + if (err==ZIP_OK) /* uncompressed size, unknown */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,uncompressed_size,4); + + if (ZSEEK(zi->z_filefunc,zi->filestream, + cur_pos_inzip,ZLIB_FILEFUNC_SEEK_SET)!=0) + err = ZIP_ERRNO; + } + + zi->number_entry ++; + zi->in_opened_file_inzip = 0; + + return err; +} + +extern int ZEXPORT zipCloseFileInZip (file) + zipFile file; +{ + return zipCloseFileInZipRaw (file,0,0); +} + +extern int ZEXPORT zipClose (file, global_comment) + zipFile file; + const char* global_comment; +{ + zip_internal* zi; + int err = 0; + uLong size_centraldir = 0; + uLong centraldir_pos_inzip; + uInt size_global_comment; + if (file == NULL) + return ZIP_PARAMERROR; + zi = (zip_internal*)file; + + if (zi->in_opened_file_inzip == 1) + { + err = zipCloseFileInZip (file); + } + +#ifndef NO_ADDFILEINEXISTINGZIP + if (global_comment==NULL) + global_comment = zi->globalcomment; +#endif + if (global_comment==NULL) + size_global_comment = 0; + else + size_global_comment = (uInt)strlen(global_comment); + + centraldir_pos_inzip = ZTELL(zi->z_filefunc,zi->filestream); + if (err==ZIP_OK) + { + linkedlist_datablock_internal* ldi = zi->central_dir.first_block ; + while (ldi!=NULL) + { + if ((err==ZIP_OK) && (ldi->filled_in_this_block>0)) + if (ZWRITE(zi->z_filefunc,zi->filestream, + ldi->data,ldi->filled_in_this_block) + !=ldi->filled_in_this_block ) + err = ZIP_ERRNO; + + size_centraldir += ldi->filled_in_this_block; + ldi = ldi->next_datablock; + } + } + free_datablock(zi->central_dir.first_block); + + if (err==ZIP_OK) /* Magic End */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)ENDHEADERMAGIC,4); + + if (err==ZIP_OK) /* number of this disk */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,2); + + if (err==ZIP_OK) /* number of the disk with the start of the central directory */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,2); + + if (err==ZIP_OK) /* total number of entries in the central dir on this disk */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->number_entry,2); + + if (err==ZIP_OK) /* total number of entries in the central dir */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->number_entry,2); + + if (err==ZIP_OK) /* size of the central directory */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_centraldir,4); + + if (err==ZIP_OK) /* offset of start of central directory with respect to the + starting disk number */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream, + (uLong)(centraldir_pos_inzip - zi->add_position_when_writting_offset),4); + + if (err==ZIP_OK) /* zipfile comment length */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_global_comment,2); + + if ((err==ZIP_OK) && (size_global_comment>0)) + if (ZWRITE(zi->z_filefunc,zi->filestream, + global_comment,size_global_comment) != size_global_comment) + err = ZIP_ERRNO; + + if (ZCLOSE(zi->z_filefunc,zi->filestream) != 0) + if (err == ZIP_OK) + err = ZIP_ERRNO; + +#ifndef NO_ADDFILEINEXISTINGZIP + TRYFREE(zi->globalcomment); +#endif + TRYFREE(zi); + + return err; +} diff --git a/Modules/spdlog b/Modules/spdlog new file mode 160000 index 0000000..01b350d --- /dev/null +++ b/Modules/spdlog @@ -0,0 +1 @@ +Subproject commit 01b350de96483cf00b267c6db4c25f3b739b3fee diff --git a/README.md b/README.md index 55e3235..fd09366 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ ReHitman Tools -------------- +![Build Status of main branch](https://ci.appveyor.com/api/projects/status/github/ReGlacier/ReHitmanTools?branch=main&svg=true&retina=true) + This repository contains utilities for work with files of games who was built on Glacier Engine License: **GNU GPLv2** @@ -19,4 +21,13 @@ For build all projects: mkdir build && cd build cmake -A Win32 .. cmake --build . --config Release -``` \ No newline at end of file +``` + +Contact Information. +--------------- + +The team can be contacted in the Project's issue tracker or via Discord (any questions, ideas, etc). + + + + diff --git a/Tools/GMSInfo/CMakeLists.txt b/Tools/GMSInfo/CMakeLists.txt index f175817..b931301 100644 --- a/Tools/GMSInfo/CMakeLists.txt +++ b/Tools/GMSInfo/CMakeLists.txt @@ -7,12 +7,15 @@ if (NOT CMAKE_SIZEOF_VOID_P EQUAL 4) # TODO: Think about how to do it better message(FATAL_ERROR "Supported only x86 arch!") endif() -file(GLOB_RECURSE GMSTOOL_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/source/*.cc ${CMAKE_CURRENT_SOURCE_DIR}/source/*.c) +file(GLOB_RECURSE GMSTOOL_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp) add_executable(HBM_GMSTool ${GMSTOOL_SOURCES}) -target_link_libraries(HBM_GMSTool PRIVATE fmt::fmt nlohmann_json zlibstatic) +target_link_libraries(HBM_GMSTool PRIVATE nlohmann_json zlibstatic minizip CLI11::CLI11 BMFormats::Localization) +target_link_libraries(HBM_GMSTool PUBLIC spdlog) + target_compile_definitions(HBM_GMSTool PRIVATE -D_CRT_SECURE_NO_WARNINGS=1) target_include_directories(HBM_GMSTool PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/../../Modules/zlib # hotfix for zlib ${CMAKE_CURRENT_BINARY_DIR} $/.. # hotfix for zlib (final path contains type of build) diff --git a/Tools/GMSInfo/README.md b/Tools/GMSInfo/README.md index 049617c..16fbfb3 100644 --- a/Tools/GMSInfo/README.md +++ b/Tools/GMSInfo/README.md @@ -3,6 +3,12 @@ GMSInfo This tool was created to parse GMS file format +Required +-------- + * Visual Studio 2019 (or other compiled with C++20) + * CMake 3.16 + * Python 3.9.0 + Build ----- ``` diff --git a/Tools/GMSInfo/data/typeids.json b/Tools/GMSInfo/data/typeids.json index 382192d..68799ed 100644 --- a/Tools/GMSInfo/data/typeids.json +++ b/Tools/GMSInfo/data/typeids.json @@ -88,5 +88,12 @@ "0x10042B": ["ZHM3ItemWeapon","ZItemWeapon"], "0x10042D": ["ZHM3ItemWeaponCustom","ZHM3ItemWeapon"], "0x4000022": ["ZSHAPE","ZGEOM"], - "0x1007D3": ["ZItemTemplate","ZGROUP"] + "0x1007D3": ["ZItemTemplate","ZGROUP"], + "0x100450": ["ZHM3ItemBomb","ZHM3ItemWeapon"], + "0x200449": ["ZHM3Boid","ZIKLNKOBJ"], + "0x20044A": ["ZModifiableCloth","ZSTDOBJ"], + "0x200114": ["ZFadeFullScreen","ZSTDOBJ"], + "0x8000F9": ["ZGateLightSpot","ZSPOTLIGHT"], + "0x2000E2": ["ZParticleBox","ZSTDOBJ"], + "0x2000E8": ["ZCubeGridParticles","ZCubeGrid"] } \ No newline at end of file diff --git a/Tools/GMSInfo/include/ANM/ANM.h b/Tools/GMSInfo/include/ANM/ANM.h new file mode 100644 index 0000000..2633b4a --- /dev/null +++ b/Tools/GMSInfo/include/ANM/ANM.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace ReGlacier +{ + class ANM : public IGameEntity + { + public: + using Ptr = std::unique_ptr; + + ANM(const std::string& name, LevelContainer* levelContainer, LevelAssets* levelAssets); + + bool Load() override; + }; +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/BinaryWalker.h b/Tools/GMSInfo/include/BinaryWalker.h new file mode 100644 index 0000000..6ba20e4 --- /dev/null +++ b/Tools/GMSInfo/include/BinaryWalker.h @@ -0,0 +1,256 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +#include + +namespace ReGlacier +{ + template + concept STLContainer = requires (T t) { + t.size(); + t.begin(); + t.end(); + t.data(); + typename T::value_type; + }; + + class IBaseStreamWalker + { + protected: + size_t m_size { 0 }; + mutable size_t m_offset { 0 }; + + public: + virtual ~IBaseStreamWalker() noexcept = default; + IBaseStreamWalker(size_t size, size_t offset); + + IBaseStreamWalker(const IBaseStreamWalker& copy); + IBaseStreamWalker(IBaseStreamWalker&& move) noexcept; + IBaseStreamWalker& operator=(const IBaseStreamWalker& copy); + IBaseStreamWalker& operator=(IBaseStreamWalker&& move) noexcept; + + enum class SeekType : uint8_t + { + FROM_CURRENT_POSITION, ///< Append offset to current offset + FROM_BEGIN, ///< Override current offset to new value + FROM_END ///< Override current offset to m_size - offset + }; + + static constexpr SeekType BEGIN = SeekType::FROM_BEGIN; + static constexpr SeekType END = SeekType::FROM_END; + static constexpr SeekType CURR = SeekType::FROM_CURRENT_POSITION; + + /** + * Move internal offset to zero, equal to Seek(0, SeekType::FROM_BEGIN) + */ + void Reset(); + + /** + * Move internal offset to passed value with passed rule + * @param offset - new offset + * @param seekType - new offset type + */ + void Seek(size_t offset, SeekType seekType); + + /** + * Write align bytes until current offset not aligned + * @param align + * @param padByte + */ + void Align(size_t align, char padByte); + + /** + * @return current offset + */ + size_t GetPosition() const; + + /** + * @brief Function for control of buffer space requirement for ADL parsers + * @param space how much bytes required for data structure + * @note this function will throw std::runtime_error if not enough space in buffer (current pos + required space >= buffer size) + */ + void RequireSpace(size_t space) const; + + virtual void WriteUInt8(uint8_t value) = 0; + virtual void WriteInt8(int8_t value) = 0; + + virtual void WriteUInt16(uint16_t value) = 0; + virtual void WriteInt16(int16_t value) = 0; + + virtual void WriteUInt32(uint32_t value) = 0; + virtual void WriteInt32(int32_t value) = 0; + + virtual uint8_t ReadUInt8() const = 0; + virtual int8_t ReadInt8() const = 0; + + virtual uint16_t ReadUInt16() const = 0; + virtual int16_t ReadInt16() const = 0; + + virtual uint32_t ReadUInt32() const = 0; + virtual int32_t ReadInt32() const = 0; + }; + + /** + * @class BinaryWalker + * @brief Simple abstraction under "bytes stream" + */ + class BinaryWalker final : public IBaseStreamWalker + { + private: + uint8_t* m_buffer; + + public: + BinaryWalker(uint8_t* buffer, size_t size); + + BinaryWalker(const BinaryWalker& copy) noexcept; + BinaryWalker(BinaryWalker&& move) noexcept; + BinaryWalker& operator=(const BinaryWalker& copy) noexcept; + BinaryWalker& operator=(BinaryWalker&& move) noexcept; + + operator bool() const; + + /** + * Read single value and move caret to sizeof(T) + * @tparam T type of entity (arithmetic types only!) + * @return value + */ + template T Read() const requires std::is_arithmetic_v + { + if constexpr (std::is_same_v) return ReadUInt8(); + else if constexpr (std::is_same_v) return ReadInt8(); + else if constexpr (std::is_same_v) return ReadUInt16(); + else if constexpr (std::is_same_v) return ReadInt16(); + else if constexpr (std::is_same_v) return ReadUInt32(); + else if constexpr (std::is_same_v) return ReadInt32(); + else + { + RequireSpace(sizeof(T)); + + char* ptr = (&((char*)m_buffer)[m_offset]); + + m_offset += sizeof(T); + return *((T*)ptr); + } + } + + template void Write(const T& value) requires std::is_arithmetic_v + { + if constexpr (std::is_same_v) WriteUInt8(value); + else if constexpr (std::is_same_v) WriteInt8(value); + else if constexpr (std::is_same_v) WriteUInt16(value); + else if constexpr (std::is_same_v) WriteInt16(value); + else if constexpr (std::is_same_v) WriteUInt32(value); + else if constexpr (std::is_same_v) WriteInt32(value); + else { + RequireSpace(sizeof(T)); + + char* ptr = (&((char*)m_buffer)[m_offset]); + *(T*)(ptr) = value; + + m_offset += sizeof(T); + } + } + + /** + * Read multiple values of given type into STL container + * @tparam T type of STL container + * @param container reference to STL container with result + */ + template + void ReadArray(T& container) const requires (STLContainer) + { + using IT = typename T::value_type; + + if (!m_buffer || m_offset >= m_size) + throw std::out_of_range { fmt::format("Trying to read {:X} byte from buffer with size is {:X} bytes", m_offset, m_size) }; + + size_t total = container.size(); + for (size_t i = 0; i < total; i++) + { + container[i] = Read(); + } + } + + template + void ReadArray(T* buffer, size_t size) const + { + if (!m_buffer || m_offset + sizeof(T) * size >= m_size) + throw std::out_of_range { "Unable to read buffer. Not enough bytes" }; + + std::memcpy(buffer, m_buffer + (sizeof(T) * size), size); + + m_offset += (sizeof(T) * size); + } + + /** + * Read multiple values of given type into non managed container (C array) + * @tparam T entity type + * @tparam S entities count + * @param container pointer to the storage + */ + template + void ReadArray(T* container) const + { + std::array buffer; + ReadArray(buffer); + std::memcpy(container, buffer.data(), buffer.size()); + } + + template + void WriteArray(const T& container) requires (STLContainer) + { + using IT = typename T::value_type; + + for (const auto& entity : container) + { + Write(entity); + } + } + + template + void WriteArray(const T* container) + { + for (size_t i = 0; i < S; i++) + { + Write(container[i]); + } + } + + template + void WriteArray(const T* buffer, size_t size) + { + if (!m_buffer || m_offset + sizeof(T) * size >= m_size) + throw std::out_of_range { "Unable to read buffer. Not enough bytes" }; + + std::memcpy(m_buffer, buffer + (sizeof(T) * size), size); + + m_offset += (sizeof(T) * size); + } + + // Interface + void WriteUInt8(uint8_t value) override; + void WriteInt8(int8_t value) override; + + void WriteUInt16(uint16_t value) override; + void WriteInt16(int16_t value) override; + + void WriteUInt32(uint32_t value) override; + void WriteInt32(int32_t value) override; + + uint8_t ReadUInt8() const override; + int8_t ReadInt8() const override; + + uint16_t ReadUInt16() const override; + int16_t ReadInt16() const override; + + uint32_t ReadUInt32() const override; + int32_t ReadInt32() const override; + }; +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/BinaryWalkerADL.h b/Tools/GMSInfo/include/BinaryWalkerADL.h new file mode 100644 index 0000000..e07f9cc --- /dev/null +++ b/Tools/GMSInfo/include/BinaryWalkerADL.h @@ -0,0 +1,71 @@ +#pragma once + +#include + +#include +#include + +namespace ReGlacier +{ + template + struct BinaryWalkerADL + { + static void Read(const BinaryWalker& binaryWalker, T& value) {} + static void Write(BinaryWalker& binaryWalker, const T& value) {} + }; + + template <> + struct BinaryWalkerADL + { + static constexpr char kEOS = 0x0; + + static void Read(const BinaryWalker& binaryWalker, std::string& value) + { + char ch; + + while ((ch = binaryWalker.Read()) != 0x0) + { + value.push_back(ch); + } + } + + static void Write(BinaryWalker& binaryWalker, const std::string& value) + { + binaryWalker.WriteArray(value.data(), value.length()); + } + }; + + template + struct BinaryWalkerADL> + { + static void Read(const BinaryWalker& binaryWalker, std::array& array) + { + if constexpr (std::is_trivial_v) + { + binaryWalker.ReadArray(array); + } + else + { + for (auto& item : array) + { + BinaryWalkerADL::Read(binaryWalker, item); + } + } + } + + static void Write(BinaryWalker& binaryWalker, const std::array& array) + { + if constexpr (std::is_trivial_v) + { + binaryWalker.WriteArray(array); + } + else + { + for (const auto& item : array) + { + BinaryWalkerADL::Write(binaryWalker, item); + } + } + } + }; +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/GMS/ADL/GMSADL.h b/Tools/GMSInfo/include/GMS/ADL/GMSADL.h new file mode 100644 index 0000000..4f83229 --- /dev/null +++ b/Tools/GMSInfo/include/GMS/ADL/GMSADL.h @@ -0,0 +1,105 @@ +#pragma once + +#include + +#include +#include + +#include + +namespace ReGlacier +{ + template <> + struct BinaryWalkerADL + { + static void Read(const BinaryWalker& binaryWalker, SGMSUncompressedHeader& value) + { + binaryWalker.RequireSpace(sizeof(SGMSUncompressedHeader)); + + value.TotalEntitiesCountPos = binaryWalker.Read(); + value.Unknown1 = binaryWalker.Read(); + value.Unknown2 = binaryWalker.Read(); + value.Unknown3 = binaryWalker.Read(); + value.DataPos = binaryWalker.Read(); + value.Unknown5 = binaryWalker.Read(); + value.Unknown6 = binaryWalker.Read(); + value.Unknown7 = binaryWalker.Read(); + value.Unknown8 = binaryWalker.Read(); + value.Unknown9 = binaryWalker.Read(); + value.Unknown10 = binaryWalker.Read(); + value.Unknown11 = binaryWalker.Read(); + value.Unknown12 = binaryWalker.Read(); + value.Unknown13 = binaryWalker.Read(); + value.Unknown14 = binaryWalker.Read(); + value.BaseGeomsCount = binaryWalker.Read(); + } + + static void Write(BinaryWalker& binaryWalker, const SGMSUncompressedHeader& value) + { + binaryWalker.Write(value.TotalEntitiesCountPos); + binaryWalker.Write(value.Unknown1); + binaryWalker.Write(value.Unknown2); + binaryWalker.Write(value.Unknown3); + binaryWalker.Write(value.DataPos); + binaryWalker.Write(value.Unknown5); + binaryWalker.Write(value.Unknown6); + binaryWalker.Write(value.Unknown7); + binaryWalker.Write(value.Unknown8); + binaryWalker.Write(value.Unknown9); + binaryWalker.Write(value.Unknown10); + binaryWalker.Write(value.Unknown11); + binaryWalker.Write(value.Unknown12); + binaryWalker.Write(value.Unknown13); + binaryWalker.Write(value.Unknown14); + binaryWalker.Write(value.BaseGeomsCount); + } + }; + + template <> + struct BinaryWalkerADL + { + static void Read(const BinaryWalker& binaryWalker, SGMSEntry& value) + { + binaryWalker.RequireSpace(sizeof(SGMSEntry)); + + value.Unknown1 = binaryWalker.Read(); + value.TypeInfoPos = binaryWalker.Read(); + } + + static void Write(BinaryWalker& binaryWalker, const SGMSEntry& value) + { + binaryWalker.Write(value.Unknown1); + binaryWalker.Write(value.TypeInfoPos); + } + }; + + template <> + struct BinaryWalkerADL + { + static void Read(const BinaryWalker& binaryWalker, SGMSBaseGeom& value) + { + binaryWalker.RequireSpace(sizeof(SGMSBaseGeom)); + + value.PrimitiveId = binaryWalker.Read(); + value.Unknown2 = binaryWalker.Read(); + value.Unknown3 = binaryWalker.Read(); + value.PrimitiveOffset = binaryWalker.Read(); + value.Unknown5 = binaryWalker.Read(); + value.TypeId = static_cast(binaryWalker.Read()); + value.Unknown7 = binaryWalker.Read(); + value.Unknown8 = binaryWalker.Read(); + } + + static void Write(BinaryWalker& binaryWalker, const SGMSBaseGeom& value) + { + binaryWalker.Write(value.PrimitiveId); + binaryWalker.Write(value.Unknown2); + binaryWalker.Write(value.Unknown3); + binaryWalker.Write(value.PrimitiveOffset); + binaryWalker.Write(value.Unknown5); + binaryWalker.Write(static_cast(value.TypeId)); + binaryWalker.Write(value.Unknown7); + binaryWalker.Write(value.Unknown8); + } + }; +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/GMS/GMS.h b/Tools/GMSInfo/include/GMS/GMS.h new file mode 100644 index 0000000..25b36b7 --- /dev/null +++ b/Tools/GMSInfo/include/GMS/GMS.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace ReGlacier +{ + class LevelContainer; + struct LevelAssets; + + class GMS : public IGameEntity + { + std::vector m_excludedAnimationsList; + + int32_t m_totalLinkRefsCount = 0; + std::vector m_linkRefs; + + int32_t m_weaponHandlesCount = 0; + std::vector m_weaponHandles; + public: + using Ptr = std::unique_ptr; + + GMS(std::string name, LevelContainer* levelContainer, LevelAssets* levelAssets); + + bool Load() override; + bool SaveUncompressed(const std::string& filePath); + void PrintInfo(); + + [[nodiscard]] const std::vector& GetExcludedAnimations() const; + [[nodiscard]] const std::vector& GetLinkReferences() const; + private: + bool LoadEntities(std::unique_ptr&& buffer, size_t bufferSize); + bool LoadImportTable(const char* gmsBuffer, size_t bufferSize); + bool LoadProperties(const char* gmsBuffer, size_t bufferSize); + bool LoadExcludedAnimations(char* gmsBuffer, size_t gmsBufferSize, char* bufBuffer, size_t bufBufferSize); + bool LoadWeaponHandles(char* gmsBuffer, size_t gmsBufferSize, char* bufBuffer, size_t bufBufferSize); + + std::unique_ptr GetRawGMS(size_t& bufferSize); + + private: + int32_t m_totalEntities; + + std::vector m_geoms; + }; +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/GMS/GMSTypes.h b/Tools/GMSInfo/include/GMS/GMSTypes.h new file mode 100644 index 0000000..6f4de02 --- /dev/null +++ b/Tools/GMSInfo/include/GMS/GMSTypes.h @@ -0,0 +1,86 @@ +#pragma once + +#include + +#include + +namespace ReGlacier +{ + struct GMSGeomReference + { + union TD_t { + int32_t rawTypeId; + Glacier::TypeId typeId; + } type; + int32_t id; + int32_t unknown; + }; + + struct GMSLinkRef + { + uint32_t index = 0u; + + struct { + uint32_t index = 0; + Glacier::TypeId id = Glacier::NOT_INITIALISED; + } typeInfo; + + GMSLinkRef() = default; + GMSLinkRef(uint32_t _index, uint32_t _typeId, bool isDeclared) + : index(_index) + { + typeInfo.index = _typeId; + typeInfo.id = isDeclared ? static_cast(_typeId) : Glacier::NOT_FOUND; + } + }; + + struct GMSWeaponHandle + { + uint32_t entityId = 0; + uint32_t m_field4 = 0; + uint32_t m_field8 = 0; + + GMSWeaponHandle() = default; + GMSWeaponHandle(uint32_t id, uint32_t u0, uint32_t u1) + : entityId(id), m_field4(u0), m_field8(u1) {} + }; + + /// ------------------------------------------------- + struct SGMSUncompressedHeader + { + uint32_t TotalEntitiesCountPos; //0x0 + uint32_t Unknown1; //0x4 + uint32_t Unknown2; //0x8 + uint32_t Unknown3; //0xC + uint32_t DataPos; //0x10 + uint32_t Unknown5; //0x14 + uint32_t Unknown6; //0x18 + uint32_t Unknown7; //0x1C + uint32_t Unknown8; //0x20 + uint32_t Unknown9; //0x24 + uint32_t Unknown10; //0x28 + uint32_t Unknown11; //0x2C + uint32_t Unknown12; //0x30 + uint32_t Unknown13; //0x34 + uint32_t Unknown14; //0x38 + uint32_t BaseGeomsCount; //0x3C + }; + + struct SGMSEntry + { + uint32_t Unknown1; + uint32_t TypeInfoPos; + }; + + struct SGMSBaseGeom + { + uint32_t PrimitiveId; //+0x0 + uint32_t Unknown2; //+0x4 + uint32_t Unknown3; //+0x8 + uint32_t PrimitiveOffset; //+0xC + uint32_t Unknown5; //+0x10 + Glacier::TypeId TypeId; //+0x14 + uint32_t Unknown7; //+0x16 + uint32_t Unknown8; //+0x18 + }; +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/GameEntityFactory.h b/Tools/GMSInfo/include/GameEntityFactory.h new file mode 100644 index 0000000..eb2e9d4 --- /dev/null +++ b/Tools/GMSInfo/include/GameEntityFactory.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include +#include +#include + +namespace ReGlacier +{ + template + concept IsContext = requires (T t) { + t.Assets; + t.Container; + t.Container.get(); + }; + + class GameEntityFactory + { + public: + template + static std::unique_ptr Create(const std::string& name, Context* context) + requires (IsContext && std::is_base_of_v && + std::is_constructible_v) + { + if (name.empty() || !context || !context->Container) + return nullptr; + + return std::make_unique(name, context->Container.get(), &context->Assets); + } + }; +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/IGameEntity.h b/Tools/GMSInfo/include/IGameEntity.h new file mode 100644 index 0000000..fe135ee --- /dev/null +++ b/Tools/GMSInfo/include/IGameEntity.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include +#include + +namespace ReGlacier +{ + class LevelContainer; + struct LevelAssets; + + class IGameEntity + { + protected: + bool m_isLoaded { false }; + LevelContainer* m_container; + LevelAssets* m_assets; + std::string m_name; + + public: + virtual ~IGameEntity() noexcept = default; + + IGameEntity(const std::string& name, LevelContainer* levelContainer, LevelAssets* levelAssets) + : m_container(levelContainer) + , m_assets(levelAssets) + , m_name(name) + { + } + + virtual bool Load() = 0; + }; +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/LOC/LOC.h b/Tools/GMSInfo/include/LOC/LOC.h new file mode 100644 index 0000000..df4227a --- /dev/null +++ b/Tools/GMSInfo/include/LOC/LOC.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +#include +#include + +namespace ReGlacier +{ + class LOC : public IGameEntity + { + public: + using Ptr = std::unique_ptr; + + LOC(const std::string& name, LevelContainer* levelContainer, LevelAssets* levelAssets); + ~LOC(); + + bool Load() override; + + bool SaveAsJson(std::string_view filePath); + + private: + BM::LOC::LOCTreeNode* m_root { nullptr }; + std::unique_ptr m_currentBuffer { nullptr }; //< We are taking ownership of the LOC buffer. It's required until the tree not serialized into other structs. + size_t m_currentBufferSize { 0 }; + + private: + //void VisitTreeNode(LOCTreeNode* treeNode); + + /** + * @brief Search passed key in contents buffer + * @param key zero terminated key string + * @param buffer LOC file memory mapped buffer + * @return pointer to value or nullptr if value not found + */ + static char* Lookup(char* key, char* buffer); + /** + * @brief Try to lookup for key in buffer, if key exists this function returns true, otherwise false + * @param key + * @param buffer + * @return + */ + static bool HasTextResource(char* key, char* buffer); + }; +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/LevelAssets.h b/Tools/GMSInfo/include/LevelAssets.h new file mode 100644 index 0000000..da523fe --- /dev/null +++ b/Tools/GMSInfo/include/LevelAssets.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +namespace ReGlacier +{ + struct LevelAssets + { + std::string ANM; + std::string BUF; + std::string GMS; + std::string LOC; + std::string MAT; + std::string OCT; + std::string PRP; + std::string PRM; + std::string RMC; + std::string RMI; + std::string SGD; + std::string SGP; + std::string SND; + std::string SUP; + std::string TEX; + std::string ZGF; + + [[nodiscard]] bool AllResolved() const; + void TryResolve(const std::string& path); + }; +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/LevelContainer.h b/Tools/GMSInfo/include/LevelContainer.h new file mode 100644 index 0000000..226636e --- /dev/null +++ b/Tools/GMSInfo/include/LevelContainer.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +namespace ReGlacier +{ + class LevelContainer + { + void* m_zip; + public: + using Ptr = std::unique_ptr; + + explicit LevelContainer(void* zipPtr); + + std::unique_ptr Read(std::string_view path, size_t& bufferSize); + }; +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/LevelDescription.h b/Tools/GMSInfo/include/LevelDescription.h new file mode 100644 index 0000000..7472329 --- /dev/null +++ b/Tools/GMSInfo/include/LevelDescription.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +namespace ReGlacier +{ + class LevelDescription + { + protected: + struct Context; + Context* m_context; + + public: + explicit LevelDescription(const std::string& pathToLevelArchive); + ~LevelDescription(); + + bool Open(); + [[nodiscard]] bool IsMain() const; + void LoadAndAnalyze(); + void PrintInfo(); + void ExportUncompressedGMS(const std::string& path); + bool ExportLocalizationToJson(std::string_view path); + + void SetIgnoreGMSFlag(bool flag); + void SetIgnoreANMFlag(bool flag); + void SetIgnoreLOCFlag(bool flag); + void SetIgnorePRMFlag(bool flag); + void SetIgnorePRPFlag(bool flag); + void SetIgnoreTEXFlag(bool flag); + void SetIgnoreSNDFlag(bool flag); + private: + bool ValidateLevelArchive(); + }; +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/PRM/ADL/PRMADL.h b/Tools/GMSInfo/include/PRM/ADL/PRMADL.h new file mode 100644 index 0000000..755c93c --- /dev/null +++ b/Tools/GMSInfo/include/PRM/ADL/PRMADL.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +#include + +namespace ReGlacier +{ + template <> + struct BinaryWalkerADL + { + static void Read(const BinaryWalker& binaryWalker, PRMHeader& value) + { + value.ChunkPos = binaryWalker.Read(); + value.ChunkNum = binaryWalker.Read(); + value.ChunkPos2 = binaryWalker.Read(); + value.Zero = binaryWalker.Read(); + } + + static void Write(BinaryWalker& binaryWalker, const PRMHeader& value) + { + binaryWalker.Write(value.ChunkPos); + binaryWalker.Write(value.ChunkNum); + binaryWalker.Write(value.ChunkPos2); + binaryWalker.Write(value.Zero); + } + }; + + template <> + struct BinaryWalkerADL + { + static void Read(const BinaryWalker& binaryWalker, PRMChunk& value) + { + value.Pos = binaryWalker.Read(); + value.Size = binaryWalker.Read(); + value.IsGeometry = binaryWalker.Read(); + value.Unknown2 = binaryWalker.Read(); + } + + static void Write(BinaryWalker& binaryWalker, const PRMChunk& value) + { + binaryWalker.Write(value.Pos); + binaryWalker.Write(value.Size); + binaryWalker.Write(value.IsGeometry); + binaryWalker.Write(value.Unknown2); + } + }; +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/PRM/PRM.h b/Tools/GMSInfo/include/PRM/PRM.h new file mode 100644 index 0000000..88548a3 --- /dev/null +++ b/Tools/GMSInfo/include/PRM/PRM.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace ReGlacier +{ + class PRM : public IGameEntity + { + public: + using Ptr = std::unique_ptr; + + PRM(std::string name, LevelContainer* levelContainer, LevelAssets* levelAssets); + + bool Load() override; + }; +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/PRM/PRMTypes.h b/Tools/GMSInfo/include/PRM/PRMTypes.h new file mode 100644 index 0000000..8e41367 --- /dev/null +++ b/Tools/GMSInfo/include/PRM/PRMTypes.h @@ -0,0 +1,26 @@ +#pragma once + +/** + * @credits: John "Cryect" Rittenhouse - Hitman PRM to Obj converter 0.1 (8/12/2006) + */ + +#include + +namespace ReGlacier +{ + struct PRMHeader + { + uint32_t ChunkPos; + uint32_t ChunkNum; + uint32_t ChunkPos2; + uint32_t Zero; + }; + + struct PRMChunk + { + int32_t Pos; + int32_t Size; + int32_t IsGeometry; + int32_t Unknown2; + }; +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/Resources/Texture.h b/Tools/GMSInfo/include/Resources/Texture.h new file mode 100644 index 0000000..93255af --- /dev/null +++ b/Tools/GMSInfo/include/Resources/Texture.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include +#include +#include +#include + +#include + +namespace ReGlacier +{ + enum class TextureFormat + { + DDS, PNG, JPG + }; + + class Texture + { + friend class TEX; + public: + using Ptr = std::shared_ptr; + using Ref = std::weak_ptr; + + Texture() = default; + + [[nodiscard]] int32_t GetWidth() const; + [[nodiscard]] int32_t GetHeight() const; + [[nodiscard]] int32_t GetMip() const; + [[nodiscard]] ETEXEntityType GetEntityType() const; + [[nodiscard]] std::string_view GetName() const; + [[nodiscard]] float GetUnknownFloat2() const; + + protected: + int32_t m_width; + int32_t m_height; + int32_t m_mipLevel; + int32_t m_indicesCount; + float m_unknown2; + ETEXEntityType m_gameTexType; + std::string m_name; + + std::vector m_allocationInfoPool; + std::optional m_PALPaletteData; + std::vector m_indices; + }; +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/Resources/TextureImporter.h b/Tools/GMSInfo/include/Resources/TextureImporter.h new file mode 100644 index 0000000..b4802b7 --- /dev/null +++ b/Tools/GMSInfo/include/Resources/TextureImporter.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace ReGlacier +{ + class TextureImporter + { + public: + static std::unique_ptr&& RecognizeTextureOptions(std::unique_ptr&& buffer, size_t bufferSize, int& width, int& height, bool& recognized); + }; +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/SND/ADL/SNDADL.h b/Tools/GMSInfo/include/SND/ADL/SNDADL.h new file mode 100644 index 0000000..54d5d0e --- /dev/null +++ b/Tools/GMSInfo/include/SND/ADL/SNDADL.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include + +namespace ReGlacier +{ + +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/SND/SND.h b/Tools/GMSInfo/include/SND/SND.h new file mode 100644 index 0000000..cafc23f --- /dev/null +++ b/Tools/GMSInfo/include/SND/SND.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace ReGlacier +{ + class SND : public IGameEntity + { + public: + using Ptr = std::unique_ptr; + + SND(const std::string& name, LevelContainer* levelContainer, LevelAssets* levelAssets); + + bool Load() override; + }; +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/SND/SNDTypes.h b/Tools/GMSInfo/include/SND/SNDTypes.h new file mode 100644 index 0000000..bf3b7a3 --- /dev/null +++ b/Tools/GMSInfo/include/SND/SNDTypes.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +namespace ReGlacier +{ + +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/TEX/ADL/TEXADL.h b/Tools/GMSInfo/include/TEX/ADL/TEXADL.h new file mode 100644 index 0000000..8d286f2 --- /dev/null +++ b/Tools/GMSInfo/include/TEX/ADL/TEXADL.h @@ -0,0 +1,98 @@ +#pragma once + +#include +#include + +#include + +namespace ReGlacier +{ + template <> + struct BinaryWalkerADL + { + static void Read(const BinaryWalker& binaryWalker, STEXHeader& header) + { + header.Table1Location = binaryWalker.Read(); + header.Table2Location = binaryWalker.Read(); + header.RawBufferLocation = binaryWalker.Read(); + header.Unknown1 = binaryWalker.Read(); + } + + static void Write(BinaryWalker& binaryWalker, const STEXHeader& header) + { + binaryWalker.Write(header.Table1Location); + binaryWalker.Write(header.Table2Location); + binaryWalker.Write(header.RawBufferLocation); + binaryWalker.Write(header.Unknown1); + } + }; + + template <> + struct BinaryWalkerADL + { + static void Read(const BinaryWalker& binaryWalker, ETEXEntityType& type) + { + char buff[4]; + binaryWalker.ReadArray(&buff[0]); + type = *(ETEXEntityType*)(&buff[0]); + } + + static void Write(BinaryWalker& binaryWalker, const ETEXEntityType& entry) + { + binaryWalker.WriteArray((char*)&entry); + } + }; + + template <> + struct BinaryWalkerADL + { + static void Read(const BinaryWalker& binaryWalker, STEXEntry& entry) + { + entry.FileSize = binaryWalker.Read(); + BinaryWalkerADL::Read(binaryWalker, entry.Type); + BinaryWalkerADL::Read(binaryWalker, entry.Type2); + entry.Index = binaryWalker.Read(); + entry.Height = binaryWalker.Read(); + entry.Width = binaryWalker.Read(); + entry.MipMapLevels = binaryWalker.Read(); + entry.Unknown1 = binaryWalker.Read(); + entry.Unknown2 = binaryWalker.Read(); + entry.Unknown3 = binaryWalker.Read(); + BinaryWalkerADL::Read(binaryWalker, entry.FileName); + } + + static void Write(BinaryWalker& binaryWalker, const STEXEntry& entry) + { + binaryWalker.Write(entry.FileSize); + BinaryWalkerADL::Write(binaryWalker, entry.Type); + BinaryWalkerADL::Write(binaryWalker, entry.Type2); + binaryWalker.Write(entry.Index); + binaryWalker.Write(entry.Height); + binaryWalker.Write(entry.Width); + binaryWalker.Write(entry.MipMapLevels); + binaryWalker.Write(entry.Unknown1); + binaryWalker.Write(entry.Unknown2); + binaryWalker.Write(entry.Unknown3); + BinaryWalkerADL::Write(binaryWalker, entry.FileName); + } + }; + + template <> + struct BinaryWalkerADL + { + static void Read(const BinaryWalker& binaryWalker, STEXEntityAllocationInfo& entry) + { + entry.MipMapLevelsSize = binaryWalker.Read(); + entry.DataOffsets = binaryWalker.GetPosition(); + entry.Data = std::make_unique(entry.MipMapLevelsSize); + binaryWalker.ReadArray(entry.Data.get(), entry.MipMapLevelsSize); + } + + static void Write(BinaryWalker& binaryWalker, const STEXEntityAllocationInfo& entry) + { + binaryWalker.Write(entry.MipMapLevelsSize); + binaryWalker.Write(entry.DataOffsets); + binaryWalker.WriteArray(entry.Data.get(), entry.MipMapLevelsSize); + } + }; +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/TEX/TEX.h b/Tools/GMSInfo/include/TEX/TEX.h new file mode 100644 index 0000000..abbe3b2 --- /dev/null +++ b/Tools/GMSInfo/include/TEX/TEX.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include + +#include + +namespace ReGlacier +{ + class TEX : public IGameEntity + { + public: + using Ptr = std::unique_ptr; + + TEX(std::string name, LevelContainer* levelContainer, LevelAssets* levelAssets); + + bool Load() override; + + const std::vector& GetLoadedTextures() const; + + private: + std::vector m_textures; + }; + +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/TEX/TEXTypes.h b/Tools/GMSInfo/include/TEX/TEXTypes.h new file mode 100644 index 0000000..b30194b --- /dev/null +++ b/Tools/GMSInfo/include/TEX/TEXTypes.h @@ -0,0 +1,55 @@ +#pragma once + +#include + +namespace ReGlacier +{ + struct STEXHeader + { + uint32_t Table1Location; + uint32_t Table2Location; + uint32_t RawBufferLocation; + uint32_t Unknown1; + }; + + enum ETEXEntityType : unsigned int + { + BITMAP_PAL = 'PALN', + BITMAP_PAL_OPAC = 'PALO', + BITMAP_32 = 'RGBA', + BITMAP_U8V8 = 'U8V8', + BITMAP_DXT1 = 'DXT1', + BITMAP_DXT3 = 'DXT3', + BITMAP_I8 = ' I8', + BITMAP_UNKNOWN = 'UNKN' + }; + + struct STEXEntry + { + uint32_t FileSize; + ETEXEntityType Type; + ETEXEntityType Type2; + int32_t Index; + int16_t Height; + int16_t Width; + int32_t MipMapLevels; + int32_t Unknown1; + float Unknown2; + int32_t Unknown3; + std::string FileName; ///NOTE: Sometimes this field would be empty. I don't know why but most tools ignore that and I'm too. + }; + + struct STEXEntityAllocationInfo + { + int32_t MipMapLevelsSize; + int32_t DataOffsets; + std::unique_ptr Data; + }; + + struct SPALPaletteInfo + { + int32_t Size; + int32_t DataSize; + std::unique_ptr Data; + }; +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/TypesDataBase.h b/Tools/GMSInfo/include/TypesDataBase.h new file mode 100644 index 0000000..bbc45a9 --- /dev/null +++ b/Tools/GMSInfo/include/TypesDataBase.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +namespace ReGlacier +{ + class TypesDataBase + { + private: + std::unordered_map> m_db; + public: + static TypesDataBase& GetInstance(); + static void Release(); + + bool Load(const std::string& path); + + [[nodiscard]] bool HasDefinitionForEntityTypeId(unsigned int entityTypeId) const; + [[nodiscard]] std::string GetEntityTypeById(unsigned int entityTypeId) const; + }; +} \ No newline at end of file diff --git a/Tools/GMSInfo/include/ZPackedDataChunk.h b/Tools/GMSInfo/include/ZPackedDataChunk.h new file mode 100644 index 0000000..fef7e88 --- /dev/null +++ b/Tools/GMSInfo/include/ZPackedDataChunk.h @@ -0,0 +1,23 @@ +#pragma once + +namespace ReGlacier +{ + struct ZPackedDataChunk + { + bool m_bufferWasFreed; ///+0 + bool field_1; ///+1 + bool field_2; //+2 + bool field_3; //+3 + int *m_buffer; ///+4 + int m_bufferSize; ///+8 + int m_uncompressedSize; ///+C + int field_10;///+10 + int m_isUncompressedAlready;///+14 + + // API + ZPackedDataChunk(); + ~ZPackedDataChunk(); + + bool unzip(void* outputBuffer, size_t outputBufferSize); + }; +} \ No newline at end of file diff --git a/Tools/GMSInfo/source/ANM/ANM.cpp b/Tools/GMSInfo/source/ANM/ANM.cpp new file mode 100644 index 0000000..aea350d --- /dev/null +++ b/Tools/GMSInfo/source/ANM/ANM.cpp @@ -0,0 +1,43 @@ +#include + +#include +#include +#include +#include + +#include +#include + +#include + +namespace ReGlacier +{ + constexpr uint32_t kMagicBytes = 0x00414E4D; + + ANM::ANM(const std::string& name, LevelContainer* levelContainer, LevelAssets* levelAssets) + : IGameEntity(name, levelContainer, levelAssets) + {} + + bool ANM::Load() + { + size_t anmBufferSize = 0; + auto anmBuffer = m_container->Read(m_name, anmBufferSize); + + if (!anmBuffer) + { + spdlog::error("ANM::Load| Failed to load ANM file {}", m_name); + return false; + } + BinaryWalker binaryWalker(anmBuffer.get(), anmBufferSize); + + const auto gotMagicBytes = binaryWalker.Read(); + const bool isValidMagicBytes = gotMagicBytes == kMagicBytes; + if (!isValidMagicBytes) + { + spdlog::error("ANM::Load| Bad magic bytes! Got {:X} required {:X}", gotMagicBytes, kMagicBytes); + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/Tools/GMSInfo/source/BinaryWalker.cpp b/Tools/GMSInfo/source/BinaryWalker.cpp new file mode 100644 index 0000000..ab3568e --- /dev/null +++ b/Tools/GMSInfo/source/BinaryWalker.cpp @@ -0,0 +1,265 @@ +#include + +namespace ReGlacier +{ + IBaseStreamWalker::IBaseStreamWalker(size_t size, size_t offset) + : m_size(size) + , m_offset(offset) + {} + + IBaseStreamWalker::IBaseStreamWalker(const IBaseStreamWalker& copy) + { + IBaseStreamWalker::operator=(copy); + } + + IBaseStreamWalker::IBaseStreamWalker(IBaseStreamWalker&& move) noexcept + { + IBaseStreamWalker::operator=(std::move(move)); + } + + IBaseStreamWalker& IBaseStreamWalker::operator=(const IBaseStreamWalker& copy) + { + if (© != this) + { + m_size = copy.m_size; + m_offset = copy.m_offset; + } + return *this; + } + + IBaseStreamWalker& IBaseStreamWalker::operator=(IBaseStreamWalker&& move) noexcept + { + if (&move != this) + { + m_size = move.m_size; move.m_size = 0; + m_offset = move.m_offset; move.m_offset = 0; + } + + return *this; + } + + void IBaseStreamWalker::Reset() + { + m_offset = 0; + } + + void IBaseStreamWalker::Seek(size_t offset, SeekType seekType) + { + switch (seekType) + { + case SeekType::FROM_CURRENT_POSITION: + { + if (m_offset + offset >= m_size) + throw std::out_of_range { "Seek operation failed. Current offset is greater that buffer size" }; + + m_offset += offset; + } + break; + case SeekType::FROM_BEGIN: + { + if (offset >= m_size) + throw std::out_of_range { "Seek operation failed. Current offset is greater that buffer size" }; + + m_offset = offset; + } + break; + case SeekType::FROM_END: + { + if (offset >= m_size) + throw std::out_of_range { "Seek operation failed. Current offset is greater that buffer size" }; + + m_offset = m_size - offset; + } + break; + } + } + + void IBaseStreamWalker::Align(size_t align, char padByte) + { + auto position = GetPosition(); + + if (position % align == 0) + return; + + auto offset = align - position % align; + + for (auto i = 0; i < offset; i++) + { + WriteInt8(padByte); + } + } + + size_t IBaseStreamWalker::GetPosition() const + { + return m_offset; + } + + void IBaseStreamWalker::RequireSpace(size_t space) const + { + if (m_offset + space > m_size) + throw std::runtime_error { fmt::format("Not enough space! Required {} available {} (offset {:X})", space, m_size - m_offset, m_offset) }; + } + + // ------------------------------------------------------------------------------- + + BinaryWalker::BinaryWalker(uint8_t* buffer, size_t size) + : IBaseStreamWalker(size, 0) + , m_buffer(buffer) + {} + + BinaryWalker::BinaryWalker(const BinaryWalker& copy) noexcept + : IBaseStreamWalker(copy) + { + operator=(copy); + } + + BinaryWalker::BinaryWalker(BinaryWalker&& move) noexcept + : IBaseStreamWalker(std::move(move)) + { + operator=(std::move(move)); + } + + BinaryWalker& BinaryWalker::operator=(const BinaryWalker& copy) noexcept + { + if (© != this) + { + IBaseStreamWalker::operator=(copy); + m_buffer = copy.m_buffer; + } + + return *this; + } + + BinaryWalker& BinaryWalker::operator=(BinaryWalker&& move) noexcept + { + if (&move != this) + { + IBaseStreamWalker::operator=(std::move(move)); + m_buffer = move.m_buffer; move.m_buffer = nullptr; + } + + return *this; + } + + BinaryWalker::operator bool() const { return m_buffer != nullptr && m_size > 0; } + + void BinaryWalker::WriteUInt8(uint8_t value) + { + RequireSpace(sizeof(uint8_t)); + + char* ptr = (&((char*)m_buffer)[m_offset]); + *(uint8_t*)(ptr) = value; + + m_offset += sizeof(uint8_t); + } + + void BinaryWalker::WriteInt8(int8_t value) + { + RequireSpace(sizeof(int8_t)); + + char* ptr = (&((char*)m_buffer)[m_offset]); + *(int8_t*)(ptr) = value; + + m_offset += sizeof(int8_t); + } + + void BinaryWalker::WriteUInt16(uint16_t value) + { + RequireSpace(sizeof(uint16_t)); + + char* ptr = (&((char*)m_buffer)[m_offset]); + *(uint16_t*)(ptr) = value; + + m_offset += sizeof(uint16_t); + } + + void BinaryWalker::WriteInt16(int16_t value) + { + RequireSpace(sizeof(int16_t)); + + char* ptr = (&((char*)m_buffer)[m_offset]); + *(int16_t*)(ptr) = value; + + m_offset += sizeof(int16_t); + } + + void BinaryWalker::WriteUInt32(uint32_t value) + { + RequireSpace(sizeof(uint32_t)); + + char* ptr = (&((char*)m_buffer)[m_offset]); + *(uint32_t*)(ptr) = value; + + m_offset += sizeof(uint32_t); + } + + void BinaryWalker::WriteInt32(int32_t value) + { + RequireSpace(sizeof(int32_t)); + + char* ptr = (&((char*)m_buffer)[m_offset]); + *(int32_t*)(ptr) = value; + + m_offset += sizeof(int32_t); + } + + uint8_t BinaryWalker::ReadUInt8() const + { + RequireSpace(sizeof(uint8_t)); + + char* ptr = (&((char*)m_buffer)[m_offset]); + + m_offset += sizeof(uint8_t); + return *((uint8_t*)ptr); + } + + int8_t BinaryWalker::ReadInt8() const + { + RequireSpace(sizeof(int8_t)); + + char* ptr = (&((char*)m_buffer)[m_offset]); + + m_offset += sizeof(int8_t); + return *((int8_t*)ptr); + } + + uint16_t BinaryWalker::ReadUInt16() const + { + RequireSpace(sizeof(uint16_t)); + + char* ptr = (&((char*)m_buffer)[m_offset]); + + m_offset += sizeof(uint16_t); + return *((uint16_t*)ptr); + } + + int16_t BinaryWalker::ReadInt16() const + { + RequireSpace(sizeof(int16_t)); + + char* ptr = (&((char*)m_buffer)[m_offset]); + + m_offset += sizeof(int16_t); + return *((int16_t*)ptr); + } + + uint32_t BinaryWalker::ReadUInt32() const + { + RequireSpace(sizeof(uint32_t)); + + char* ptr = (&((char*)m_buffer)[m_offset]); + + m_offset += sizeof(uint32_t); + return *((uint32_t*)ptr); + } + + int32_t BinaryWalker::ReadInt32() const + { + RequireSpace(sizeof(int32_t)); + + char* ptr = (&((char*)m_buffer)[m_offset]); + + m_offset += sizeof(int32_t); + return *((int32_t*)ptr); + } +} \ No newline at end of file diff --git a/Tools/GMSInfo/source/GMS/GMS.cpp b/Tools/GMSInfo/source/GMS/GMS.cpp new file mode 100644 index 0000000..4515d20 --- /dev/null +++ b/Tools/GMSInfo/source/GMS/GMS.cpp @@ -0,0 +1,419 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include + +extern "C" { +#include +} + +namespace Legacy +{ + struct GMS2 + { + int field_0; + int m_raw; + int field_8; + int field_C; + int field_10; + int field_14; + }; + + struct GMSFileEntry + { + int field_0; + int field_4; + int field_8; + }; + + [[deprecated("This function is an old implementation of IOI decoder. Please, rewrite it to nice C++ api")]] + bool GMS_Decompress(GMS2* gms, uint8_t* gmsDecompressed, int gmsInLength) { + if (gms->field_14 == 1) { + const int length = (gms->field_C >= gmsInLength) ? gms->field_C : gmsInLength; + memcpy(gmsDecompressed, (void*) (gms->m_raw + 9), length); + return true; + } + + auto entry = reinterpret_cast(gms->m_raw); + + z_stream stream; + stream.avail_in = gms->field_8; + stream.next_in = (unsigned char*) &entry->field_8 + 1; + stream.next_out = (Bytef*) gmsDecompressed; + stream.avail_out = gmsInLength; + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + + if (inflateInit2(&stream, -15) != Z_OK) { + spdlog::error("inflateInit2() failed!"); + return false; + } + + int result = inflate(&stream, Z_FINISH); + inflateEnd(&stream); + + if ((result != Z_STREAM_END || result != Z_BUF_ERROR) && !stream.avail_out) { + return false; + } + + return true; + } +} + +namespace ReGlacier +{ + enum GMSOffsets : unsigned int { + ExcludedAnimationsRegionAddr = 0x11, + WeaponHandlesRegionAddr = 0x10 + }; + + GMS::GMS(std::string name, LevelContainer* levelContainer, LevelAssets* levelAssets) + : IGameEntity(name, levelContainer, levelAssets) {} + + bool GMS::Load() + { + assert(!m_isLoaded); + + size_t gmsBufferSize = 0; + auto gmsBuffer = GetRawGMS(gmsBufferSize); + + if (!gmsBuffer) + { + spdlog::error("GMS::Load| Failed to load GMS {}", m_name); + return false; + } + + size_t prmBufferSize = 0; + auto prmBuffer = m_container->Read(m_assets->PRM, prmBufferSize); + if (!prmBuffer) + { + spdlog::error("GMS::Load| Failed to load PRM {}", m_assets->PRM); + return false; + } + + BinaryWalker gmsBinaryWalker(gmsBuffer.get(), gmsBufferSize); + BinaryWalker prmBinaryWalker(prmBuffer.get(), prmBufferSize); + + SGMSUncompressedHeader header {}; + BinaryWalkerADL::Read(gmsBinaryWalker, header); + + gmsBinaryWalker.Seek(header.TotalEntitiesCountPos, BinaryWalker::SeekType::FROM_BEGIN); + m_totalEntities = gmsBinaryWalker.Read(); + spdlog::info("GMS total entities: {}", m_totalEntities); + + m_geoms.reserve(m_totalEntities); + + for (int entryId = 0; entryId < m_totalEntities; ++entryId) + { + gmsBinaryWalker.Seek(header.TotalEntitiesCountPos + (8 * entryId), BinaryWalker::SeekType::FROM_BEGIN); + SGMSEntry entry {}; + BinaryWalkerADL::Read(gmsBinaryWalker, entry); + + gmsBinaryWalker.Seek(4 * (entry.TypeInfoPos & 0xFFFFFF), BinaryWalker::SeekType::FROM_BEGIN); //& 0xFFFFFF IT'S VERY IMPORTANT!!! + + auto& baseGeom = m_geoms.emplace_back(); + BinaryWalkerADL::Read(gmsBinaryWalker, baseGeom); + } + + m_isLoaded = true; + return true; + } + + bool GMS::SaveUncompressed(const std::string& filePath) + { + std::fstream stream(filePath, std::ios::out | std::ios::binary | std::ios::app); + if (!stream) + { + spdlog::error("GMS::SaveUncompressed| Failed to create file {} to write binary", filePath); + return false; + } + + size_t uncompressedBufferSize = 0; + auto buff = GetRawGMS(uncompressedBufferSize); + if (buff) + { + stream.write(reinterpret_cast(buff.get()), uncompressedBufferSize); + stream.flush(); + } + else + { + spdlog::error("GMS::GetRawGMS() failed to extract raw contents"); + return false; + } + stream.close(); + + return true; + } + + void GMS::PrintInfo() { + { + auto& db = TypesDataBase::GetInstance(); + + spdlog::info("GMS Linking table (total {})", m_linkRefs.size()); + for (const auto& linkRef : m_linkRefs) + { + spdlog::info("#{:06d} as {}", linkRef.index, db.GetEntityTypeById(linkRef.typeInfo.index)); + } + } + + { + if (!m_excludedAnimationsList.empty()) + { + spdlog::info("GMS| Excluded animations"); + for (const auto& anim : m_excludedAnimationsList) + { + spdlog::info(" * {}", anim); + } + } + else + { + spdlog::info("GMS| No excluded animations"); + } + } + + { + if (!m_weaponHandles.empty()) + { + spdlog::info("Weapon handles (total {}):", m_weaponHandlesCount); + spdlog::info("------------------------------"); + + spdlog::info("# | ID | Unknown1 | Unknown 2"); + for (int i = 0; i < m_weaponHandles.size(); i++) + { + spdlog::info("{:04d} {:04X} {:08X} {:08X}", i, m_weaponHandles[i].entityId, m_weaponHandles[i].m_field4, m_weaponHandles[i].m_field8); + } + } + else + { + spdlog::info("GMS::LoadWeaponHandles| No weapon handles declared there. Probably, you work with LoaderSequence.GMS"); + } + } + } + + const std::vector & GMS::GetExcludedAnimations() const + { + return m_excludedAnimationsList; + } + + const std::vector& GMS::GetLinkReferences() const + { + return m_linkRefs; + } + + bool GMS::LoadEntities(std::unique_ptr&& gmsBuffer, size_t bufferSize) + { + std::unique_ptr data = std::move(gmsBuffer); + char* buffer = data.get(); + + size_t prmSize = 0, bufSize = 0; + + auto prm = m_container->Read(m_assets->PRM, prmSize); + auto buf = m_container->Read(m_assets->BUF, bufSize); + + if (!prm) + { + spdlog::error("GMS::LoadEntities() Failed to read PRM file {}", m_assets->PRM); + return false; + } + + if (!buf) + { + spdlog::error("GMS::LoadEntities() Failed to read BUF file {}", m_assets->BUF); + return false; + } + + auto BUFBuffer = reinterpret_cast(buf.get()); + + const bool importTablesOk = LoadImportTable(buffer, bufferSize); + const bool propertiesOk = LoadProperties(buffer, bufferSize); + const bool excludedAnimsOk = LoadExcludedAnimations(buffer, bufferSize, BUFBuffer, bufSize); + const bool weaponsHandlesOk = LoadWeaponHandles(buffer, bufferSize, BUFBuffer, bufSize); + + spdlog::info("GMS::LoadEntities() = {} {} {} {}", importTablesOk, propertiesOk, excludedAnimsOk, weaponsHandlesOk); + + return importTablesOk || propertiesOk || excludedAnimsOk || weaponsHandlesOk; + } + + bool GMS::LoadImportTable(const char* buffer, size_t bufferSize) + { + if (*(int*)buffer > bufferSize) { + return false; + } + + m_totalLinkRefsCount = *(int*)&buffer[*(int*)buffer]; + + int entityLocator = 0; + uint32_t entityIndex = 0; + + auto& db = TypesDataBase::GetInstance(); + + m_linkRefs.reserve(m_totalLinkRefsCount); + + //while (true) + do + { + //TODO: Think how to wrap that into the structure + entityLocator = *(int*)(&buffer[8 * entityIndex + 4] + *(int*)buffer) & 0xFFFFFF; + unsigned int entityType = *(int*)(&buffer[4 * entityLocator + 20]); + //const bool isLoaderContents = entityType == Glacier::TypeId::ZLoader_Sequence_Setup_ZSTDOBJ; + + //int ptr = (int)&buffer[4 * entityLocator]; + m_linkRefs.emplace_back(entityIndex, entityType, db.HasDefinitionForEntityTypeId(entityType)); + + ++entityIndex; + } while (entityIndex != m_totalLinkRefsCount); + + //result = &prmBuffer[*((int*)v7 + 3)]; + + return true; + } + + bool GMS::LoadProperties(const char* buffer, size_t bufferSize) + { + return true; + } + + std::vector ParseIOISmartString(const char* string, int awaitEntitiesCount) + { + if (!string || !awaitEntitiesCount) + return {}; + + std::vector result; + // First string not declared + auto caretPointer = string; + do { + int lengthOfString = static_cast(caretPointer[0]); // NOLINT(cert-str34-c) + ++caretPointer; + + result.emplace_back(caretPointer, lengthOfString); + caretPointer += lengthOfString; + --awaitEntitiesCount; + } while (awaitEntitiesCount); + + return result; + } + + bool GMS::LoadExcludedAnimations(char* gmsBuffer, size_t gmsBufferSize, char* bufBuffer, size_t bufBufferSize) + { + auto excludedAnimationsOffset = ((int*)gmsBuffer)[GMSOffsets::ExcludedAnimationsRegionAddr]; + if (excludedAnimationsOffset >= bufBufferSize) + { + return false; + } + + auto totalExcludedAnimations = ((int*)bufBuffer)[excludedAnimationsOffset / sizeof(int)]; + if (excludedAnimationsOffset + 4 >= bufBufferSize) + { + return false; + } + + auto excludedAnimationsBuffer = (char*)&((char*)bufBuffer)[excludedAnimationsOffset + 4]; + auto parsedAnimationsList = ParseIOISmartString(excludedAnimationsBuffer, totalExcludedAnimations); + + parsedAnimationsList.reserve(parsedAnimationsList.size()); + + for (auto&& animName : parsedAnimationsList) + { + m_excludedAnimationsList.emplace_back(animName.data(), animName.length()); + } + + return true; + } + + bool GMS::LoadWeaponHandles(char* gmsBuffer, size_t gmsBufferSize, char* bufBuffer, size_t bufBufferSize) + { + auto weaponHandlesOffset = ((int*)gmsBuffer)[GMSOffsets::WeaponHandlesRegionAddr]; + if (!weaponHandlesOffset) + { + // Note: I don't know how to check it more correctly but if weaponHandlesOffset == 0 we will have bad pointer + return false; + } + + if (weaponHandlesOffset / sizeof(int) >= bufBufferSize) + { + return false; + } + + auto weaponHandlesCountPtr = (int*)&((int*)bufBuffer)[weaponHandlesOffset / sizeof(int)]; + + m_weaponHandlesCount = *weaponHandlesCountPtr; + + auto weaponHandlesLocation = reinterpret_cast(weaponHandlesCountPtr + 1); + + if (!m_weaponHandlesCount) + { + return true; + } + + m_weaponHandles.reserve(m_weaponHandlesCount); + + auto handles = std::make_unique(m_weaponHandlesCount); + std::memcpy(handles.get(), weaponHandlesLocation, sizeof(GMSWeaponHandle) * m_weaponHandlesCount); + + for (int i = 0; i < m_weaponHandlesCount; i++) + { + m_weaponHandles.emplace_back(handles[i].entityId, handles[i].m_field4, handles[i].m_field8); + } + + return true; + } + + std::unique_ptr GMS::GetRawGMS(size_t& outBufferSize) + { + size_t bufferSize = 0; + auto buffer = m_container->Read(m_name, bufferSize); + + if (!buffer) { + spdlog::error("GMS::GetRawGMS() | Unable to read GMS file {}", m_name); + return nullptr; + } + + // Uncompress (legacy, TODO: Refactor!) + auto raw = reinterpret_cast(buffer.get()); + + Legacy::GMS2 gms = { 0 }; + gms.field_0 = 1; + gms.m_raw = (int)raw; + + int v5 = *(int*)raw; + + gms.field_C = v5; + gms.field_8 = *(unsigned int*)(raw + 4); + gms.field_14 = (*(unsigned char*)(raw + 8)) != 0; + + outBufferSize = (v5 + 15) & 0xFFFFFFF0; ///GOT WRONG SIZE + + auto outBuffer = std::make_unique(outBufferSize); + Legacy::GMS_Decompress(&gms, outBuffer.get(), outBufferSize); + + return std::move(outBuffer); + } + + /** + * SND buffer usage note + * + * @ref0 unsigned int __thiscall sub_5A4620(int this, const void *a2, unsigned int a3) + * @ref1 ZDllSound::GetStreamFilename + * @ref2 _DWORD *__thiscall sub_4C82A0(void *this, int a2, int a3) + * @ref3 int __thiscall sub_4C80C0(int this, int a2) + * @ref4 int __thiscall sub_4C80F0(int this, int a2, int a3) + * + * OCT buffer usage notes + * + * @ref0 int __cdecl sub_40DEC0(int a1, int a2, int a3) + * @ref1 int __thiscall ZEngineDataBase::CreateBoundTrees(ZEngineDataBase *this) + */ +} \ No newline at end of file diff --git a/Tools/GMSInfo/source/LOC/LOC.cpp b/Tools/GMSInfo/source/LOC/LOC.cpp new file mode 100644 index 0000000..c906a7d --- /dev/null +++ b/Tools/GMSInfo/source/LOC/LOC.cpp @@ -0,0 +1,201 @@ +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace ReGlacier +{ + LOC::LOC(const std::string& name, LevelContainer* levelContainer, LevelAssets* levelAssets) + : IGameEntity(name, levelContainer, levelAssets) + {} + + LOC::~LOC() + { + if (m_root) + { + delete m_root; + m_root = nullptr; + } + } + + bool LOC::Load() + { + if (m_root) + { + delete m_root; + m_root = nullptr; + } + + size_t locBufferSize = 0; + auto locBuffer = m_container->Read(m_name, locBufferSize); + + if (!locBuffer) + { + spdlog::error("LOC::Load| Failed to read file {}", m_name); + return false; + } + + auto buffer = (char*)locBuffer.get(); + + m_root = BM::LOC::LOCTreeNode::ReadFromMemory(buffer, locBufferSize); + + m_currentBufferSize = locBufferSize; + m_currentBuffer = std::move(locBuffer); + + return true; + } + + bool LOC::SaveAsJson(std::string_view filePath) + { + if (!m_root || m_root->IsEmpty()) + { + spdlog::warn("LOC::SaveAsJson| Nothing to save into file {}", filePath); + return false; + } + + std::fstream file { filePath.data(), std::ios::out | std::ios::trunc }; + if (!file) + { + spdlog::error("LOC::SaveAsJson| Failed to create file {}", filePath); + return false; + } + + nlohmann::json j; + nlohmann::adl_serializer::to_json(j, m_root); + + try { + std::string jsonContents = j.dump(4); + file.write(jsonContents.data(), jsonContents.size()); + file.close(); + } catch (const nlohmann::json::exception&) + { + spdlog::error("Decoder error! Wrong LOC input format or error in LOC-Tree."); + file.close(); + return false; + } + catch (const std::exception& runtimeError) + { + spdlog::error("C++ Runtime error! Message: {}", runtimeError.what()); + file.close(); + return false; + } + + return true; + } + + bool LOC::HasTextResource(char* key, char* buffer) + { + return Lookup(key, buffer) != nullptr; + } + + char* LOC::Lookup(char* key, char* buffer) + { + // This is result of reverse engineering of function at 0x00464FF0 aka ResourceCollection::Lookup + char *keyWithoutLeadingSlash; // edx + char currentChar; // al + char currentCharInKeyWithoutLeadingSlash; // al + size_t newIndex; // ecx + int currentKeyOffset; // ebx + int keyIndex; // ebp + int v8; // edi + int v9; // eax + int v10; // eax + int v11; // esi + int v12; // esi + char *valuePtr; // edx + size_t index; // [esp+10h] [ebp-Ch] + int firstEntityKeyOffset; // [esp+14h] [ebp-8h] + char *pChunkName; // [esp+18h] [ebp-4h] + + while (true) + { + /// KEY NORMALISATION + keyWithoutLeadingSlash = key; + if ( *key == '/' ) /// Search place where our key starts not from / + { + do + currentChar = (keyWithoutLeadingSlash++)[1]; + while (currentChar == '/' ); + key = keyWithoutLeadingSlash; + } + + currentCharInKeyWithoutLeadingSlash = *keyWithoutLeadingSlash; + newIndex = 0; + index = 0; + + if (*keyWithoutLeadingSlash != '/' ) + { + do + { + if ( !currentCharInKeyWithoutLeadingSlash ) // If we have zero terminator -> break + break; + + currentCharInKeyWithoutLeadingSlash = keyWithoutLeadingSlash[newIndex++ + 1]; // save current char and increment newIndex + } + while (currentCharInKeyWithoutLeadingSlash != '/' ); // if our new char not slash -> continue + + index = newIndex; + } + + /// KEY SEARCH AT THE CURRENT BRANCH + currentKeyOffset = (unsigned __int8)*buffer; // First byte - the offset from formula firstEntryLocation(offset) = 4 * offset- 3 + keyIndex = 0; + firstEntityKeyOffset = (unsigned __int8)*buffer; + if (currentKeyOffset <= 0 ) + goto OnOrphanedTreeNodeDetected; + + pChunkName = &buffer[4 * currentKeyOffset - 3]; + do + { + v8 = (currentKeyOffset >> 1) + keyIndex; /// Divide by two (fast method) + keyIndex + if ( v8 ) + v9 = *(int *)&buffer[4 * v8 - 3]; + else + v9 = 0; + if (strnicmp(&pChunkName[v9], keyWithoutLeadingSlash, newIndex) >= 0 ) // if value of first group greater or equal to our key + { + currentKeyOffset >>= 1; // Divide by two + } + else // Group name is less than our key + { + keyIndex = v8 + 1; + currentKeyOffset += -1 - (currentKeyOffset >> 1); + } + + newIndex = index; + keyWithoutLeadingSlash = key; + } + while (currentKeyOffset > 0); + + /// VALUE RESOLVING + currentKeyOffset = firstEntityKeyOffset; //Restore back? o_0 + if ( keyIndex ) + v10 = *(int*)&buffer[4 * keyIndex - 3]; + else + OnOrphanedTreeNodeDetected: + v10 = 0; + v11 = v10 + 4 * currentKeyOffset - 3; + if (keyIndex >= currentKeyOffset || strnicmp(&buffer[v11], keyWithoutLeadingSlash, newIndex) ) + return nullptr; + + /// ITERATION OVER TREE + v12 = index + v11; + valuePtr = &buffer[v12 + 2]; + buffer += v12 + 2; + + if ( !key[index] ) + return valuePtr - 1; + + key += index + 1; + } + } +} \ No newline at end of file diff --git a/Tools/GMSInfo/source/LevelAssets.cpp b/Tools/GMSInfo/source/LevelAssets.cpp new file mode 100644 index 0000000..34cb1a4 --- /dev/null +++ b/Tools/GMSInfo/source/LevelAssets.cpp @@ -0,0 +1,40 @@ +#include +#include + +#include + +namespace ReGlacier +{ + bool LevelAssets::AllResolved() const + { + return + !ANM.empty() && !BUF.empty() && !GMS.empty() && !LOC.empty() && + !MAT.empty() && !OCT.empty() && !PRP.empty() && !PRM.empty() && + !RMC.empty() && !RMI.empty() && !SGD.empty() && !SGP.empty() && + !SND.empty() && !SUP.empty() && !TEX.empty() && !ZGF.empty(); + } + + void LevelAssets::TryResolve(const std::string& path) + { + std::string nameInLowerCase = path; + std::for_each(std::begin(nameInLowerCase), std::end(nameInLowerCase), [](char& c) { c = static_cast(std::tolower(c)); }); + + if (nameInLowerCase.ends_with("anm")) ANM = path; + else if (nameInLowerCase.ends_with("buf")) BUF = path; + else if (nameInLowerCase.ends_with("gms")) GMS = path; + else if (nameInLowerCase.ends_with("loc")) LOC = path; + else if (nameInLowerCase.ends_with("mat")) MAT = path; + else if (nameInLowerCase.ends_with("oct")) OCT = path; + else if (nameInLowerCase.ends_with("prp")) PRP = path; + else if (nameInLowerCase.ends_with("prm")) PRM = path; + else if (nameInLowerCase.ends_with("rmc")) RMC = path; + else if (nameInLowerCase.ends_with("rmi")) RMI = path; + else if (nameInLowerCase.ends_with("sgd")) SGD = path; + else if (nameInLowerCase.ends_with("sgp")) SGP = path; + else if (nameInLowerCase.ends_with("snd")) SND = path; + else if (nameInLowerCase.ends_with("sup")) SUP = path; + else if (nameInLowerCase.ends_with("tex")) TEX = path; + else if (nameInLowerCase.ends_with("zgf")) ZGF = path; + else spdlog::warn("Assets::TryResolve| File {} ignored", path); + } +} \ No newline at end of file diff --git a/Tools/GMSInfo/source/LevelContainer.cpp b/Tools/GMSInfo/source/LevelContainer.cpp new file mode 100644 index 0000000..52251a4 --- /dev/null +++ b/Tools/GMSInfo/source/LevelContainer.cpp @@ -0,0 +1,80 @@ +#include +#include + +extern "C" { +#include +} + +namespace ReGlacier +{ + LevelContainer::LevelContainer(void* zipPtr) : m_zip(zipPtr) + {} + + std::unique_ptr LevelContainer::Read(std::string_view path, size_t& bufferSize) + { + bufferSize = 0; + unzFile zip = m_zip; + + if (unzGoToFirstFile(zip) != UNZ_OK) + { + spdlog::error("Failed to open {} file in level archive", path); + return nullptr; + } + + do { + static constexpr int kMaxFileNameLength = 256; + char fileName[kMaxFileNameLength] = { 0 }; + unz_file_info fileInfo; + + unsigned int ret = unzGetCurrentFileInfo( + zip, + &fileInfo, + fileName, + kMaxFileNameLength, + nullptr,0, nullptr, 0); + if (ret != UNZ_OK) + { + spdlog::error("LevelContainer::Read| unzGetCurrentFileInfo() failed with error code {}", ret); + return nullptr; + } + + if (std::string_view(fileName) == path) + { + ret = unzOpenCurrentFile(zip); + if (ret != UNZ_OK) + { + spdlog::error("LevelContainer::Read| Unable to open file {}. unzOpenCurrentFile() failed with error code {}", path, ret); + return nullptr; + } + + auto buffer = std::make_unique(fileInfo.uncompressed_size); + int readBytes = unzReadCurrentFile(zip, buffer.get(), fileInfo.uncompressed_size); + + if (readBytes < 0) + { + spdlog::error("LevelContainer::Read| unzReadCurrentFile() failed for file {} with error code {}", path, readBytes); + return nullptr; + } + + bufferSize = readBytes; + + if (fileInfo.uncompressed_size != bufferSize) + { + spdlog::warn("LevelContainer::Read| File read operation got wrong buffer size. Await {} got {}", fileInfo.uncompressed_size, bufferSize); + } + + return std::move(buffer); + } + + ret = unzGoToNextFile(zip); + if (ret != UNZ_OK) + { + spdlog::warn("LevelContainer::Read| File {} not found in level archive. unzGoToNextFile() = {}", path, ret); + return nullptr; + } + } + while (true); + + return nullptr; + } +} \ No newline at end of file diff --git a/Tools/GMSInfo/source/LevelDescription.cpp b/Tools/GMSInfo/source/LevelDescription.cpp new file mode 100644 index 0000000..03f2019 --- /dev/null +++ b/Tools/GMSInfo/source/LevelDescription.cpp @@ -0,0 +1,278 @@ +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +extern "C" { +#include +} + +namespace ReGlacier +{ + enum IgnoreFlags : int { + IgnoreGMS = 0, + IgnoreANM = 1, + IgnoreLOC = 2, + IgnorePRM = 3, + IgnorePRP = 4, + IgnoreTEX = 5, + IgnoreSND = 6 + }; + + static constexpr size_t kTotalFlags = 7; + + struct LevelDescription::Context + { + std::string ArchivePath; + LevelAssets Assets; + LevelContainer::Ptr Container; + + ANM::Ptr ANMInstance; + GMS::Ptr GMSInstance; + PRM::Ptr PRMInstance; + TEX::Ptr TEXInstance; + SND::Ptr SNDInstance; + LOC::Ptr LOCInstance; + + std::array Flags {}; + + unzFile Zip { nullptr }; + + Context() + { + Flags[IgnoreFlags::IgnoreGMS] = false; + Flags[IgnoreFlags::IgnoreANM] = false; + Flags[IgnoreFlags::IgnoreLOC] = false; + Flags[IgnoreFlags::IgnorePRM] = false; + Flags[IgnoreFlags::IgnorePRP] = false; + Flags[IgnoreFlags::IgnoreTEX] = false; + Flags[IgnoreFlags::IgnoreSND] = false; + } + + ~Context() + { + if (Zip) + { + unzClose(Zip); + Zip = nullptr; + } + } + }; + + LevelDescription::LevelDescription(const std::string& pathToLevelArchive) + { + m_context = new Context(); + m_context->ArchivePath = pathToLevelArchive; + } + + LevelDescription::~LevelDescription() + { + if (m_context) + { + delete m_context; + m_context = nullptr; + } + } + + bool LevelDescription::IsMain() const + { + return m_context != nullptr && (m_context->ArchivePath.find("_main") != std::string::npos); + } + + bool LevelDescription::Open() + { + if (!m_context) + { + spdlog::error("Unable to open level. Context not inited!"); + return false; + } + + m_context->Zip = unzOpen(m_context->ArchivePath.c_str()); + if (!m_context->Zip) + { + spdlog::error("Unable to open level archive {}", m_context->ArchivePath); + return false; + } + + return ValidateLevelArchive(); + } + + void LevelDescription::LoadAndAnalyze() + { + if (!m_context) + { + spdlog::error("LevelDescription::LoadAndAnalyze| Unable to analyze level. Context not inited!"); + return; + } + + m_context->Container = std::make_unique(m_context->Zip); + + if (!m_context->Flags[IgnoreFlags::IgnoreLOC]) { + m_context->LOCInstance = GameEntityFactory::Create(m_context->Assets.LOC, m_context); + if (!m_context->LOCInstance->Load()) + { + spdlog::error("LevelDescription::Analyze| Failed to load LOC to analyze!"); + } + } else spdlog::info(" * LOC ignored by user"); + + if (!m_context->Flags[IgnoreFlags::IgnoreANM]) { + m_context->ANMInstance = GameEntityFactory::Create(m_context->Assets.ANM, m_context); + if (!m_context->ANMInstance->Load()) { + spdlog::error("LevelDescription::Analyze| Failed to load ANM to analyze!"); + } + } else spdlog::info(" * ANM ignored by user"); + + if (!m_context->Flags[IgnoreFlags::IgnoreSND]) { + m_context->SNDInstance = GameEntityFactory::Create(m_context->Assets.SND, m_context); + if (!m_context->SNDInstance->Load()) { + spdlog::error("LevelDescription::Analyze| Failed to load SND to analyze!"); + } + } else spdlog::info(" * SND ignored by user"); + + if (!m_context->Flags[IgnoreFlags::IgnoreTEX]) { + m_context->TEXInstance = GameEntityFactory::Create(m_context->Assets.TEX, m_context); + if (!m_context->TEXInstance->Load()) { + spdlog::error("LevelDescription::Analyze| Failed to load TEX to analyze!"); + } + } else spdlog::info(" * TEX ignored by user"); + + if (!m_context->Flags[IgnoreFlags::IgnorePRM]) { + m_context->PRMInstance = GameEntityFactory::Create(m_context->Assets.PRM, m_context); + if (!m_context->PRMInstance->Load()) { + spdlog::error("LevelDescription::Analyze| Failed to load PRM to analyze!"); + } + } else spdlog::info(" * PRM ignored by user"); + + if (!m_context->Flags[IgnoreFlags::IgnoreGMS]) { + m_context->GMSInstance = GameEntityFactory::Create(m_context->Assets.GMS, m_context); + if (!m_context->GMSInstance->Load()) { + spdlog::error("LevelDescription::Analyze| Failed to load GMS to analyze!"); + } + } else spdlog::info(" * GMS ignored by user"); + } + + void LevelDescription::PrintInfo() + { + if (m_context) + { + if (m_context->GMSInstance) + { + m_context->GMSInstance->PrintInfo(); + } + } + } + + void LevelDescription::ExportUncompressedGMS(const std::string& path) + { + if (!m_context) + { + spdlog::error("LevelDescription::ExportUncompressedGMS| No available context. Fatal error."); + return; + } + + if (m_context->Flags[IgnoreFlags::IgnoreGMS]) return; + + if (m_context->GMSInstance->SaveUncompressed(path)) + { + spdlog::info("Raw GMS file exported to {}", path); + } else { + spdlog::error("Failed to save raw GMS file to {}. Please, check the log", path); + } + } + + bool LevelDescription::ExportLocalizationToJson(std::string_view path) + { + if (!m_context) + { + spdlog::error("LevelDescription::ExportLocalizationToJson| No available context. Fatal error."); + return false; + } + + if (m_context->Flags[IgnoreFlags::IgnoreLOC]) + { + spdlog::warn("LevelDescription::ExportLocalizationToJson| Unable to export LOC file because it's ignored by user"); + return false; + } + + if (!m_context->LOCInstance) + { + spdlog::error("LevelDescription::ExportLocalizationToJson| Call LoadAndAnalyze() before!"); + return false; + } + + return m_context->LOCInstance->SaveAsJson(path); + } + + void LevelDescription::SetIgnoreGMSFlag(bool flag) { m_context->Flags[IgnoreFlags::IgnoreGMS] = flag; } + void LevelDescription::SetIgnoreANMFlag(bool flag) { m_context->Flags[IgnoreFlags::IgnoreANM] = flag; } + void LevelDescription::SetIgnoreLOCFlag(bool flag) { m_context->Flags[IgnoreFlags::IgnoreLOC] = flag; } + void LevelDescription::SetIgnorePRMFlag(bool flag) { m_context->Flags[IgnoreFlags::IgnorePRM] = flag; } + void LevelDescription::SetIgnorePRPFlag(bool flag) { m_context->Flags[IgnoreFlags::IgnorePRP] = flag; } + void LevelDescription::SetIgnoreTEXFlag(bool flag) { m_context->Flags[IgnoreFlags::IgnoreTEX] = flag; } + void LevelDescription::SetIgnoreSNDFlag(bool flag) { m_context->Flags[IgnoreFlags::IgnoreSND] = flag; } + + bool LevelDescription::ValidateLevelArchive() + { + if (!m_context || !m_context->Zip) + { + spdlog::error("Level validation failed! Wrong call"); + return false; + } + + int ret = unzGoToFirstFile(m_context->Zip); + if (ret != UNZ_OK) + { + spdlog::error("ValidateLevelArchive| unzGoToFirstFile() failed with code {}", ret); + return false; + } + + // We need to locate folder + bool allAssetsExplored = false; + + do { + static constexpr int kMaxFileNameLength = 256; + char fileName[kMaxFileNameLength] = { 0 }; + unz_file_info fileInfo; + + ret = unzGetCurrentFileInfo(m_context->Zip, &fileInfo, fileName, kMaxFileNameLength, nullptr, 0, nullptr, 0); + if (ret != UNZ_OK) + { + spdlog::error("unzGetCurrentFileInfo() failed with error code {}", ret); + return false; + } + + std::string name { fileName }; + m_context->Assets.TryResolve(name); + + // Check + allAssetsExplored = m_context->Assets.AllResolved(); + + if (!allAssetsExplored && unzGoToNextFile(m_context->Zip) != UNZ_OK) + { + spdlog::warn("No more files to analyze. End of archive {}", m_context->ArchivePath); + break; + } + } while (!allAssetsExplored); + + if (allAssetsExplored) + { + spdlog::info("Level structure resolved!"); + } + + unzGoToFirstFile(m_context->Zip); + return true; + } +} \ No newline at end of file diff --git a/Tools/GMSInfo/source/PRM/PRM.cpp b/Tools/GMSInfo/source/PRM/PRM.cpp new file mode 100644 index 0000000..a7d0a80 --- /dev/null +++ b/Tools/GMSInfo/source/PRM/PRM.cpp @@ -0,0 +1,74 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +/** + * @brief + * HWORD - Get high 16 bits of 32 bit number + * LWORD - Get low 16 bits of 32 bit number + */ +#define HWORD(v) (static_cast(((static_cast(v) >> 0x10) & 0xFFFF))) +#define LWORD(v) (static_cast(static_cast(v) & 0xFFFF)) + +namespace ReGlacier +{ + static constexpr size_t kMeshSize = 0x40; + + PRM::PRM(std::string name, LevelContainer* levelContainer, LevelAssets* levelAssets) + : IGameEntity(name, levelContainer, levelAssets) {} + + bool PRM::Load() + { + return true; //Skip for speed + + size_t prmBufferSize = 0; + auto prmBuffer = m_container->Read(m_name, prmBufferSize); + + if (!prmBuffer) + { + spdlog::error("PRM::Load| Failed to load file {}", m_name); + return false; + } + + BinaryWalker binaryWalker(prmBuffer.get(), prmBufferSize); + + PRMHeader header {}; + PRMChunk chunk {}; + + // Read header + BinaryWalkerADL::Read(binaryWalker, header); + + // Read root chunk + binaryWalker.Seek(header.ChunkPos, BinaryWalker::SeekType::FROM_BEGIN); + + spdlog::info("Total chunks: {}, first chunk at +{:X}", header.ChunkNum, header.ChunkPos); + spdlog::info(" # | Pos | Size | Is GEOM | Unknown "); + int chunkId = 0; + do { + BinaryWalkerADL::Read(binaryWalker, chunk); + { + spdlog::info( + "#{:4d} | {:8X} | {:8X} | {:8X} | {:8X}", + chunkId, chunk.Pos, chunk.Size, chunk.IsGeometry, chunk.Unknown2); + + } + ++chunkId; + } while (chunkId < header.ChunkNum); + + spdlog::info("--- end ---"); + return true; + } +} \ No newline at end of file diff --git a/Tools/GMSInfo/source/Resources/Texture.cpp b/Tools/GMSInfo/source/Resources/Texture.cpp new file mode 100644 index 0000000..b42d04a --- /dev/null +++ b/Tools/GMSInfo/source/Resources/Texture.cpp @@ -0,0 +1,16 @@ +#include +#include + +#include + +#include + +namespace ReGlacier +{ + int32_t Texture::GetWidth() const { return m_width; } + int32_t Texture::GetHeight() const { return m_height; } + int32_t Texture::GetMip() const { return m_mipLevel; } + ETEXEntityType Texture::GetEntityType() const { return m_gameTexType; } + std::string_view Texture::GetName() const { return m_name; } + float Texture::GetUnknownFloat2() const { return m_unknown2; } +} \ No newline at end of file diff --git a/Tools/GMSInfo/source/Resources/TextureImporter.cpp b/Tools/GMSInfo/source/Resources/TextureImporter.cpp new file mode 100644 index 0000000..12ea16f --- /dev/null +++ b/Tools/GMSInfo/source/Resources/TextureImporter.cpp @@ -0,0 +1,16 @@ +#include + +#include + +namespace ReGlacier +{ + std::unique_ptr && TextureImporter::RecognizeTextureOptions( + std::unique_ptr&& buffer, + size_t bufferSize, + int& width, + int& height, + bool& recognized) + { + throw std::runtime_error { "NOT IMPLEMENTED" }; + } +} \ No newline at end of file diff --git a/Tools/GMSInfo/source/SND/SND.cpp b/Tools/GMSInfo/source/SND/SND.cpp new file mode 100644 index 0000000..026b6fb --- /dev/null +++ b/Tools/GMSInfo/source/SND/SND.cpp @@ -0,0 +1,29 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +namespace ReGlacier +{ + SND::SND(const std::string& name, LevelContainer* levelContainer, LevelAssets* levelAssets) + : IGameEntity(name, levelContainer, levelAssets) + { + } + + bool SND::Load() + { + return true; + } +} \ No newline at end of file diff --git a/Tools/GMSInfo/source/TEX/TEX.cpp b/Tools/GMSInfo/source/TEX/TEX.cpp new file mode 100644 index 0000000..d1d9038 --- /dev/null +++ b/Tools/GMSInfo/source/TEX/TEX.cpp @@ -0,0 +1,153 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +namespace ReGlacier +{ + static constexpr size_t kOffsetsTableSize = 0x800; + static constexpr size_t kBadOffset = 0x0; + + TEX::TEX(std::string name, LevelContainer* levelContainer, LevelAssets* levelAssets) + : IGameEntity(name, levelContainer, levelAssets) + {} + + bool TEX::Load() + { + m_textures.clear(); + + size_t texBufferSize = 0; + auto texBuffer = m_container->Read(m_name, texBufferSize); + if (!texBuffer) + { + spdlog::error("TEX::Load| Failed to load TEX file {}", m_name); + return false; + } + + BinaryWalker binaryWalker(texBuffer.get(), texBufferSize); + + STEXHeader header {}; + BinaryWalkerADL::Read(binaryWalker, header); + + binaryWalker.Seek(header.Table1Location, BinaryWalker::SeekType::FROM_BEGIN); + + std::array offsetsTable {}; + binaryWalker.ReadArray(offsetsTable); + + int emptyBlocks = 0; + + for (size_t i = 0; i < kOffsetsTableSize; i++) + { + const auto offset = offsetsTable[i]; + + if (offset == kBadOffset) + { + if (m_textures.empty()) + { + ++emptyBlocks; + } + continue;; + } + + binaryWalker.Seek(offset, BinaryWalker::SeekType::FROM_BEGIN); + STEXEntry entry {}; + BinaryWalkerADL::Read(binaryWalker, entry); + + auto texture = std::make_shared(); + texture->m_allocationInfoPool.reserve(entry.MipMapLevels); + texture->m_width = entry.Width; + texture->m_height = entry.Height; + texture->m_mipLevel = entry.MipMapLevels; + texture->m_gameTexType = entry.Type; + texture->m_name = entry.FileName; + texture->m_unknown2 = entry.Unknown2; + + for (size_t j = 0; j < entry.MipMapLevels; j++) + { + auto& allocationInfo = texture->m_allocationInfoPool.emplace_back(); + BinaryWalkerADL::Read(binaryWalker, allocationInfo); + } + + if (entry.Type == ETEXEntityType::BITMAP_PAL) + { + texture->m_PALPaletteData = SPALPaletteInfo(); + auto& value = texture->m_PALPaletteData.value(); + value.Size = binaryWalker.Read(); + value.DataSize = texture->m_PALPaletteData.value().Size * 4; + value.Data = std::make_unique(value.DataSize); + binaryWalker.ReadArray(value.Data.get(), value.DataSize); + } + + m_textures.push_back(texture); + } + + std::array offsetsTable2 {}; + binaryWalker.Seek(header.Table2Location, BinaryWalker::SeekType::FROM_BEGIN); + + for (int i = 0; i < kOffsetsTableSize; i++) + { + offsetsTable2[i] = binaryWalker.Read(); + auto position = binaryWalker.GetPosition(); + + if (offsetsTable2[i] != 0) + { + binaryWalker.Seek(offsetsTable2[i], BinaryWalker::SeekType::FROM_BEGIN); + + auto indicesCount = binaryWalker.Read(); + std::vector indices; + indices.reserve(indicesCount); + + // TODO: Rewrite to ReadArray + for (int j = 0; j < indicesCount; j++) + { + indices.push_back(binaryWalker.Read()); + } + + int index = 0; + + if (indices[indicesCount - 1] == 0) + { + for (int j = indicesCount - 1; j >= 0; j--) + { + if (indices[j] > 0) + { + index = indices[j] - emptyBlocks; + break; + } + } + } else { + index = indices[indicesCount - 1] - emptyBlocks; + } + + assert(index >= 0 && index < m_textures.size()); + + m_textures[index]->m_indicesCount = indicesCount; + m_textures[index]->m_indices.reserve(indicesCount); + + std::copy(std::begin(indices), std::end(indices), std::back_inserter(m_textures[index]->m_indices)); + + binaryWalker.Seek(position, BinaryWalker::SeekType::FROM_BEGIN); + } + } + + spdlog::info("TEX::Load| Total textures in memory: {}", m_textures.size()); + return true; + } + + const std::vector & TEX::GetLoadedTextures() const + { + return m_textures; + } +} \ No newline at end of file diff --git a/Tools/GMSInfo/source/TypesDataBase.cpp b/Tools/GMSInfo/source/TypesDataBase.cpp new file mode 100644 index 0000000..bf087c1 --- /dev/null +++ b/Tools/GMSInfo/source/TypesDataBase.cpp @@ -0,0 +1,71 @@ +#include +#include +#include +#include + +namespace ReGlacier +{ + TypesDataBase* g_typesDataBase { nullptr }; + + TypesDataBase& TypesDataBase::GetInstance() + { + if (!g_typesDataBase) + { + g_typesDataBase = new TypesDataBase(); + } + + return *g_typesDataBase; + } + + void TypesDataBase::Release() + { + if (g_typesDataBase) + { + delete g_typesDataBase; + g_typesDataBase = nullptr; + } + } + + bool TypesDataBase::Load(const std::string& path) + { + m_db.clear(); + + std::ifstream fileStream(path, std::ifstream::binary); + if (!fileStream) { + spdlog::error("Failed to open types data base file {}", path); + return false; + } + + try { + auto db = nlohmann::json::parse(fileStream); + nlohmann::adl_serializer::from_json(db, m_db); + spdlog::info("Type information DB loaded"); + } catch (nlohmann::json::exception& ex) { + spdlog::error("Invalid types data base file {}. Parse error: {}", path, ex.what()); + return false; + } + + fileStream.close(); + + return true; + } + + bool TypesDataBase::HasDefinitionForEntityTypeId(unsigned int entityTypeId) const + { + std::string value = fmt::format("0x{:X}", entityTypeId); + auto iter = m_db.find(value); + return iter != std::end(m_db); + } + + std::string TypesDataBase::GetEntityTypeById(unsigned int entityTypeId) const + { + std::string value = fmt::format("0x{:X}", entityTypeId); + auto iter = m_db.find(value); + if (iter != std::end(m_db)) + { + return fmt::format("{} : {} (0x{:X})", iter->second[0], iter->second[1], entityTypeId); + } + + return fmt::format("NOT FOUND 0x{:X}", entityTypeId); + } +} \ No newline at end of file diff --git a/Tools/GMSInfo/source/ZPackedDataChunk.cpp b/Tools/GMSInfo/source/ZPackedDataChunk.cpp new file mode 100644 index 0000000..2312123 --- /dev/null +++ b/Tools/GMSInfo/source/ZPackedDataChunk.cpp @@ -0,0 +1,81 @@ +#include + +#include +#include + +extern "C" { +#include +} + +namespace ReGlacier +{ + static constexpr int kDataOffset = 0x9; + static constexpr int kZlibWindowBits = -15; + static constexpr int kZlibStreamSize = 0x38; + static constexpr int kZlibFlushValue = 4; + + ZPackedDataChunk::ZPackedDataChunk() + : m_bufferWasFreed(false) + , field_1(false) + , field_2(false) + , field_3(false) + , m_buffer(nullptr) + , m_bufferSize(0) + , m_uncompressedSize(0) + , field_10(0) + , m_isUncompressedAlready(0) + {} + + ZPackedDataChunk::~ZPackedDataChunk() + { + if (m_buffer) + { + if (!m_bufferWasFreed) + { + std::free(m_buffer); + } + + m_buffer = nullptr; + m_bufferSize = 0; + m_uncompressedSize = 0; + m_isUncompressedAlready = 0; + } + } + + bool ZPackedDataChunk::unzip(void* outputBuffer, size_t outputBufferSize) + { + if (m_isUncompressedAlready == 1) + { + auto uncompressedSize = m_uncompressedSize; + if (outputBufferSize <= uncompressedSize) + { + uncompressedSize = outputBufferSize; + } + + std::memcpy(outputBuffer, m_buffer + kDataOffset, uncompressedSize); + return false; + } + + char* buffer = reinterpret_cast(m_buffer); + + z_stream zStream; + zStream.avail_in = m_bufferSize; // how much bytes ready to decompress + zStream.next_in = reinterpret_cast(buffer + 1); // entry buffer (compressed) + zStream.next_out = reinterpret_cast(outputBuffer); // out stream + zStream.avail_out = outputBufferSize; // chunk size (for this - full stream size) + zStream.zalloc = Z_NULL; // custom allocator [nope] + zStream.zfree = Z_NULL; // custom allocator [nope] + + if ( !inflateInit2_(&zStream, kZlibWindowBits, "1.1.3", kZlibStreamSize) )// inflateInit2_ (stream, windowBits, version, stream size) + { + auto streamDecompressionResult = inflate(&zStream, kZlibFlushValue); + inflateEnd(&zStream); + if (streamDecompressionResult == Z_STREAM_END || streamDecompressionResult == Z_BUF_ERROR && !zStream.avail_out) + { + return false; + } + } + + return true; + } +} \ No newline at end of file diff --git a/Tools/GMSInfo/source/main.cpp b/Tools/GMSInfo/source/main.cpp index 6e5687a..132cf8f 100644 --- a/Tools/GMSInfo/source/main.cpp +++ b/Tools/GMSInfo/source/main.cpp @@ -1,268 +1,102 @@ /** GMS Tool for Hitman Blood Money **/ -#include -#include -#include -#include -#include - -#include #include -#include -#include - -extern "C" { -#include "zlib.h" -} - -#include -#include -#include -#define GLACIER_GMS_ZLIB_WBTS -15 - -struct GMS -{ - int field_0; - int m_raw; - int field_8; - int field_C; - int field_10; - int field_14; -}; +#include -struct GMSFileEntry -{ - int field_0; - int field_4; - int field_8; -}; +#include +#include -std::unordered_map> g_typeInfoDB; - -void loadTypesDataBase() -{ - std::ifstream fileStream("typeids.json", std::ifstream::binary); - if (!fileStream) - { - printf("Failed to open typeids.json! All types will be not resolved!\n"); - return; - } - - try - { - auto db = nlohmann::json::parse(fileStream); - nlohmann::adl_serializer::from_json(db, g_typeInfoDB); - printf("Type information DB loaded!\n"); - } catch (nlohmann::json::exception& ex) - { - printf("Bad typeids.json file! JSON Error: %s\n", ex.what()); - throw; - } - - fileStream.close(); -} - -std::string getEntityTypeAsStringIfItPossible(unsigned int typeIndex) -{ - std::string value = fmt::format("0x{:X}", typeIndex); - auto iter = g_typeInfoDB.find(value); - if (iter != std::end(g_typeInfoDB)) - return fmt::format("{} : {} (0x{:X})", iter->second[0], iter->second[1], typeIndex); - return fmt::format("NOT FOUND 0x{:X}", typeIndex); -} - -void analyzeGMS(const char* prmFilePath, char* buffer, size_t size) -{ - FILE* fp = fopen(prmFilePath, "rb"); - if (!fp) - { - printf("Analyze failed! Failed to open PRM file %s\n", prmFilePath); - return; - } - - fseek(fp, 0L, SEEK_END); - auto prmBufferSize = ftell(fp); - rewind(fp); - - char* prmBuffer = (char*)calloc(1, prmBufferSize); - fread(prmBuffer, prmBufferSize, 1, fp); - fclose(fp); - - int totalEntitiesCount = *(int*)&buffer[*(int*)buffer]; - char* result = 0; - int entityIndex = 0; - int v5; - bool v6; - char* v7; - - if (totalEntitiesCount) - { - { -#define G_AT(x) (*(((int*)buffer) + x)) - printf(" +0x10 +0x11 \n"); - printf(" %X %X \n", G_AT(0x10), G_AT(0x11)); -#undef G_AT - } - - printf("GMS Import table\n"); - printf("-----------------\n\n"); - printf("Total entities count: %.8d\n", totalEntitiesCount); - printf("Index | Ref |Pointer | Type ID\n"); - - while (1) - { - v5 = *(int*)(&buffer[8 * entityIndex + 4] + *(int*)buffer) & 0xFFFFFF; - unsigned int entityType = *(int*)(&buffer[4 * v5 + 20]); - const bool isLoaderContents = entityType == Glacier::TypeId::ZLoader_Sequence_Setup_ZSTDOBJ; - - int ptr = (int)&buffer[4 * v5]; - std::string typeInfoStr = getEntityTypeAsStringIfItPossible(entityType); - - printf("#%.4d | %.9X | %.8X | %s\n", entityIndex, v5, ptr, typeInfoStr.c_str()); - - if (isLoaderContents) - { - int addr = (int)&prmBuffer[*((int*)ptr + 3)]; - //TODO: Save XML - } - - if (++entityIndex == totalEntitiesCount) - { - printf("--------------- END OF GMS TABLE ---------------\n"); - break; - } - } - - //result = &prmBuffer[*((int*)v7 + 3)]; - } - - free(prmBuffer); -} - -bool GMS_Decompress(GMS* gms, char* gmsDecompressed, int gmsInLength) -{ - if (gms->field_14 == 1) - { - const int length = (gms->field_C >= gmsInLength) ? gms->field_C : gmsInLength; - memcpy(gmsDecompressed, (void*)(gms->m_raw + 9), length); - return true; - } - - auto entry = reinterpret_cast(gms->m_raw); - - z_stream stream; - stream.avail_in = gms->field_8; - stream.next_in = (unsigned char*)&entry->field_8 + 1; - stream.next_out = (Bytef*)gmsDecompressed; - stream.avail_out = gmsInLength; - stream.zalloc = Z_NULL; - stream.zfree = Z_NULL; - - if (inflateInit2(&stream, -15) != Z_OK) - { - printf("inflateInit2() failed!\n"); - return false; - } - - int result = inflate(&stream, Z_FINISH); - inflateEnd(&stream); - - if ((result != Z_STREAM_END || result != Z_BUF_ERROR) && !stream.avail_out) - { - printf("inflate() failed with error code %d\n", result); - return false; - } - - return true; -} +// CLI11 +#include +#include +#include +static constexpr const char* kDefaultTypeStorageFile = "typeids.json"; int main(int argc, char** argv) { - if (argc < 3) + bool printLevelInfo; + bool ignoreGMS { false }; + bool ignoreANM { false }; + bool ignoreLOC { false }; + bool ignorePRM { false }; + bool ignorePRP { false }; + bool ignoreTEX { false }; + bool ignoreSND { false }; + + std::string levelArchivePath; + std::string typesDataBaseFilePath = kDefaultTypeStorageFile; + std::string uncompressedGMSPath; + std::string exportLocalizationToFilePath; + + CLI::App app { "GMS Tool" }; + + app.add_option("--level", levelArchivePath, "Path to level ZIP")->required(); + app.add_option("--types", typesDataBaseFilePath, "Set types DB JSON file"); + app.add_option("--export-gms", uncompressedGMSPath, "Export uncompressed GMS to specified file"); + app.add_option("--print-info", printLevelInfo, "Dump level info to console"); + app.add_option("--export-loc", exportLocalizationToFilePath, "Export decompiled LOC file into file at specified path"); + app.add_option("--ignore-gms", ignoreGMS, "Ignore .GMS file"); + app.add_option("--ignore-anm", ignoreANM, "Ignore .ANM file"); + app.add_option("--ignore-loc", ignoreLOC, "Ignore .LOC file"); + app.add_option("--ignore-prm", ignorePRM, "Ignore .PRM file"); + app.add_option("--ignore-prp", ignorePRP, "Ignore .PRP file"); + app.add_option("--ignore-tex", ignoreTEX, "Ignore .TEX file"); + app.add_option("--ignore-snd", ignoreSND, "Ignore .SND file"); + CLI11_PARSE(app, argc, argv); + + if (!ReGlacier::TypesDataBase::GetInstance().Load(typesDataBaseFilePath)) { - printf("ERROR: You need to specify input and output files!\n\tUsage: gmstool.exe [Path to new uncompressed GMS]\n"); - return 0; + spdlog::error("Failed to load types database from file {}", typesDataBaseFilePath); + return -2; } - const char* inFile = argv[1]; - const char* prmInFile = argv[2]; - - printf(" *** GMS TOOL ***\n"); - printf(" * IN : %s\n", inFile); - - FILE* fp = fopen(inFile, "rb"); - if (!fp) + // Open level archive + auto level = std::make_unique(levelArchivePath); + if (!level->Open()) { - printf("Failed to open file %s\n", inFile); - return -400; + spdlog::error("Failed to open level {}", levelArchivePath); + return -1; } - fseek(fp, 0L, SEEK_END); - auto rawBufferSize = ftell(fp); - rewind(fp); - - char* rawBuffer = (char*)malloc(rawBufferSize); - auto readyBytes = fread(rawBuffer, sizeof(char), rawBufferSize, fp); - - if (rawBufferSize != readyBytes) { - printf("Failed to read full contents from raw stream! Required %d ready %d\n", rawBufferSize, readyBytes); - fclose(fp); - return -1; + // Setup ignore flags + level->SetIgnoreGMSFlag(ignoreGMS); + level->SetIgnoreANMFlag(ignoreANM); + level->SetIgnoreLOCFlag(ignoreLOC); + level->SetIgnorePRMFlag(ignorePRM); + level->SetIgnorePRPFlag(ignorePRP); + level->SetIgnoreTEXFlag(ignoreTEX); + level->SetIgnoreSNDFlag(ignoreSND); } - fclose(fp); - - // decompress - GMS gms = { 0 }; - gms.field_0 = 1; - gms.m_raw = (int)rawBuffer; - int v5 = *(unsigned long*)rawBuffer; - gms.field_C = v5; - gms.field_8 = *(unsigned long*)(rawBuffer + 4); - gms.field_14 = (*(unsigned char*)(rawBuffer + 8)) != 0; + level->LoadAndAnalyze(); - int outBuffSize = (v5 + 15) & 0xFFFFFFF0; - char* outBuffer = (char*)malloc(outBuffSize); - memset(outBuffer, 0x0, outBuffSize); - GMS_Decompress(&gms, outBuffer, outBuffSize); + if (printLevelInfo) + level->PrintInfo(); - /* - if (argv > 3) { - const char* outFile = argv[3]; - printf(" * OUT : %s\n", outFile); + if (!uncompressedGMSPath.empty()) + level->ExportUncompressedGMS(uncompressedGMSPath); - printf(" *** DUMP ***\n"); - printf(" Decompressed size: 0x%.8X (%d)\n", gms.field_C, gms.field_C); - for (int i = 0; i < outBuffSize; i += 4) - { - printf("%.4X %.2X %.2X %.2X %.2X\n", i, outBuffer[i], outBuffer[i + 1], outBuffer[i + 2], outBuffer[i + 3]); - } - - FILE* outFP = fopen(outFile, "a+"); - if (outFP) + if (!exportLocalizationToFilePath.empty()) + { + if (ignoreLOC) { - fwrite(outBuffer, sizeof(char), outBuffSize, outFP); - fclose(outFP); + spdlog::warn("--export-loc option was ignored because LOC file was excluded from analysis by user"); } else { - printf("Failed to open file %s to write result!\n", outFile); + if (level->ExportLocalizationToJson(exportLocalizationToFilePath)) + { + spdlog::info("Localization exported to file {}", exportLocalizationToFilePath); + } else { + spdlog::error("Failed to export localization contents. More details in log."); + } } - - printf(" --- PARSING DONE ---\n"); } - */ - - loadTypesDataBase(); - analyzeGMS(prmInFile, outBuffer, outBuffSize); - - free(rawBuffer); - free(outBuffer); return 0; } \ No newline at end of file diff --git a/Tools/GMSInfo/utils/decompose.py b/Tools/GMSInfo/utils/decompose.py index 7f76542..601cbfc 100644 --- a/Tools/GMSInfo/utils/decompose.py +++ b/Tools/GMSInfo/utils/decompose.py @@ -1,8 +1,45 @@ +""" + @file decompose.py + @brief This script implements a generator of C++ code for type definition at runtime + @usage python decompose.py [path to JSON with type info] [path to output C++ header file] + @author DronCode + @license MIT +""" import sys import json import datetime +class TypeRow: + def __init__(self, index, name, parent=None): + self._index = index + self._name = name + self._parent = parent + + @property + def PrettyName(self): + if not self._parent: + return "{}".format(self._name) + else: + return "{}_{}".format(self._name, self._parent) + + @property + def Index(self): + return int(self._index, 16) + + @property + def HexIndex(self): + return self._index + + @property + def ClassName(self): + return self._name + + @property + def ParentClassName(self): + return self._parent + + def generate_definitions(input_definitions_file, output_cpp_header_file): with open(input_definitions_file, "r") as source_definitions_file_handler: type_info_file_json = json.load(source_definitions_file_handler) @@ -13,30 +50,34 @@ def generate_definitions(input_definitions_file, output_cpp_header_file): for type_id in type_info_file_json: if not type_info_file_json[type_id][0] in used_keys_set: + class_list.append( + TypeRow(type_id, type_info_file_json[type_id][0], type_info_file_json[type_id][1])) used_keys_set.add(type_info_file_json[type_id][0]) - class_list.append("\t\t{}_{} = {}".format( - type_info_file_json[type_id][0], - type_info_file_json[type_id][1], - type_id)) else: for copy_index in range(1, 100): if not "{}_{}".format(type_info_file_json[type_id][0], copy_index) in used_keys_set: - class_list.append("\t\t{}_{}_{} = {}".format( - type_info_file_json[type_id][0], - type_info_file_json[type_id][1], - copy_index, - type_id)) + new_name = "{}_{}".format(type_info_file_json[type_id][0], copy_index) + class_list.append(TypeRow(type_id, new_name, type_info_file_json[type_id][1])) + used_keys_set.add(new_name) break + # Add not initialised type id of default initialisation in C++ code + class_list.append(TypeRow("std::numeric_limits::max()-1", "NOT_FOUND")) + class_list.append(TypeRow("std::numeric_limits::max()", "NOT_INITIALISED")) + cpp_header_output_file.write("/*\n") cpp_header_output_file.write(" THIS IS AUTOGENERATED FILE! DO NOT EDIT!\n") cpp_header_output_file.write(" Generated by decompose.py at {}\n*/\n\n".format(datetime.datetime.now())) cpp_header_output_file.write("#ifndef __GLACIER_TYPE_IDS_H__\n") - cpp_header_output_file.write("#define __GLACIER_TYPE_IDS_H__\n\n") + cpp_header_output_file.write("#define __GLACIER_TYPE_IDS_H__\n") + cpp_header_output_file.write("#include \n\n") cpp_header_output_file.write("namespace Glacier {\n") + # Generate enum cpp_header_output_file.write("\tenum TypeId : unsigned int {\n") - cpp_header_output_file.write(',\n'.join(class_list)) - cpp_header_output_file.write("\n\t};\n") + get_type_name = lambda t: "\t\t{} = {}".format(t.PrettyName, t.HexIndex) + cpp_header_output_file.write(',\n'.join(map(get_type_name, class_list))) + # Print footer + cpp_header_output_file.write("\n\t};\n\n") cpp_header_output_file.write("}\n") cpp_header_output_file.write("#endif") cpp_header_output_file.close() diff --git a/Tools/LOCC/CMakeLists.txt b/Tools/LOCC/CMakeLists.txt new file mode 100644 index 0000000..741ba7c --- /dev/null +++ b/Tools/LOCC/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.16) +project(LOCC) + +set(CMAKE_CXX_STANDARD 20) + +if (NOT CMAKE_SIZEOF_VOID_P EQUAL 4) # TODO: Think about how to do it better + message(FATAL_ERROR "Supported only x86 arch!") +endif() + +file(GLOB_RECURSE LOCC_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp) +add_executable(LOCC ${LOCC_SOURCES}) + +target_link_libraries(LOCC PRIVATE nlohmann_json zlibstatic CLI11::CLI11 minizip BMFormats::Localization) +target_link_libraries(LOCC PUBLIC spdlog) + +target_compile_definitions(LOCC PRIVATE -D_CRT_SECURE_NO_WARNINGS=1) +target_include_directories(LOCC PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../Modules/zlib # hotfix for zlib + ${CMAKE_CURRENT_BINARY_DIR} + $/.. # hotfix for zlib (final path contains type of build) + $) \ No newline at end of file diff --git a/Tools/LOCC/README.md b/Tools/LOCC/README.md new file mode 100644 index 0000000..f5b2fc6 --- /dev/null +++ b/Tools/LOCC/README.md @@ -0,0 +1,38 @@ +LOCC - LOC Compiler & Decompiler +================================ + +Localization compiler & decompiler for Hitman Blood Money + +Usage +===== + +Decompile: +---------- + +``` +LOCC.exe --from=M13_main.LOC --to=rel/M13_main.JSON --mode=decompile --pretty-json=on +``` + +Compile: +-------- + +``` +LOCC.exe --from=rel/M13_main.JSON --to=rel/M13_main.LOC +``` + +Options: +======== + + * `--f`, `--from` - Path to source file + * `--t`, `--to` - Path to destination file + * `--m`, `--mode` - Specify tool mode. Allowed values: + * `compile` - Use tool as compiler. `--from` must be path to **LOC** file. `--to` must be path to JSON + * `decompile` - Use tool as decompiler. ``--from` must be path to **JSON** file. `--to` must be path to LOC + * `--g`, `--game` - Specify game name. Allowed values: + * `bloodmoney` - Hitman Blood Money LOC format - **supported** + * `contracts` - Hitman Contracts LOC format - **in progress** + * `2sa` - Hitman 2 Silent Assassin - **queued** + * `a47` - Hitman Agent 47 - **queued** + * `--p`, `--pretty`, `--pretty-json` - Specify JSON pretty printing. Allowed values: + * `on` - Enable JSON pretty printing + * `off` - Disable JSON pretty printing (default) \ No newline at end of file diff --git a/Tools/LOCC/include/Compiler.h b/Tools/LOCC/include/Compiler.h new file mode 100644 index 0000000..ca6a384 --- /dev/null +++ b/Tools/LOCC/include/Compiler.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include + +namespace LOCC +{ + ToolExitCodes Compile(std::string_view from, std::string_view to); +} \ No newline at end of file diff --git a/Tools/LOCC/include/CompilerOptionsStorage.h b/Tools/LOCC/include/CompilerOptionsStorage.h new file mode 100644 index 0000000..ae4326a --- /dev/null +++ b/Tools/LOCC/include/CompilerOptionsStorage.h @@ -0,0 +1,25 @@ +#include + +#include + +namespace LOCC +{ + enum class UtilityMode : int { Compiler, Decompiler }; + + struct CompilerOptionsStorage + { + struct Consts + { + static const std::map ModesMap; + static const std::map SupportModesMap; + }; + + std::string From; + std::string To; + UtilityMode ToolMode { UtilityMode::Compiler }; + BM::LOC::LOCSupportMode SupportMode { BM::LOC::LOCSupportMode::Generic }; + bool PrettifyOutputJson { false }; + }; + + extern CompilerOptionsStorage CompilerOptions; +} \ No newline at end of file diff --git a/Tools/LOCC/include/Decompiler.h b/Tools/LOCC/include/Decompiler.h new file mode 100644 index 0000000..a336b4b --- /dev/null +++ b/Tools/LOCC/include/Decompiler.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include + +namespace LOCC +{ + ToolExitCodes Decompile(std::string_view from, std::string_view to); +} \ No newline at end of file diff --git a/Tools/LOCC/include/FIO.h b/Tools/LOCC/include/FIO.h new file mode 100644 index 0000000..1a4734b --- /dev/null +++ b/Tools/LOCC/include/FIO.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace LOCC +{ + struct FIO + { + static bool HasFile(std::string_view path); + + static std::unique_ptr ReadFile(std::string_view path, size_t& bufferSize); + static nlohmann::json ReadJson(std::string_view path); + + static bool WriteFile(std::string_view path, const char* buffer, size_t bufferSize); + static bool WriteFile(std::string_view path, const nlohmann::json& json, int indent = -1); + }; +} \ No newline at end of file diff --git a/Tools/LOCC/include/LOCC.h b/Tools/LOCC/include/LOCC.h new file mode 100644 index 0000000..9c05536 --- /dev/null +++ b/Tools/LOCC/include/LOCC.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include +#include \ No newline at end of file diff --git a/Tools/LOCC/include/ResourceCollection.h b/Tools/LOCC/include/ResourceCollection.h new file mode 100644 index 0000000..314a096 --- /dev/null +++ b/Tools/LOCC/include/ResourceCollection.h @@ -0,0 +1,10 @@ +#pragma once + +namespace LOCC +{ + struct ResourceCollection + { + public: + static char* Lookup(char* key, char* buffer); + }; +} \ No newline at end of file diff --git a/Tools/LOCC/include/ToolExitCodes.h b/Tools/LOCC/include/ToolExitCodes.h new file mode 100644 index 0000000..8f07760 --- /dev/null +++ b/Tools/LOCC/include/ToolExitCodes.h @@ -0,0 +1,18 @@ +#pragma once + +namespace LOCC +{ + enum ToolExitCodes : int + { + Success = 0, + + BadSourceFile = -10, + BadSourceFormat = -11, + FailedToSerialize = -12, + FailedToSaveSerializedResult = -13, + FailedToLoadJson = -14, + CompileError = -15, + UnknownCompileError = -16, + FailedToSaveCompiledResult = -17 + }; +} \ No newline at end of file diff --git a/Tools/LOCC/source/Compiler.cpp b/Tools/LOCC/source/Compiler.cpp new file mode 100644 index 0000000..2b14d24 --- /dev/null +++ b/Tools/LOCC/source/Compiler.cpp @@ -0,0 +1,72 @@ +#include +#include +#include + +#include +#include +#include +#include + +using namespace BM::LOC; + +LOCC::ToolExitCodes LOCC::Compile(std::string_view from, std::string_view to) +{ + spdlog::info("LOCC::Compile| Loading source JSON from {}", from); + nlohmann::json sourceJson = FIO::ReadJson(from); + if (sourceJson.empty()) + { + spdlog::error("LOCC::Compile| Failed to load source JSON from file {}", from); + return LOCC::ToolExitCodes::FailedToLoadJson; + } + + spdlog::info("LOCC::Compile| Loaded! Trying to deserialize tree ..."); + auto root = LOCTreeFactory::Create(); + try + { + nlohmann::adl_serializer::from_json(sourceJson, root); + } + catch (const nlohmann::json::exception& badJsonEx) + { + spdlog::error("LOCC::Compile| Failed to deserialize source json {}. Error: {}", from, badJsonEx.what()); + delete root; + return LOCC::ToolExitCodes::BadSourceFormat; + } + catch (const std::exception& badDataEx) + { + spdlog::error("LOCC::Compile| Failed to deserialize source json {}. STL Error: {}", from, badDataEx.what()); + delete root; + return LOCC::ToolExitCodes::BadSourceFormat; + } + + spdlog::info("LOCC::Compile| Done! Trying to compile final LOC ..."); + LOCTreeCompiler::Buffer compiledBuffer {}; + bool compileResult = false; + try + { + compileResult = LOCTreeCompiler::Compile(compiledBuffer, root, CompilerOptions.SupportMode); + } + catch (const std::exception& compilerEx) + { + spdlog::error("LOCC::Compile| Compiler error! Message: {}", compilerEx.what()); + delete root; + return LOCC::ToolExitCodes::CompileError; + } + + delete root; + + if (!compileResult) + { + spdlog::error("LOCC::Compile| Compile failed. See log for details"); + return LOCC::ToolExitCodes::UnknownCompileError; + } + + spdlog::info("LOCC::Compile| Compiled! Trying save to file {}", to); + if (!FIO::WriteFile(to, (char*)compiledBuffer.data(), compiledBuffer.size())) + { + spdlog::error("LOCC::Compile| Failed to save output file to {}", to); + return LOCC::ToolExitCodes::FailedToSaveCompiledResult; + } + + spdlog::info("LOCC::Compile| Done! File {} compiled and saved to {}", from, to); + return LOCC::ToolExitCodes::Success; +} \ No newline at end of file diff --git a/Tools/LOCC/source/CompilerOptionsStorage.cpp b/Tools/LOCC/source/CompilerOptionsStorage.cpp new file mode 100644 index 0000000..cda2d73 --- /dev/null +++ b/Tools/LOCC/source/CompilerOptionsStorage.cpp @@ -0,0 +1,18 @@ +#include + +namespace LOCC +{ + const std::map CompilerOptionsStorage::Consts::ModesMap = { + {"compile", UtilityMode::Compiler}, + {"decompile", UtilityMode::Decompiler} + }; + + const std::map CompilerOptionsStorage::Consts::SupportModesMap = { + {"bloodmoney", BM::LOC::LOCSupportMode::Hitman_BloodMoney}, + {"contracts", BM::LOC::LOCSupportMode::Hitman_Contracts}, + {"2sa", BM::LOC::LOCSupportMode::Hitman_2SA}, + {"a47", BM::LOC::LOCSupportMode::Hitman_A47} + }; + + CompilerOptionsStorage CompilerOptions {}; +} \ No newline at end of file diff --git a/Tools/LOCC/source/Decompiler.cpp b/Tools/LOCC/source/Decompiler.cpp new file mode 100644 index 0000000..de6ab0e --- /dev/null +++ b/Tools/LOCC/source/Decompiler.cpp @@ -0,0 +1,62 @@ +#include +#include +#include + +#include +#include +#include +#include + +using namespace BM::LOC; + +static constexpr int kPrettifyJsonIndentValue = 4; +static constexpr int kNoPrettifyJsonIndentValue = -1; + +LOCC::ToolExitCodes LOCC::Decompile(std::string_view from, std::string_view to) +{ + spdlog::info("LOCC::Decompile| Decompiling {} to {} ...", from, to); + + size_t sourceBufferSize = 0; + auto sourceBuffer = FIO::ReadFile(from, sourceBufferSize); + if (!sourceBuffer) + { + spdlog::error("LOCC::Decompile| Failed to read source file {}", from); + return LOCC::ToolExitCodes::BadSourceFile; + } + + auto root = LOCTreeNode::ReadFromMemory(sourceBuffer.get(), sourceBufferSize, CompilerOptions.SupportMode); + if (!root) + { + spdlog::error("LOCC::Decompile| Failed to decompile tree. Probably, you forgot to specify the game?"); + return LOCC::ToolExitCodes::BadSourceFormat; + } + + spdlog::info("LOCC::Decompile| Decompiled! Serializing to JSON ..."); + + nlohmann::json outJson; + + try + { + nlohmann::adl_serializer::to_json(outJson, root); + } + catch (const nlohmann::json::exception& jsonSerErr) + { + delete root; + spdlog::error("LOCC::Decompile| Failed to serialize tree into JSON format. Reason: {}", jsonSerErr.what()); + return LOCC::ToolExitCodes::FailedToSerialize; + } + + delete root; + + spdlog::info("LOCC::Decompile| Serialized! Saving to disk ..."); + + if (!FIO::WriteFile(to, outJson, CompilerOptions.PrettifyOutputJson ? kPrettifyJsonIndentValue : kNoPrettifyJsonIndentValue)) + { + spdlog::error("LOCC::Decompile| Failed to save serialized json to file {}!", to); + return LOCC::ToolExitCodes::FailedToSaveSerializedResult; + } + + spdlog::info("LOCC::Decompile| Source LOC {} decompiled to {} JSON successfully!", from, to); + + return LOCC::ToolExitCodes::Success; +} \ No newline at end of file diff --git a/Tools/LOCC/source/FIO.cpp b/Tools/LOCC/source/FIO.cpp new file mode 100644 index 0000000..35e4aaa --- /dev/null +++ b/Tools/LOCC/source/FIO.cpp @@ -0,0 +1,101 @@ +#include + +namespace LOCC +{ + bool FIO::HasFile(std::string_view path) + { + FILE* fp = fopen(path.data(), "rb"); + if (!fp) return false; + fclose(fp); + return true; + } + + std::unique_ptr FIO::ReadFile(std::string_view path, size_t& bufferSize) + { + std::unique_ptr result = nullptr; + + FILE* fp = fopen(path.data(), "rb"); + if (!fp) + { + spdlog::error("FIO::ReadFile| Failed to open file {} to read binary contents!", path); + return nullptr; + } + + // Recognize the size + fseek(fp, 0L, SEEK_END); + bufferSize = ftell(fp); + rewind(fp); + + if (bufferSize == 0) + { + spdlog::warn("FIO::ReadFile| File {} is empty. Nothing to return/allocate", path); + return nullptr; // Empty file - no buffer + } + + result = std::make_unique(bufferSize); + auto readBytes = fread(result.get(), sizeof(char), bufferSize, fp); + fclose(fp); + + if (readBytes != bufferSize) + { + spdlog::error("FIO::ReadFile| Failed to read file {}. Requested {} bytes, got {} bytes {}", path, bufferSize, readBytes); + bufferSize = 0; + return nullptr; + } + + return result; + } + + nlohmann::json FIO::ReadJson(std::string_view path) + { + std::ifstream file { path.data(), std::ifstream::in }; + if (!file.good()) + { + spdlog::error("FIO::ReadJson| Failed to read file {}", path); + return nlohmann::json {}; + } + + try + { + nlohmann::json j; + file >> j; + file.close(); + return j; + }catch (const nlohmann::json::exception& ex) + { + spdlog::error("FIO::ReadJson| Failed to parse json from file {}. Reason: {}", path, ex.what()); + } + catch (const std::exception& STDERR) + { + spdlog::error("FIO::ReadJson| Failed to parse json from file {}. STD Reason: {}", path, STDERR.what()); + } + + return nlohmann::json {}; + } + + bool FIO::WriteFile(std::string_view path, const char* buffer, size_t bufferSize) + { + FILE* fp = fopen(path.data(), "wb"); + if (!fp) { + spdlog::error("FIO::WriteFile| Failed to open file {} to write binary contents", path); + return false; + } + + auto writtenBytes = fwrite(buffer, sizeof(char), bufferSize, fp); + fclose(fp); + + if (writtenBytes != bufferSize) + { + spdlog::error("FIO::WriteFile| Failed to write buffer of size {} into file {}. Actually written {} bytes", path, bufferSize, writtenBytes); + return false; + } + + return true; + } + + bool FIO::WriteFile(std::string_view path, const nlohmann::json& json, int indent) + { + std::string buffer = json.dump(indent); + return FIO::WriteFile(path, buffer.data(), buffer.size()); + } +} \ No newline at end of file diff --git a/Tools/LOCC/source/LOCC.cpp b/Tools/LOCC/source/LOCC.cpp new file mode 100644 index 0000000..41776e0 --- /dev/null +++ b/Tools/LOCC/source/LOCC.cpp @@ -0,0 +1,59 @@ +/** + * LOC File Compiler + */ +// --- LOC Compiler headers +#include +#include +#include +#include +#include +#include +#include + +// --- CLI11 lib +#include +#include +#include + +// --- BloodMoney LOC Format support engine +#include +#include +#include + +int main(int argc, char** argv) +{ + CLI::App app { "LOC Compiler Utility" }; + + app.add_option("--f,--from", LOCC::CompilerOptions.From, "Path to file who will be compiled/decompiled")->required(); + app.add_option("--t,--to", LOCC::CompilerOptions.To, "Path to file who will be the result of compilation/decompilation")->required(); + app.add_option("--p,--pretty,--pretty-json", LOCC::CompilerOptions.PrettifyOutputJson, "Make result JSON more human-readable"); + app.add_option("--m,--mode", LOCC::CompilerOptions.ToolMode, "Tool mode") + ->transform(CLI::CheckedTransformer(LOCC::CompilerOptionsStorage::Consts::ModesMap, CLI::ignore_case)); + app.add_option("--g,--game", LOCC::CompilerOptions.SupportMode, "Support mode") + ->transform(CLI::CheckedTransformer(LOCC::CompilerOptionsStorage::Consts::SupportModesMap, CLI::ignore_case)); + + CLI11_PARSE(app, argc, argv); + + if (!LOCC::FIO::HasFile(LOCC::CompilerOptions.From)) + { + spdlog::error("LOCC| Source file {} not found!", LOCC::CompilerOptions.From); + return -1; + } + + LOCC::ToolExitCodes exitCode = LOCC::ToolExitCodes::Success; + + switch (LOCC::CompilerOptions.ToolMode) + { + case LOCC::UtilityMode::Compiler: + exitCode = LOCC::Compile(LOCC::CompilerOptions.From, LOCC::CompilerOptions.To); + break; + case LOCC::UtilityMode::Decompiler: + exitCode = LOCC::Decompile(LOCC::CompilerOptions.From, LOCC::CompilerOptions.To); + break; + default: + spdlog::warn("LOCC| No command..."); + break; + } + + return exitCode; +} \ No newline at end of file diff --git a/Tools/LOCC/source/ResourceCollection.cpp b/Tools/LOCC/source/ResourceCollection.cpp new file mode 100644 index 0000000..32bd7b9 --- /dev/null +++ b/Tools/LOCC/source/ResourceCollection.cpp @@ -0,0 +1,116 @@ +#include + +#include + +namespace LOCC +{ + char* ResourceCollection::Lookup(char* key, char* buffer) + { + // This is result of reverse engineering of function at 0x00464FF0 aka ResourceCollection::Lookup + char *keyWithoutLeadingSlash; // edx + char currentChar; // al + char currentCharInKeyWithoutLeadingSlash; // al + size_t newIndex; // ecx + int numChild; // ebx + int keyIndex; // ebp + int v8; // edi + int v9; // eax + int v10; // eax + int v11; // esi + int v12; // esi + char *valuePtr; // edx + size_t index; // [esp+10h] [ebp-Ch] + int numChildOrg; // [esp+14h] [ebp-8h] + char *pChunkName; // [esp+18h] [ebp-4h] + + while (true) + { + /// KEY NORMALISATION + keyWithoutLeadingSlash = key; + if ( *key == '/' ) /// Search place where our key starts not from / + { + do + currentChar = (keyWithoutLeadingSlash++)[1]; + while (currentChar == '/' ); + key = keyWithoutLeadingSlash; + } + + currentCharInKeyWithoutLeadingSlash = *keyWithoutLeadingSlash; + newIndex = 0; + index = 0; + + if (*keyWithoutLeadingSlash != '/' ) + { + do + { + if ( !currentCharInKeyWithoutLeadingSlash ) // If we have zero terminator -> break + break; + + currentCharInKeyWithoutLeadingSlash = keyWithoutLeadingSlash[newIndex++ + 1]; // save current char and increment newIndex + } + while (currentCharInKeyWithoutLeadingSlash != '/' ); // if our new char not slash -> continue + + index = newIndex; + } + + /// KEY SEARCH AT THE CURRENT BRANCH + numChild = (unsigned __int8)*buffer; + keyIndex = 0; + numChildOrg = (unsigned __int8)*buffer; + if (numChild <= 0 ) + goto OnOrphanedTreeNodeDetected; + + pChunkName = &buffer[4 * numChild - 3]; + do + { + v8 = (numChild >> 1) + keyIndex; + if ( v8 ) + v9 = *(int *)&buffer[4 * v8 - 3]; + else + v9 = 0; + + int ret = 0; + if ((ret = strnicmp(&pChunkName[v9], keyWithoutLeadingSlash, newIndex)) >= 0) // if value of first group greater or equal to our key + { + numChild >>= 1; // Divide by two + } + else // Group name is less than our key + { + keyIndex = v8 + 1; + numChild += -1 - (numChild >> 1); + } + + newIndex = index; + keyWithoutLeadingSlash = key; + } + while (numChild > 0); + + /// VALUE RESOLVING + numChild = numChildOrg; //Restore back? o_0 + if ( keyIndex ) + v10 = *(int*)&buffer[4 * keyIndex - 3]; + else + OnOrphanedTreeNodeDetected: + v10 = 0; + v11 = v10 + 4 * numChild - 3; + + int ret = 0; + if (keyIndex >= numChild || (ret = strnicmp(&buffer[v11], keyWithoutLeadingSlash, newIndex))) + { + return nullptr; + } + + /// ITERATION OVER TREE + v12 = index + v11; + valuePtr = &buffer[v12 + 2]; + buffer += v12 + 2; + + if ( !key[index] ) + { + return valuePtr - 1; + } + + key += index + 1; + } + } +} \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index f87236a..1b60fc0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -33,9 +33,12 @@ build: build_script: | cmake --build . --config Release +test_script: + - 'c:\projects\ReHitmanTools\build\Modules\BMLOC\%CONFIGURATION%\BMLOC_Tests.exe' + after_build: - cmd: cd c:\projects\ReHitmanTools\build - - cmd: 7z a GlacierTools.zip Tools\GMSInfo\Release\HBM_GMSTool.exe Tools\GMSInfo\Release\typeids.json + - cmd: 7z a GlacierTools.zip Tools\LOCC\Release\LOCC.exe Tools\GMSInfo\Release\HBM_GMSTool.exe Tools\GMSInfo\Release\typeids.json - cmd: mv GlacierTools.zip ../GlacierTools.zip #---------------------------------#