#include "IWD.h" #include "ObjLoading.h" #include "Utils/FileToZlibWrapper.h" #include #include #include #include #include #include namespace fs = std::filesystem; ObjContainerRepository IWD::Repository; class IWDFile final : public objbuf { public: class IParent { public: virtual ~IParent() = default; virtual void OnIWDFileClose() = 0; }; private: IParent* m_parent; bool m_open; int64_t m_size; unzFile m_container; bool m_peeked; int_type m_peek_symbol; public: IWDFile(IParent* parent, const unzFile container, const int64_t size) : m_parent(parent), m_open(true), m_size(size), m_container(container), m_peeked(false), m_peek_symbol(0) { } ~IWDFile() override { if (m_open) { close(); } } protected: int_type underflow() override { if (m_peeked) return m_peek_symbol; const auto result = unzReadCurrentFile(m_container, &m_peek_symbol, 1u); if (result >= 0) { m_peeked = true; return static_cast(m_peek_symbol); } return EOF; } int_type uflow() override { if (m_peeked) { m_peeked = false; return m_peek_symbol; } const auto result = unzReadCurrentFile(m_container, &m_peek_symbol, 1u); return result >= 0 ? static_cast(m_peek_symbol) : EOF; } std::streamsize xsgetn(char* ptr, std::streamsize count) override { if (m_peeked && count >= 1) { *ptr = static_cast(m_peek_symbol); ptr++; count--; } const auto result = unzReadCurrentFile(m_container, ptr, static_cast(count)); return result >= 0 ? static_cast(result) : 0; } pos_type seekoff(const off_type off, const std::ios_base::seekdir dir, const std::ios_base::openmode mode) override { const auto currentPos = unztell64(m_container); pos_type targetPos; if (dir == std::ios_base::beg) { targetPos = off; } else if (dir == std::ios_base::cur) { targetPos = currentPos + off; } else { targetPos = m_size - off; } return seekpos(targetPos, mode); } pos_type seekpos(const pos_type pos, const std::ios_base::openmode mode) override { const auto currentPos = unztell64(m_container); if (static_cast(currentPos) < pos) { auto skipAmount = pos - static_cast(currentPos); while (skipAmount > 0) { char temp[1024]; const auto toRead = skipAmount > sizeof(temp) ? sizeof(temp) : static_cast(skipAmount); unzReadCurrentFile(m_container, temp, toRead); skipAmount -= toRead; } return pos; } if (currentPos == pos) { // This is fine return currentPos; } return std::streampos(-1); } public: _NODISCARD bool is_open() const override { return m_open; } bool close() override { unzCloseCurrentFile(m_container); m_open = false; m_parent->OnIWDFileClose(); return true; } }; class IWD::Impl : public ISearchPath, public IObjContainer, public IWDFile::IParent { class IWDEntry { public: int64_t m_size{}; unz_file_pos m_file_pos{}; }; std::string m_path; std::unique_ptr m_stream; unzFile m_unz_file; IWDFile* m_last_file; std::map m_entry_map; public: Impl(std::string path, std::unique_ptr stream) : m_path(std::move(path)), m_stream(std::move(stream)), m_unz_file(nullptr), m_last_file(nullptr) { } ~Impl() override { if (m_unz_file != nullptr) { unzClose(m_unz_file); m_unz_file = nullptr; } } Impl(const Impl& other) = delete; Impl(Impl&& other) noexcept = default; Impl& operator=(const Impl& other) = delete; Impl& operator=(Impl&& other) noexcept = default; bool Initialize() { auto ioFunctions = FileToZlibWrapper::CreateFunctions32ForFile(m_stream.get()); m_unz_file = unzOpen2("", &ioFunctions); if (m_unz_file == nullptr) { printf("Could not open IWD \"%s\"\n", m_path.c_str()); return false; } auto ret = unzGoToFirstFile(m_unz_file); while (ret == Z_OK) { unz_file_info64 info; char fileNameBuffer[256]; unzGetCurrentFileInfo64(m_unz_file, &info, fileNameBuffer, sizeof(fileNameBuffer), nullptr, 0, nullptr, 0); std::string fileName(fileNameBuffer); std::filesystem::path path(fileName); if (path.has_filename()) { IWDEntry entry; entry.m_size = info.uncompressed_size; unzGetFilePos(m_unz_file, &entry.m_file_pos); m_entry_map.emplace(std::move(fileName), entry); } ret = unzGoToNextFile(m_unz_file); } if (ObjLoading::Configuration.Verbose) { printf("Loaded IWD \"%s\" with %u entries\n", m_path.c_str(), m_entry_map.size()); } return true; } SearchPathOpenFile Open(const std::string& fileName) override { if (m_unz_file == nullptr) { return SearchPathOpenFile(); } auto iwdFilename = fileName; std::ranges::replace(iwdFilename, '\\', '/'); const auto iwdEntry = m_entry_map.find(iwdFilename); if (iwdEntry != m_entry_map.end()) { if (m_last_file != nullptr) { throw std::runtime_error("Trying to open new IWD file while last one was not yet closed."); } auto pos = iwdEntry->second.m_file_pos; unzGoToFilePos(m_unz_file, &pos); if (unzOpenCurrentFile(m_unz_file) == UNZ_OK) { auto result = std::make_unique(this, m_unz_file, iwdEntry->second.m_size); m_last_file = result.get(); return SearchPathOpenFile(std::make_unique(std::move(result)), iwdEntry->second.m_size); } return SearchPathOpenFile(); } return SearchPathOpenFile(); } std::string GetPath() override { return m_path; } std::string GetName() override { return fs::path(m_path).filename().string(); } void Find(const SearchPathSearchOptions& options, const std::function& callback) override { if (options.m_disk_files_only) { return; } for (auto& [entryName, entry] : m_entry_map) { std::filesystem::path entryPath(entryName); if (!options.m_should_include_subdirectories && entryPath.has_parent_path()) continue; if (options.m_filter_extensions && options.m_extension != entryPath.extension().string()) continue; callback(entryName); } } void OnIWDFileClose() override { m_last_file = nullptr; } }; IWD::IWD(std::string path, std::unique_ptr stream) { m_impl = new Impl(std::move(path), std::move(stream)); } IWD::~IWD() { delete m_impl; m_impl = nullptr; } IWD::IWD(IWD&& other) noexcept { m_impl = other.m_impl; other.m_impl = nullptr; } IWD& IWD::operator=(IWD&& other) noexcept { m_impl = other.m_impl; other.m_impl = nullptr; return *this; } bool IWD::Initialize() { return m_impl->Initialize(); } SearchPathOpenFile IWD::Open(const std::string& fileName) { return m_impl->Open(fileName); } std::string IWD::GetPath() { return m_impl->GetPath(); } std::string IWD::GetName() { return m_impl->GetName(); } void IWD::Find(const SearchPathSearchOptions& options, const std::function& callback) { return m_impl->Find(options, callback); }