diff --git a/.gitmodules b/.gitmodules index b3925c9..5fe5de8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,12 @@ [submodule "libs/imgui"] path = libs/imgui url = https://github.com/ocornut/imgui +[submodule "libs/detex"] + path = libs/detex + url = https://github.com/hglm/detex +[submodule "libs/libpng"] + path = libs/libpng + url = https://github.com/glennrp/libpng +[submodule "libs/zlib"] + path = libs/zlib + url = https://github.com/madler/zlib diff --git a/ArchiveTest.cmake b/ArchiveTest.cmake index a1dcc9e..ab2f2ef 100644 --- a/ArchiveTest.cmake +++ b/ArchiveTest.cmake @@ -1,18 +1,21 @@ add_executable(ArchiveTest src/archive_test.cpp src/decima/archive/archive.cpp - src/decima/file_types/prefetch.cpp + src/decima/archive/archive_file.cpp src/decima/archive/archive_array.cpp - src/utils.cpp + src/decima/file_types/prefetch.cpp src/decima/file_types/prefetch.cpp src/decima/file_types/core.cpp - src/decima/archive/archive_file.cpp) + src/decima/file_types/core_draw.cpp + src/utils.cpp + # src/decima/file_types/core_draw.cpp + ) target_include_directories(ArchiveTest PUBLIC include ${HASHLIB_INC} ${OOZLIB_INC}) target_link_libraries(ArchiveTest PUBLIC - HashLib oozLib + HashLib oozLib imgui -static-libgcc -static-libstdc++ -static -Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic diff --git a/CMakeLists.txt b/CMakeLists.txt index cd608e3..a55342c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,9 +17,12 @@ endif (DEBUG_MODE) add_subdirectory(libs/hash) add_subdirectory(libs/ooz) add_subdirectory(libs/glfw) +add_subdirectory(libs/zlib) +add_subdirectory(libs/libpng) add_subdirectory(cmake_subprojects/glad) add_subdirectory(cmake_subprojects/stb) add_subdirectory(cmake_subprojects/imgui) +add_subdirectory(cmake_subprojects/detex) include(QuickHash.cmake) include(ArchiveTest.cmake) diff --git a/ProjectDS.cmake b/ProjectDS.cmake index 8aaf010..d0506e1 100644 --- a/ProjectDS.cmake +++ b/ProjectDS.cmake @@ -25,7 +25,7 @@ target_include_directories(ProjectDS PUBLIC ) target_link_libraries(ProjectDS PUBLIC - HashLib oozLib glfw glad imgui + HashLib oozLib glfw glad imgui detex -static-libgcc -static-libstdc++ -static -Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic diff --git a/cmake_subprojects/detex/alloca.h b/cmake_subprojects/detex/alloca.h new file mode 100644 index 0000000..25fe588 --- /dev/null +++ b/cmake_subprojects/detex/alloca.h @@ -0,0 +1,2 @@ +#include +#include \ No newline at end of file diff --git a/include/ash.hpp b/include/ash.hpp index 64612ad..7b78b8b 100644 --- a/include/ash.hpp +++ b/include/ash.hpp @@ -70,6 +70,10 @@ namespace ash { pos = std::min(pos + amount, cap); } + [[nodiscard]] bool eof() const noexcept { + return cur == end; + } + [[nodiscard]] std::size_t tell() const noexcept { return std::distance(beg, cur); } diff --git a/include/decima/archive/archive_file.h b/include/decima/archive/archive_file.h index 232d641..e521a61 100644 --- a/include/decima/archive/archive_file.h +++ b/include/decima/archive/archive_file.h @@ -16,8 +16,7 @@ namespace Decima { class CompressedFile { public: - - CompressedFile(FileEntry* file_entry_, mio::mmap_source* filebuffer_,Archive* archive_); + CompressedFile(FileEntry* file_entry_, mio::mmap_source* filebuffer_, Archive* archive_, bool encrypted_); CompressedFile() = default; @@ -25,6 +24,7 @@ namespace Decima { FileEntry* file_entry = nullptr; mio::mmap_source* filebuffer = nullptr; Archive* archive = nullptr; + bool encrypted = true; std::vector storage; diff --git a/include/decima/archive/archive_tree.h b/include/decima/archive/archive_tree.h index 36640be..fda7da1 100644 --- a/include/decima/archive/archive_tree.h +++ b/include/decima/archive/archive_tree.h @@ -18,31 +18,27 @@ #include "archive_file.h" struct SelectionInfo { - std::uint64_t preview_file{0}; - std::uint64_t selected_file{0}; + SelectionInfo() = default; + std::uint64_t preview_file { 0 }; + std::uint64_t preview_file_size { 0 }; + std::uint64_t preview_file_offset { 0 }; + std::uint64_t selected_file { 0 }; std::unordered_set selected_files; Decima::CompressedFile file; }; struct FileInfo { - uint64_t hash{0}; - Decima::CoreHeader header{0}; + uint64_t hash { 0 }; + Decima::CoreHeader header { 0 }; }; -struct FileTypeHandler { - std::string name; - std::function render = nullptr; -}; - -template +template using FileTreeToggleable = std::pair; class FileTree { public: std::map>> folders; std::map> files; -// std::unordered_map file_type_handlers; - FileTree* add_folder(const std::string& name); @@ -52,19 +48,19 @@ class FileTree { void reset_filter(bool state); - template + template void visit(const Visitor& visitor, std::size_t depth = 0) const { - for (const auto&[name, data] : folders) { + for (const auto& [name, data] : folders) { visitor(name, depth); data.first->visit(visitor, depth + 1); } - for (const auto&[name, _] : files) { + for (const auto& [name, _] : files) { visitor(name, depth); } } - void draw(SelectionInfo& selection, Decima::ArchiveArray& archive_array); + void draw(SelectionInfo& selection, Decima::ArchiveArray& archive_array, bool draw_header = true); }; #endif //PROJECTDS_ARCHIVE_TREE_H diff --git a/include/decima/constants.hpp b/include/decima/constants.hpp index 21a471a..b258893 100644 --- a/include/decima/constants.hpp +++ b/include/decima/constants.hpp @@ -24,6 +24,11 @@ namespace Decima { static constexpr uint64_t Translation = 0x31be502435317445; static constexpr uint64_t Model = 0x16bb69a9e5aa0d9e; }; + class ZeroDawn_FileMagics : public FileMagics { + public: + static constexpr uint64_t Texture = 0xf2e1afb7052b3866; + + }; enum class Version : uint32_t { default_version = 0x20304050, diff --git a/include/decima/file_types/core.h b/include/decima/file_types/core.h index aca22d7..be75650 100644 --- a/include/decima/file_types/core.h +++ b/include/decima/file_types/core.h @@ -11,6 +11,8 @@ #include "ash.hpp" #include "shared.hpp" +class ProjectDS; + namespace Decima { struct GUID { std::uint64_t data[2]; @@ -36,7 +38,7 @@ namespace Decima { virtual void parse(Source& stream); - virtual void draw(ArchiveArray& archive_array); + virtual void draw(ProjectDS& ctx); }; std::string read_string(CoreFile::Source& stream, const std::string& default_value = ""); diff --git a/include/decima/file_types/texture.h b/include/decima/file_types/texture.h index c027abf..b39ad84 100644 --- a/include/decima/file_types/texture.h +++ b/include/decima/file_types/texture.h @@ -6,9 +6,10 @@ #define PROJECTDS_TEXTURE_H #include "decima/file_types/core.h" + namespace Decima { enum class TexturePixelFormat : uint8_t { - R8G8B8A8 = 0xC, + RGBA8 = 0xC, A8 = 0x1F, BC1 = 0x42, BC2 = 0x43, @@ -18,26 +19,35 @@ namespace Decima { BC7 = 0x4B, }; + std::ostream& operator<<(std::ostream& os, Decima::TexturePixelFormat fmt); + class Texture : public CoreFile { public: - uint16_t unk1{}; - uint16_t width{}; - uint16_t height{}; - uint16_t layers{}; - uint8_t mip_count{}; - TexturePixelFormat pixel_format{}; - uint16_t unk2{}; - uint32_t unk3{}; - uint64_t file_guid[2]{}; - uint32_t buffer_size{}; - uint32_t total_size{}; - uint32_t unks[4]{}; - - std::string stream_name="NO_EXTERNAL_STREAM"; + ~Texture(); + + std::uint16_t unk1 {}; + std::uint16_t width {}; + std::uint16_t height {}; + std::uint16_t layers {}; + std::uint8_t mip_count {}; + TexturePixelFormat pixel_format {}; + std::uint16_t unk2 {}; + std::uint32_t unk3 {}; + GUID file_guid {}; + std::uint32_t buffer_size {}; + std::uint32_t total_size {}; + std::uint32_t stream_size {}; + std::uint32_t unks[3] {}; + + std::vector stream_buffer; + std::vector image_buffer; + unsigned int image_texture; + + std::string stream_name = "NO_EXTERNAL_STREAM"; void parse(Source& stream) override; - void draw(ArchiveArray& archive_array) override; + void draw(ProjectDS& ctx) override; + void draw_texture(ProjectDS& ctx, float preview_width, float preview_height, float zoom_region, float zoom_scale); }; - } #endif //PROJECTDS_TEXTURE_H diff --git a/include/decima/file_types/translation.hpp b/include/decima/file_types/translation.hpp index 10fba11..dfcccd6 100644 --- a/include/decima/file_types/translation.hpp +++ b/include/decima/file_types/translation.hpp @@ -48,8 +48,7 @@ namespace Decima { std::uint8_t flags[std::size(languages)]; void parse(Source& stream) override; - - void draw(ArchiveArray& archive_array) override; + void draw(ProjectDS& ctx) override; }; } diff --git a/include/projectds_app.hpp b/include/projectds_app.hpp index 85b240b..d2e9777 100644 --- a/include/projectds_app.hpp +++ b/include/projectds_app.hpp @@ -19,14 +19,14 @@ class ProjectDS : public App { ProjectDS(const std::pair& windowSize, const std::string& title, bool imgui_multi_viewport = false); -private: +public: bool m_multi_viewport; Decima::ArchiveArray archive_array; std::vector file_names; FileTree root_tree; SelectionInfo selection_info; - std::vector> parsed_files; + std::vector> parsed_files; int32_t file_id = 0; ImGuiTextFilter filter; MemoryEditor file_viewer; diff --git a/libs/detex b/libs/detex new file mode 160000 index 0000000..cab1158 --- /dev/null +++ b/libs/detex @@ -0,0 +1 @@ +Subproject commit cab11584d9be140602a66fd9a88ef0b99f08a97a diff --git a/libs/libpng b/libs/libpng new file mode 160000 index 0000000..dbe3e0c --- /dev/null +++ b/libs/libpng @@ -0,0 +1 @@ +Subproject commit dbe3e0c43e549a1602286144d94b0666549b18e6 diff --git a/libs/ooz/CMakeLists.txt b/libs/ooz/CMakeLists.txt index 84331d4..d1d67be 100644 --- a/libs/ooz/CMakeLists.txt +++ b/libs/ooz/CMakeLists.txt @@ -10,11 +10,10 @@ set(OOZLIB_INC ) -set(GCC_COVERAGE_COMPILE_FLAGS -Wpsabi) -SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_COVERAGE_COMPILE_FLAGS}") +SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O0") add_library(oozLib Kraken.cpp) #if (DEBUG_MODE) diff --git a/libs/zlib b/libs/zlib new file mode 160000 index 0000000..cacf7f1 --- /dev/null +++ b/libs/zlib @@ -0,0 +1 @@ +Subproject commit cacf7f1d4e3d44d871b605da3b647f07d718623f diff --git a/src/archive_test.cpp b/src/archive_test.cpp index 754e6c3..ac72066 100644 --- a/src/archive_test.cpp +++ b/src/archive_test.cpp @@ -6,7 +6,7 @@ #include "decima/archive/archive.h" int main() { - Decima::ArchiveArray archive_array(R"(F:\SteamLibrary\steamapps\common\Death Stranding\data)"); + Decima::ArchiveArray archive_array(R"(F:\SteamLibrary\steamapps\common\Horizon Zero Dawn\Packed_DX12)"); archive_array.read_prefetch_file(); diff --git a/src/decima/archive/archive.cpp b/src/decima/archive/archive.cpp index 675957e..617030b 100644 --- a/src/decima/archive/archive.cpp +++ b/src/decima/archive/archive.cpp @@ -11,9 +11,6 @@ #include #include -template -using slice = std::pair, std::decay_t>; - Decima::Archive::Archive(const std::string& workdir, const std::string& filename) : filepath(workdir + "\\" + filename) { } @@ -135,10 +132,10 @@ Decima::CompressedFile Decima::Archive::query_file(uint64_t file_hash) { // log("Archive", "Queried " + uint64_to_hex(file_hash) + " file"); auto file_id = get_file_index(file_hash); if (file_id == -1) { - return Decima::CompressedFile(nullptr, nullptr, nullptr); + return Decima::CompressedFile(nullptr, nullptr, nullptr, true); } auto& file_entry = content_table.at(file_id); - Decima::CompressedFile file(&file_entry, &filebuffer, this); + Decima::CompressedFile file(&file_entry, &filebuffer, this, is_encrypted()); file.chunk_range = get_mio_boundaries(file_id); diff --git a/src/decima/archive/archive_array.cpp b/src/decima/archive/archive_array.cpp index f71b2f0..e9161b2 100644 --- a/src/decima/archive/archive_array.cpp +++ b/src/decima/archive/archive_array.cpp @@ -8,7 +8,6 @@ #include "utils.h" #include "decima/archive/archive_array.h" -#include "decima/archive/archive.h" Decima::ArchiveArray::ArchiveArray(const std::string& _workdir) { open(_workdir); @@ -24,8 +23,10 @@ void Decima::ArchiveArray::read_prefetch_file() { for (auto& string : prefetch.strings) { uint64_t hash = hash_string(sanitize_name(string.string), seed); - hash_to_name[hash] = string.string; + hash_to_name.insert({ hash, string.string }); } + + hash_to_name.insert({ 0x2fff5af65cd64c0a, "prefetch/fullgame.prefetch" }); } void Decima::ArchiveArray::open(const std::string& _workdir) { @@ -57,7 +58,7 @@ Decima::ArchiveArray::get_file_entry(uint64_t file_hash) { uint64_t archive_id = hash_to_archive.at(file_hash); auto& archive = archives[archive_id]; uint64_t file_id = archive.get_file_index(file_hash); - return std::optional> {archive.content_table[file_id] }; + return std::optional> { archive.content_table[file_id] }; } return std::nullopt; } @@ -68,7 +69,7 @@ Decima::CompressedFile Decima::ArchiveArray::query_file(uint64_t file_hash) { auto& archive = archives[archive_id->second]; return std::move(archive.query_file(file_hash)); } - return Decima::CompressedFile(nullptr, nullptr,nullptr); + return Decima::CompressedFile(nullptr, nullptr, nullptr, true); } Decima::CompressedFile Decima::ArchiveArray::query_file(const std::string& file_name) { diff --git a/src/decima/archive/archive_file.cpp b/src/decima/archive/archive_file.cpp index 7e43740..f6986db 100644 --- a/src/decima/archive/archive_file.cpp +++ b/src/decima/archive/archive_file.cpp @@ -11,11 +11,14 @@ void Decima::CompressedFile::unpack(uint32_t size) { uint64_t required_size = 0; - auto[chunk_entry_begin, chunk_entry_end] = chunk_range; + auto [chunk_entry_begin, chunk_entry_end] = chunk_range; for (auto chunk_entry = chunk_entry_begin; chunk_entry != chunk_entry_end; ++chunk_entry) { required_size += chunk_entry->uncompressed_size; } - decrypt(size); + if (encrypted) + decrypt(size); + else + get_raw(); std::vector data_in(storage); storage.clear(); storage.resize(required_size); @@ -24,9 +27,9 @@ void Decima::CompressedFile::unpack(uint32_t size) { const auto file_position = file_entry->offset % archive->header.max_chunk_size; for (auto chunk_entry = chunk_entry_begin; chunk_entry != chunk_entry_end; ++chunk_entry) { decompress_chunk_data(data_in.data() + in_pos, - chunk_entry->compressed_size, - chunk_entry->uncompressed_size, - &storage.at(out_pos)); + chunk_entry->compressed_size, + chunk_entry->uncompressed_size, + &storage.at(out_pos)); out_pos += chunk_entry->uncompressed_size; in_pos += chunk_entry->compressed_size; } @@ -35,7 +38,7 @@ void Decima::CompressedFile::unpack(uint32_t size) { } void Decima::CompressedFile::decrypt(uint32_t size) { - auto[chunk_entry_begin, chunk_entry_end] = chunk_range; + auto [chunk_entry_begin, chunk_entry_end] = chunk_range; get_raw(); uint64_t out_pos = 0; for (auto chunk_entry = chunk_entry_begin; chunk_entry != chunk_entry_end; ++chunk_entry) { @@ -47,7 +50,7 @@ void Decima::CompressedFile::decrypt(uint32_t size) { } uint8_t digest[16]; - md5Hash((md5_byte_t*) iv, 16, digest); + md5Hash((md5_byte_t*)iv, 16, digest); for (int i = 0; i < chunk_entry->compressed_size; i++) { storage[out_pos + i] ^= digest[i % 16]; } @@ -55,15 +58,16 @@ void Decima::CompressedFile::decrypt(uint32_t size) { } } -Decima::CompressedFile::CompressedFile(FileEntry* file_entry_, mio::mmap_source* filebuffer_, Archive* archive_) { +Decima::CompressedFile::CompressedFile(FileEntry* file_entry_, mio::mmap_source* filebuffer_, Archive* archive_, bool encrypted_) { file_entry = file_entry_; filebuffer = filebuffer_; archive = archive_; + encrypted = encrypted_; } void Decima::CompressedFile::get_raw() { uint64_t required_size = 0; - auto[chunk_entry_begin, chunk_entry_end] = chunk_range; + auto [chunk_entry_begin, chunk_entry_end] = chunk_range; for (auto chunk_entry = chunk_entry_begin; chunk_entry != chunk_entry_end; ++chunk_entry) { required_size += chunk_entry->compressed_size; } @@ -72,9 +76,7 @@ void Decima::CompressedFile::get_raw() { uint64_t out_offset = 0; for (auto chunk_entry = chunk_entry_begin; chunk_entry != chunk_entry_end; ++chunk_entry) { memcpy(storage.data() + out_offset, filebuffer->data() + chunk_entry->compressed_offset, - chunk_entry->compressed_size); + chunk_entry->compressed_size); out_offset += chunk_entry->compressed_size; } - - } diff --git a/src/decima/archive/archive_tree.cpp b/src/decima/archive/archive_tree.cpp index aaa74fb..49c1e23 100644 --- a/src/decima/archive/archive_tree.cpp +++ b/src/decima/archive/archive_tree.cpp @@ -59,23 +59,36 @@ void FileTree::reset_filter(bool state) { } } -void FileTree::draw(SelectionInfo& selection, Decima::ArchiveArray& archive_array) { +void FileTree::draw(SelectionInfo& selection, Decima::ArchiveArray& archive_array, bool draw_header) { + if (draw_header) { + ImGui::Separator(); + ImGui::Columns(3); + ImGui::Text("Name"); + ImGui::NextColumn(); + ImGui::Text("Type"); + ImGui::NextColumn(); + ImGui::Text("Size"); + ImGui::NextColumn(); + ImGui::Separator(); + + ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() - 200); + ImGui::SetColumnWidth(1, 100); + ImGui::SetColumnWidth(2, 100); + } + for (auto& [name, data] : folders) { - const std::string tree_name = name + "##" + std::to_string(folders.size()); + const auto tree_name = name + "##" + std::to_string(folders.size()); const auto show = ImGui::TreeNode(tree_name.c_str()); - const auto files_count = data.first->files.size(); - const auto folders_count = data.first->folders.size(); + const auto items_count = data.first->files.size() + data.first->folders.size(); ImGui::NextColumn(); ImGui::Text("Folder"); ImGui::NextColumn(); - ImGui::Text("%llu file%c / %llu folder%c", - files_count, files_count == 1 ? ' ' : 's', - folders_count, folders_count == 1 ? ' ' : 's'); + ImGui::Text("%llu item%c", items_count, items_count == 1 ? ' ' : 's'); ImGui::NextColumn(); if (data.second && show) { - data.first->draw(selection, archive_array); + data.first->draw(selection, archive_array, false); ImGui::TreePop(); } } @@ -105,10 +118,10 @@ void FileTree::draw(SelectionInfo& selection, Decima::ArchiveArray& archive_arra if (data.first.header.file_type == 0) { auto file_data = archive_array.query_file(data.first.hash); if (!file_data.is_valid()) { - data.first.header.file_type =-1; + data.first.header.file_type = -1; } else { file_data.unpack(0); - memcpy(&data.first.header,file_data.storage.data(),sizeof(Decima::CoreHeader)); + memcpy(&data.first.header, file_data.storage.data(), sizeof(Decima::CoreHeader)); } } @@ -130,4 +143,8 @@ void FileTree::draw(SelectionInfo& selection, Decima::ArchiveArray& archive_arra ImGui::NextColumn(); } } + + if (draw_header) { + ImGui::Columns(1); + } } diff --git a/src/decima/file_types/core_draw.cpp b/src/decima/file_types/core_draw.cpp index 3f20d6d..139de21 100644 --- a/src/decima/file_types/core_draw.cpp +++ b/src/decima/file_types/core_draw.cpp @@ -4,9 +4,9 @@ #include "decima/file_types/core.h" #include "decima/constants.hpp" -#include "imgui.h" +#include "projectds_app.hpp" -void Decima::CoreFile::draw(ArchiveArray& archive_array) { +void Decima::CoreFile::draw(ProjectDS& ctx) { ImGui::TextDisabled("Default handler"); ImGui::Columns(2); ImGui::SetColumnWidth(-1, 200); @@ -17,14 +17,22 @@ void Decima::CoreFile::draw(ArchiveArray& archive_array) { ImGui::Separator(); - ImGui::Text("Size"); + ImGui::Text("Magic"); ImGui::NextColumn(); - ImGui::Text("%u", header.file_size + 12); - ImGui::Separator(); + ImGui::Text("%s", uint64_to_hex(header.file_type).c_str()); + if (ImGui::BeginPopupContextItem("File magic")) { + if (ImGui::Selectable("Copy file magic")) + ImGui::SetClipboardText(uint64_to_hex(header.file_type).c_str()); + ImGui::EndPopup(); + } ImGui::NextColumn(); - ImGui::Text("Offset"); + + ImGui::Separator(); + + ImGui::Text("Size"); ImGui::NextColumn(); - ImGui::Text("%u", offset); + ImGui::Text("%u", header.file_size + 12); + ImGui::Columns(1); } \ No newline at end of file diff --git a/src/decima/file_types/prefetch.cpp b/src/decima/file_types/prefetch.cpp index ab1ae0c..9330dcb 100644 --- a/src/decima/file_types/prefetch.cpp +++ b/src/decima/file_types/prefetch.cpp @@ -2,8 +2,7 @@ // Created by MED45 on 26.07.2020. // -#include - +#include "decima/file_types/prefetch.h" void Decima::Prefetch::parse(Source& stream) { CoreFile::parse(stream); diff --git a/src/decima/file_types/texture.cpp b/src/decima/file_types/texture.cpp index 4a6e743..9e19448 100644 --- a/src/decima/file_types/texture.cpp +++ b/src/decima/file_types/texture.cpp @@ -15,21 +15,48 @@ void Decima::Texture::parse(Source& stream) { pixel_format = stream.read(); unk2 = stream.read(); unk3 = stream.read(); - file_guid[0] = stream.read(); - file_guid[1] = stream.read(); + file_guid = stream.read(); buffer_size = stream.read(); total_size = stream.read(); + stream_size = stream.read(); unks[0] = stream.read(); - unks[1] = stream.read(); - unks[2] = stream.read(); - unks[3] = stream.read(); + unks[1] = stream.read(); + unks[2] = stream.read(); - if(unks[0]!=0&&unks[1]!=0){ - auto str_len = stream.read(); - std::string buff(str_len,0); - stream.read(buff); - stream_name = std::move(buff); + if (stream_size > 0) { + const auto length = stream.read(); + stream_name.resize(length); + stream.read_exact(stream_name); + stream.seek(ash::seek_dir::cur, total_size + sizeof(CoreHeader) + sizeof(GUID)); + } else { + stream_buffer.resize(total_size); + stream.read_exact(stream_buffer); } +} + +#include - stream.seek(ash::seek_dir::beg, start + header.file_size - sizeof(GUID)); +namespace Decima { + std::ostream& operator<<(std::ostream& os, Decima::TexturePixelFormat fmt) { + switch (fmt) { + case Decima::TexturePixelFormat::RGBA8: + return os << "RGBA8"; + case Decima::TexturePixelFormat::A8: + return os << "A8"; + case Decima::TexturePixelFormat::BC1: + return os << "BC1"; + case Decima::TexturePixelFormat::BC2: + return os << "BC2"; + case Decima::TexturePixelFormat::BC3: + return os << "BC3"; + case Decima::TexturePixelFormat::BC4: + return os << "BC4"; + case Decima::TexturePixelFormat::BC5: + return os << "BC5"; + case Decima::TexturePixelFormat::BC7: + return os << "BC7"; + default: + return os << "Unsupported: " << std::to_string(int(fmt)); + } + } } diff --git a/src/decima/file_types/texture_draw.cpp b/src/decima/file_types/texture_draw.cpp index 7007ab7..bab2aa8 100644 --- a/src/decima/file_types/texture_draw.cpp +++ b/src/decima/file_types/texture_draw.cpp @@ -4,11 +4,14 @@ #include "decima/file_types/texture.h" #include "decima/archive/archive_array.h" #include "utils.h" +#include "projectds_app.hpp" -#include "imgui.h" - -void Decima::Texture::draw(ArchiveArray& archive_array) { +#include +#include +#include +#include +void Decima::Texture::draw(ProjectDS& ctx) { ImGui::Columns(2); ImGui::SetColumnWidth(-1, 200); ImGui::Text("Prop"); @@ -49,7 +52,11 @@ void Decima::Texture::draw(ArchiveArray& archive_array) { ImGui::Text("Pixel format"); ImGui::NextColumn(); - ImGui::Text("%hhu", pixel_format); + { + std::stringstream buffer; + buffer << pixel_format; + ImGui::Text("%s", buffer.str().c_str()); + } ImGui::NextColumn(); ImGui::Separator(); @@ -67,7 +74,11 @@ void Decima::Texture::draw(ArchiveArray& archive_array) { ImGui::Text("GUID"); ImGui::NextColumn(); - ImGui::Text("%llX%llX", file_guid[0], file_guid[1]); + { + std::stringstream buffer; + buffer << file_guid; + ImGui::Text("%s", buffer.str().c_str()); + } ImGui::NextColumn(); ImGui::Separator(); @@ -83,35 +94,166 @@ void Decima::Texture::draw(ArchiveArray& archive_array) { ImGui::NextColumn(); ImGui::Separator(); - ImGui::Text("unks-0"); + ImGui::Text("Stream Size"); ImGui::NextColumn(); - ImGui::Text("%i", unks[0]); + ImGui::Text("%i", stream_size); ImGui::NextColumn(); ImGui::Separator(); ImGui::Text("unks-1"); ImGui::NextColumn(); - ImGui::Text("%i", unks[1]); + ImGui::Text("%i", unks[0]); ImGui::NextColumn(); ImGui::Separator(); - ImGui::Text("unks-2"); + ImGui::Text("unks-1"); ImGui::NextColumn(); - ImGui::Text("%i", unks[2]); + ImGui::Text("%i", unks[1]); ImGui::NextColumn(); ImGui::Separator(); - ImGui::Text("unks-3"); + ImGui::Text("unks-2"); ImGui::NextColumn(); - ImGui::Text("%i", unks[3]); + ImGui::Text("%i", unks[2]); ImGui::NextColumn(); ImGui::Separator(); ImGui::Text("Stream"); ImGui::NextColumn(); ImGui::Text("%s", stream_name.c_str()); + if (ImGui::BeginPopupContextItem("Stream name")) { + if (ImGui::Selectable("Copy stream path")) + ImGui::SetClipboardText((stream_name + ".core.stream").c_str()); + ImGui::EndPopup(); + } + draw_texture(ctx, 128, 128, 128, 4); ImGui::NextColumn(); - ImGui::Separator(); - ImGui::Columns(1); } + +static bool decompress_texture(std::vector& src, std::vector& dst, int width, int height, int fmt) { + detexTexture texture; + texture.format = fmt; + texture.data = src.data(); + texture.width = width; + texture.height = height; + texture.width_in_blocks = int(width / (detexGetCompressedBlockSize(fmt) / 2)); + texture.height_in_blocks = int(height / (detexGetCompressedBlockSize(fmt) / 2)); + + if (!detexDecompressTextureLinear(&texture, dst.data(), DETEX_PIXEL_FORMAT_RGBA8)) { + std::printf("Buffer cannot be decompressed: %s\n", detexGetErrorMessage()); + return false; + } + + return true; +} + +void Decima::Texture::draw_texture(ProjectDS& ctx, float preview_width, float preview_height, float zoom_region, float zoom_scale) { + /* + * This is very clunky approach. Maybe rewrite into something + * similar to what I did in ProjectDS::parse_core_file using + * factory pattern + */ + static const std::unordered_map format_mapper = { + { TexturePixelFormat::BC1, DETEX_TEXTURE_FORMAT_BC1 }, + { TexturePixelFormat::BC2, DETEX_TEXTURE_FORMAT_BC2 }, + { TexturePixelFormat::BC3, DETEX_TEXTURE_FORMAT_BC3 }, + { TexturePixelFormat::BC4, DETEX_TEXTURE_FORMAT_RGTC1 }, + { TexturePixelFormat::BC5, DETEX_TEXTURE_FORMAT_RGTC2 }, + { TexturePixelFormat::BC7, DETEX_TEXTURE_FORMAT_BPTC } + }; + + if (image_buffer.empty()) { + image_buffer.resize(width * height * 4); + + if (stream_size > 0) { + auto stream_file = ctx.archive_array.query_file(stream_name + ".core.stream"); + stream_file.unpack(0); + stream_buffer = std::move(stream_file.storage); + } + + if (pixel_format == TexturePixelFormat::RGBA8) { + std::memcpy(image_buffer.data(), stream_buffer.data(), image_buffer.size()); + } else { + const auto format = format_mapper.find(pixel_format); + + if (format == format_mapper.end()) { + std::stringstream buffer; + buffer << "Image pixel format is not supported: " << pixel_format; + + ImGui::Text("%s", buffer.str().c_str()); + return; + } + + if (!decompress_texture(stream_buffer, image_buffer, width, height, format->second)) { + std::puts("Texture was failed to decode"); + return; + } + } + + glGenTextures(1, &image_texture); + glBindTexture(GL_TEXTURE_2D, image_texture); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_buffer.data()); + } + + const ImVec2 pos = ImGui::GetCursorScreenPos(); + const ImVec4 tint = { 1, 1, 1, 1 }; + const ImVec4 border = { 1, 1, 1, 1 }; + + ImGui::Image(reinterpret_cast(image_texture), { preview_width, preview_height }, { 0, 0 }, { 1, 1 }, tint, border); + + if (ImGui::BeginPopupContextItem("Export Image")) { + if (ImGui::Selectable("Export image")) { + const auto full_path = pfd::save_file("Choose destination file", "", { "PNG", "*.png" }).result() + ".png"; + + if (!full_path.empty()) { + detexTexture texture; + texture.format = DETEX_PIXEL_FORMAT_RGBA8; + texture.data = image_buffer.data(); + texture.width = width; + texture.height = height; + detexSavePNGFile(&texture, full_path.c_str()); + + std::cout << "Image was saved to: " << full_path << '\n'; + } + } + + ImGui::EndPopup(); + } + + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + + auto& io = ImGui::GetIO(); + auto region_x = io.MousePos.x - pos.x - zoom_region * 0.5f; + auto region_y = io.MousePos.y - pos.y - zoom_region * 0.5f; + + if (region_x < 0.0f) { + region_x = 0.0f; + } else if (region_x > preview_width - zoom_region) { + region_x = preview_width - zoom_region; + } + + if (region_y < 0.0f) { + region_y = 0.0f; + } else if (region_y > preview_height - zoom_region) { + region_y = preview_height - zoom_region; + } + + ImVec2 uv0 = { region_x / preview_width, region_y / preview_height }; + ImVec2 uv1 = { (region_x + zoom_region) / preview_width, (region_y + zoom_region) / preview_height }; + ImGui::Image(reinterpret_cast(image_texture), ImVec2(zoom_region * zoom_scale, zoom_region * zoom_scale), uv0, uv1, tint, border); + + ImGui::EndTooltip(); + } +} + +Decima::Texture::~Texture() { + if (image_texture > 0) + glDeleteTextures(1, &image_texture); +} \ No newline at end of file diff --git a/src/decima/file_types/translation_draw.cpp b/src/decima/file_types/translation_draw.cpp index 12e5fa1..764fb69 100644 --- a/src/decima/file_types/translation_draw.cpp +++ b/src/decima/file_types/translation_draw.cpp @@ -6,7 +6,7 @@ #include "imgui.h" -void Decima::Translation::draw(Decima::ArchiveArray& archive_array) { +void Decima::Translation::draw(ProjectDS& ctx) { ImGui::Columns(4); ImGui::SetColumnWidth(-1, 200); ImGui::Text("Language"); @@ -22,9 +22,9 @@ void Decima::Translation::draw(Decima::ArchiveArray& archive_array) { ImGui::Separator(); ImGui::Text("%s", Decima::Translation::languages[index]); ImGui::NextColumn(); - ImGui::Text("%s", translations[index].c_str()); + ImGui::TextWrapped("%s", translations[index].c_str()); ImGui::NextColumn(); - ImGui::Text("%s", comments[index].c_str()); + ImGui::TextWrapped("%s", comments[index].c_str()); ImGui::NextColumn(); if(flags[index]) { ImGui::Text("%d", flags[index]); diff --git a/src/main.cpp b/src/main.cpp index 80795c1..15db976 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,7 +5,7 @@ #include "projectds_app.hpp" int main() { - ProjectDS app({ 1280, 720 }, "Project Decima"); + ProjectDS app({ 1700, 720 }, "Project Decima"); app.init(); app.run(); } diff --git a/src/projectds_app_draw.cpp b/src/projectds_app_draw.cpp index a06220e..a546b7a 100644 --- a/src/projectds_app_draw.cpp +++ b/src/projectds_app_draw.cpp @@ -18,6 +18,13 @@ void ProjectDS::update_user(double ts) { draw_debug(); draw_export(); draw_tree(); + + if (glfwGetKey(m_window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { + if (selection_info.preview_file != 0) { + selection_info.preview_file_size = selection_info.file.storage.size(); + selection_info.preview_file_offset = 0; + } + } } void ProjectDS::init_filetype_handlers() { @@ -99,8 +106,8 @@ void ProjectDS::draw_filepreview() { ImGui::EndPopup(); } - ImGui::Columns(2); ImGui::Separator(); + ImGui::Columns(2); ImGui::Text("Size"); ImGui::NextColumn(); ImGui::Text("%u bytes", file_entry.size); @@ -119,56 +126,67 @@ void ProjectDS::draw_filepreview() { ImGui::Text("Offset"); ImGui::NextColumn(); ImGui::Text("%llu", file_entry.offset); - ImGui::Separator(); - ImGui::NextColumn(); - ImGui::Separator(); ImGui::Columns(1); + ImGui::Separator(); const bool selected_file_changed = selection_info.preview_file != selection_info.selected_file; if (selected_file_changed) { - selection_info.preview_file = selection_info.selected_file; selection_info.file = archive_array.query_file(selection_info.selected_file); selection_info.file.unpack(0); + selection_info.preview_file = selection_info.selected_file; + selection_info.preview_file_size = selection_info.file.storage.size(); + selection_info.preview_file_offset = 0; parse_core_file(); } + } else { + ImGui::Text("Error getting file info!"); + } + } else { + ImGui::Text("No file selected"); + } + + ImGui::DockSpace(ImGui::GetID("Dock2")); + } + ImGui::End(); - // const auto file_type = Decima::CoreFile::peek_header(selection_info.file.storage); - // const auto file_handler = root_tree.file_type_handlers.find(file_type); + ImGui::Begin("Normal View", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + { + if (selection_info.selected_file > 0) { + for (const auto& file : parsed_files) { + std::stringstream buffer; + buffer << '[' << file->guid << "] " << Decima::get_type_name(file->header.file_type); - // if (selected_file_changed && file_handler != root_tree.file_type_handlers.end()) - // file_handler->second.render(selection_info.file, true); + const bool opened = ImGui::TreeNode(buffer.str().c_str()); - ImGui::BeginTabBar("Data View"); - { - if (ImGui::BeginTabItem("Raw View")) { - file_viewer.DrawContents(selection_info.file.storage.data(), - selection_info.file.storage.size()); - ImGui::EndTabItem(); + if (ImGui::BeginPopupContextItem(buffer.str().c_str())) { + if (ImGui::Selectable("Highlight")) { + selection_info.preview_file_offset = file->offset; + selection_info.preview_file_size = file->header.file_size + sizeof(Decima::CoreHeader); } - if (ImGui::BeginTabItem("Normal View")) { - for (const auto& file : parsed_files) { - std::stringstream buffer; - buffer << '[' << file->guid << "] " << Decima::get_type_name(file->header.file_type); - - if (ImGui::TreeNode(buffer.str().c_str())) { - file->draw(archive_array); - ImGui::TreePop(); - } + ImGui::EndPopup(); + } - ImGui::Separator(); - } - ImGui::EndTabItem(); - } + if (opened) { + file->draw(*this); + ImGui::TreePop(); } - ImGui::EndTabBar(); - } else { - ImGui::Text("Error getting file info!"); + + ImGui::Separator(); } - } else { - ImGui::Text("No file selected"); + } + } + ImGui::End(); + + ImGui::Begin("Raw View", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + { + if (selection_info.selected_file > 0) { + file_viewer.DrawContents( + selection_info.file.storage.data() + selection_info.preview_file_offset, + selection_info.preview_file_size, + 0); } } ImGui::End(); @@ -200,21 +218,7 @@ void ProjectDS::draw_tree() { } if (ImGui::BeginTabItem("TreeView")) { - ImGui::Columns(3); - - ImGui::Separator(); - ImGui::Text("Name"); - ImGui::NextColumn(); - ImGui::Text("Type"); - ImGui::NextColumn(); - ImGui::Text("Size"); - ImGui::NextColumn(); - ImGui::Separator(); - root_tree.draw(selection_info, archive_array); - - ImGui::Columns(1); - ImGui::EndTabItem(); } } @@ -257,7 +261,7 @@ void ProjectDS::draw_export() { std::cout << "Processing file '" << path << "' (size: " << file.file_entry->size << ")\n"; - while (stream.tell() < file.storage.size()) { + while (!stream.eof()) { Decima::Dummy dummy; dummy.parse(stream); diff --git a/src/projectds_app_utils.cpp b/src/projectds_app_utils.cpp index 75c2394..57a183a 100644 --- a/src/projectds_app_utils.cpp +++ b/src/projectds_app_utils.cpp @@ -6,34 +6,36 @@ #include "decima/file_types.hpp" +template +using Constructor = std::function(Args&&...)>; + +template +static std::unique_ptr construct(Args&&... args) { + return std::make_unique(std::forward(args)...); +} void ProjectDS::parse_core_file() { parsed_files.clear(); Decima::CoreFile::Source stream(selection_info.file.storage, 1024); - while (stream.tell() < selection_info.file.storage.size()) { - uint64_t magic = Decima::CoreFile::peek_header(stream); - - switch (magic) { - case (Decima::DeathStranding_FileMagics::Translation): { - Decima::Translation localization; - localization.parse(stream); - parsed_files.push_back(std::make_shared(localization)); - break; - } - case (Decima::DeathStranding_FileMagics::Texture): { - Decima::Texture texture; - texture.parse(stream); - parsed_files.push_back(std::make_shared(texture)); - break; - } - default:{ - Decima::Dummy dummy; - dummy.parse(stream); - parsed_files.push_back(std::make_shared(dummy)); - break; - } - } + static const std::map> types = { + { Decima::DeathStranding_FileMagics::Translation, construct }, + { Decima::DeathStranding_FileMagics::Texture, construct } + }; + + static const auto get_handler = [](std::uint64_t hash) noexcept { + const auto handler = types.find(hash); + return handler != types.end() ? handler->second() : construct(); + }; + + while (!stream.eof()) { + const auto offset = stream.tell(); + const auto magic = Decima::CoreFile::peek_header(stream); + + auto handler = get_handler(magic); + handler->parse(stream); + handler->offset = offset; + parsed_files.push_back(std::move(handler)); } } \ No newline at end of file