diff --git a/3rdparty/zlib/CMakeLists.txt b/3rdparty/zlib/CMakeLists.txt index a92d05c13e..7f75b85fb9 100644 --- a/3rdparty/zlib/CMakeLists.txt +++ b/3rdparty/zlib/CMakeLists.txt @@ -5,6 +5,7 @@ if (USE_SYSTEM_ZLIB) add_library(3rdparty_zlib INTERFACE) target_link_libraries(3rdparty_zlib INTERFACE ${ZLIB_LIBRARIES}) target_include_directories(3rdparty_zlib INTERFACE ${ZLIB_INCLUDE_DIRS}) + target_compile_definitions(3rdparty_zlib INTERFACE -DZLIB_CONST=1) else() message(STATUS "RPCS3: Using builtin ZLIB") set(SKIP_INSTALL_ALL ON) @@ -13,4 +14,5 @@ else() add_library(3rdparty_zlib INTERFACE) target_link_libraries(3rdparty_zlib INTERFACE zlibstatic) target_include_directories(3rdparty_zlib INTERFACE zlib ${CMAKE_CURRENT_BINARY_DIR}/zlib) + target_compile_definitions(3rdparty_zlib INTERFACE -DZLIB_CONST=1) endif() diff --git a/3rdparty/zlib/zlib.vcxproj b/3rdparty/zlib/zlib.vcxproj index 59c6f6127a..a333694e38 100644 --- a/3rdparty/zlib/zlib.vcxproj +++ b/3rdparty/zlib/zlib.vcxproj @@ -22,6 +22,23 @@ + + + + + + + + + + + + + + + + + {60F89955-91C6-3A36-8000-13C592FEC2DF} @@ -63,7 +80,7 @@ - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + WIN32;ZLIB_CONST;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) $(WarningLevel) ProgramDatabase Disabled @@ -90,7 +107,7 @@ true $(DisableSpecificWarnings);4127;4131;4242;4244 $(TreatWarningAsError) - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + WIN32;ZLIB_CONST;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true diff --git a/Utilities/JIT.cpp b/Utilities/JIT.cpp index 5d77d16f69..caec5a6b22 100644 --- a/Utilities/JIT.cpp +++ b/Utilities/JIT.cpp @@ -9,8 +9,8 @@ #include "util/asm.hpp" #include "util/v128.hpp" #include "util/simd.hpp" +#include "Crypto/unzip.h" #include -#include #ifdef __linux__ #define CAN_OVERCOMMIT @@ -136,7 +136,7 @@ static atomic_t s_code_pos{0}, s_data_pos{0}; static std::vector s_code_init, s_data_init; template & Ctr, uint Off, utils::protection Prot> -static u8* add_jit_memory(usz size, uint align) +static u8* add_jit_memory(usz size, usz align) { // Select subrange u8* pointer = get_jit_memory() + Off; @@ -251,7 +251,7 @@ uchar* jit_runtime::_alloc(usz size, usz align) noexcept return jit_runtime::alloc(size, align, true); } -u8* jit_runtime::alloc(usz size, uint align, bool exec) noexcept +u8* jit_runtime::alloc(usz size, usz align, bool exec) noexcept { if (exec) { @@ -1170,42 +1170,18 @@ public: //fs::file(name, fs::rewrite).write(obj.getBufferStart(), obj.getBufferSize()); name.append(".gz"); - z_stream zs{}; - uLong zsz = compressBound(::narrow(obj.getBufferSize())) + 256; - auto zbuf = std::make_unique(zsz); -#ifndef _MSC_VER -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" -#endif - deflateInit2(&zs, 9, Z_DEFLATED, 16 + 15, 9, Z_DEFAULT_STRATEGY); -#ifndef _MSC_VER -#pragma GCC diagnostic pop -#endif - zs.avail_in = static_cast(obj.getBufferSize()); - zs.next_in = reinterpret_cast(const_cast(obj.getBufferStart())); - zs.avail_out = static_cast(zsz); - zs.next_out = zbuf.get(); + const std::vector zbuf = zip(reinterpret_cast(obj.getBufferStart()), obj.getBufferSize()); - switch (deflate(&zs, Z_FINISH)) - { - case Z_OK: - case Z_STREAM_END: - { - deflateEnd(&zs); - break; - } - default: + if (zbuf.empty()) { jit_log.error("LLVM: Failed to compress module: %s", _module->getName().data()); - deflateEnd(&zs); return; } - } - if (!fs::write_file(name, fs::rewrite, zbuf.get(), zsz - zs.avail_out)) + if (!fs::write_file(name, fs::rewrite, zbuf.data(), zbuf.size())) { - jit_log.error("LLVM: Failed to create module file: %s (%s)", name, fs::g_tls_error); - return; + jit_log.error("LLVM: Failed to create module file: %s (%s)", name, fs::g_tls_error); + return; } jit_log.notice("LLVM: Created module: %s", _module->getName().data()); @@ -1215,57 +1191,21 @@ public: { if (fs::file cached{path + ".gz", fs::read}) { - std::vector gz = cached.to_vector(); - std::vector out; - z_stream zs{}; + const std::vector cached_data = cached.to_vector(); - if (gz.empty()) [[unlikely]] + if (cached_data.empty()) [[unlikely]] { return nullptr; } -#ifndef _MSC_VER -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" -#endif - inflateInit2(&zs, 16 + 15); -#ifndef _MSC_VER -#pragma GCC diagnostic pop -#endif - zs.avail_in = static_cast(gz.size()); - zs.next_in = gz.data(); - out.resize(gz.size() * 6); - zs.avail_out = static_cast(out.size()); - zs.next_out = out.data(); - while (zs.avail_in) + const std::vector out = unzip(cached_data); + + if (out.empty()) { - switch (inflate(&zs, Z_FINISH)) - { - case Z_OK: break; - case Z_STREAM_END: break; - case Z_BUF_ERROR: - { - if (zs.avail_in) - break; - [[fallthrough]]; - } - default: - inflateEnd(&zs); - return nullptr; - } - - if (zs.avail_in) - { - auto cur_size = zs.next_out - out.data(); - out.resize(out.size() + 65536); - zs.avail_out = static_cast(out.size() - cur_size); - zs.next_out = out.data() + cur_size; - } + jit_log.error("LLVM: Failed to unzip module: '%s'", path); + return nullptr; } - out.resize(zs.next_out - out.data()); - inflateEnd(&zs); - auto buf = llvm::WritableMemoryBuffer::getNewUninitMemBuffer(out.size()); std::memcpy(buf->getBufferStart(), out.data(), out.size()); return buf; diff --git a/Utilities/JIT.h b/Utilities/JIT.h index 498d5baf6b..60a9f155af 100644 --- a/Utilities/JIT.h +++ b/Utilities/JIT.h @@ -90,7 +90,7 @@ struct jit_runtime final : jit_runtime_base uchar* _alloc(usz size, usz align) noexcept override; // Allocate memory - static u8* alloc(usz size, uint align, bool exec = true) noexcept; + static u8* alloc(usz size, usz align, bool exec = true) noexcept; // Should be called at least once after global initialization static void initialize(); diff --git a/rpcs3/Crypto/unself.cpp b/rpcs3/Crypto/unself.cpp index a553bca1bd..8e1b8e9bd4 100644 --- a/rpcs3/Crypto/unself.cpp +++ b/rpcs3/Crypto/unself.cpp @@ -5,9 +5,9 @@ #include "Emu/VFS.h" #include "Emu/System.h" #include "Emu/system_utils.hpp" +#include "Crypto/unzip.h" #include -#include inline u8 Read8(const fs::file& f) { @@ -767,72 +767,32 @@ std::vector SCEDecrypter::MakeFile() u32 data_buf_offset = 0; // Write data. - for (unsigned int i = 0; i < meta_hdr.section_count; i++) + for (u32 i = 0; i < meta_hdr.section_count; i++) { + const MetadataSectionHeader& hdr = meta_shdr[i]; + const u8* src = data_buf.get() + data_buf_offset; fs::file out_f = fs::make_stream>(); - bool isValid = true; + bool is_valid = true; // Decompress if necessary. - if (meta_shdr[i].compressed == 2) + if (hdr.compressed == 2) { - const usz BUFSIZE = 32 * 1024; - u8 tempbuf[BUFSIZE]; - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = ::narrow(meta_shdr[i].data_size); - strm.avail_out = BUFSIZE; - strm.next_in = data_buf.get()+data_buf_offset; - strm.next_out = tempbuf; -#ifndef _MSC_VER -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" -#endif - int ret = inflateInit(&strm); -#ifndef _MSC_VER -#pragma GCC diagnostic pop -#endif - while (strm.avail_in) - { - ret = inflate(&strm, Z_NO_FLUSH); - if (ret == Z_STREAM_END) - break; - if (ret != Z_OK) - isValid = false; - - if (!strm.avail_out) { - out_f.write(tempbuf, BUFSIZE); - strm.next_out = tempbuf; - strm.avail_out = BUFSIZE; - } - else - break; - } - - int inflate_res = Z_OK; - inflate_res = inflate(&strm, Z_FINISH); - - if (inflate_res != Z_STREAM_END) - isValid = false; - - out_f.write(tempbuf, BUFSIZE - strm.avail_out); - inflateEnd(&strm); + is_valid = unzip(src, hdr.data_size, out_f); } else { // Write the data. - out_f.write(data_buf.get()+data_buf_offset, meta_shdr[i].data_size); + out_f.write(src, hdr.data_size); } // Advance the data buffer offset by data size. - data_buf_offset += ::narrow(meta_shdr[i].data_size); + data_buf_offset += ::narrow(hdr.data_size); if (out_f.pos() != out_f.size()) fmt::throw_exception("MakeELF written bytes (%llu) does not equal buffer size (%llu).", out_f.pos(), out_f.size()); - if (isValid) vec.push_back(std::move(out_f)); + if (is_valid) vec.push_back(std::move(out_f)); } return vec; diff --git a/rpcs3/Crypto/unzip.cpp b/rpcs3/Crypto/unzip.cpp new file mode 100644 index 0000000000..f2d46f161a --- /dev/null +++ b/rpcs3/Crypto/unzip.cpp @@ -0,0 +1,185 @@ +#include "stdafx.h" +#include "unzip.h" + +#include + +std::vector unzip(const void* src, usz size) +{ + if (!src || !size) [[unlikely]] + { + return {}; + } + + std::vector out(size * 6); + + z_stream zs{}; +#ifndef _MSC_VER +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#endif + int res = inflateInit2(&zs, 16 + 15); +#ifndef _MSC_VER +#pragma GCC diagnostic pop +#endif + zs.avail_in = static_cast(size); + zs.next_in = reinterpret_cast(src); + zs.avail_out = static_cast(out.size()); + zs.next_out = out.data(); + + while (zs.avail_in) + { + switch (inflate(&zs, Z_FINISH)) + { + case Z_OK: + case Z_STREAM_END: + break; + case Z_BUF_ERROR: + if (zs.avail_in) + break; + [[fallthrough]]; + default: + inflateEnd(&zs); + return out; + } + + if (zs.avail_in) + { + const auto cur_size = zs.next_out - out.data(); + out.resize(out.size() + 65536); + zs.avail_out = static_cast(out.size() - cur_size); + zs.next_out = &out[cur_size]; + } + } + + out.resize(zs.next_out - out.data()); + + res = inflateEnd(&zs); + + return out; +} + +bool unzip(const void* src, usz size, fs::file& out) +{ + if (!src || !size || !out) + { + return false; + } + + bool is_valid = true; + + constexpr usz BUFSIZE = 32ULL * 1024ULL; + std::vector tempbuf(BUFSIZE); + z_stream strm{}; + strm.avail_in = ::narrow(size); + strm.avail_out = static_cast(tempbuf.size()); + strm.next_in = reinterpret_cast(src); + strm.next_out = tempbuf.data(); +#ifndef _MSC_VER +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#endif + int res = inflateInit(&strm); +#ifndef _MSC_VER +#pragma GCC diagnostic pop +#endif + while (strm.avail_in) + { + res = inflate(&strm, Z_NO_FLUSH); + if (res == Z_STREAM_END) + break; + if (res != Z_OK) + is_valid = false; + + if (strm.avail_out) + break; + + if (out.write(tempbuf.data(), tempbuf.size()) != tempbuf.size()) + { + is_valid = false; + } + + strm.next_out = tempbuf.data(); + strm.avail_out = static_cast(tempbuf.size()); + } + + res = inflate(&strm, Z_FINISH); + + if (res != Z_STREAM_END) + is_valid = false; + + if (strm.avail_out < tempbuf.size()) + { + const usz bytes_to_write = tempbuf.size() - strm.avail_out; + + if (out.write(tempbuf.data(), bytes_to_write) != bytes_to_write) + { + is_valid = false; + } + } + + res = inflateEnd(&strm); + + return is_valid; +} + +std::vector zip(const void* src, usz size) +{ + if (!src || !size) + { + return {}; + } + + const uLong zsz = compressBound(::narrow(size)) + 256; + std::vector out(zsz); + + z_stream zs{}; +#ifndef _MSC_VER +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#endif + int res = deflateInit2(&zs, 9, Z_DEFLATED, 16 + 15, 9, Z_DEFAULT_STRATEGY); +#ifndef _MSC_VER +#pragma GCC diagnostic pop +#endif + zs.avail_in = static_cast(size); + zs.next_in = reinterpret_cast(src); + zs.avail_out = static_cast(out.size()); + zs.next_out = out.data(); + + res = deflate(&zs, Z_FINISH); + + switch (res) + { + case Z_OK: + case Z_STREAM_END: + if (zs.avail_out) + { + out.resize(zsz - zs.avail_out); + } + break; + default: + out.clear(); + break; + } + + deflateEnd(&zs); + + return out; +} + +bool zip(const void* src, usz size, fs::file& out) +{ + if (!src || !size || !out) + { + return false; + } + + const std::vector zipped = zip(src, size); + + if (zipped.empty() || out.write(zipped.data(), zipped.size()) != zipped.size()) + { + return false; + } + + return true; +} diff --git a/rpcs3/Crypto/unzip.h b/rpcs3/Crypto/unzip.h new file mode 100644 index 0000000000..dc7701da3e --- /dev/null +++ b/rpcs3/Crypto/unzip.h @@ -0,0 +1,33 @@ +#pragma once + +std::vector unzip(const void* src, usz size); + +template +std::vector unzip(const T& src) +{ + return unzip(src.data(), src.size()); +} + +bool unzip(const void* src, usz size, fs::file& out); + +template +bool unzip(const std::vector& src, fs::file& out) +{ + return unzip(src.data(), src.size(), out); +} + +std::vector zip(const void* src, usz size); + +template +std::vector zip(const T& src) +{ + return zip(src.data(), src.size()); +} + +bool zip(const void* src, usz size, fs::file& out); + +template +bool zip(const T& src, fs::file& out) +{ + return zip(src.data(), src.size(), out); +} diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 70df0385bf..e29c119c88 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -116,6 +116,7 @@ target_sources(rpcs3_emu PRIVATE ../Crypto/unedat.cpp ../Crypto/unpkg.cpp ../Crypto/unself.cpp + ../Crypto/unzip.cpp ../Crypto/utils.cpp ) diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 5e7215434c..bcadb9051e 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -42,8 +42,8 @@ Use ..\3rdparty\miniupnp\miniupnp\miniupnpc\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\flatbuffers\include;..\3rdparty\libusb\libusb\libusb;..\3rdparty\yaml-cpp\yaml-cpp\include;..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\rtmidi\rtmidi;..\3rdparty\zlib\zlib;..\3rdparty\llvm\llvm\include;..\3rdparty\llvm\llvm\llvm\include;..\3rdparty\llvm\llvm_build\include;$(VULKAN_SDK)\Include MaxSpeed - MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL2;%(PreprocessorDefinitions) - MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL2;%(PreprocessorDefinitions) + MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL2;ZLIB_CONST;%(PreprocessorDefinitions) + MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL2;ZLIB_CONST;%(PreprocessorDefinitions) cmd.exe /c "$(SolutionDir)\Utilities\git-version-gen.cmd" @@ -54,6 +54,7 @@ + true @@ -484,6 +485,7 @@ + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index ee1c8bb2ec..087b89c120 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -1171,6 +1171,9 @@ Emu\GPU\RSX\Core + + Crypto + @@ -2383,6 +2386,9 @@ Emu\GPU\RSX\Core + + Crypto + diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index 0b9baabdcb..1df0ca2339 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -79,7 +79,7 @@ 4577;4467;4281;%(DisableSpecificWarnings) $(IntDir) MaxSpeed - _WINDOWS;UNICODE;WIN32;WIN64;WIN32_LEAN_AND_MEAN;HAVE_VULKAN;MINIUPNP_STATICLIB;HAVE_SDL2;WITH_DISCORD_RPC;QT_NO_DEBUG;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB;NDEBUG;QT_WINEXTRAS_LIB;QT_CONCURRENT_LIB;QT_MULTIMEDIA_LIB;QT_MULTIMEDIAWIDGETS_LIB;QT_SVG_LIB;%(PreprocessorDefinitions) + _WINDOWS;UNICODE;WIN32;WIN64;WIN32_LEAN_AND_MEAN;HAVE_VULKAN;MINIUPNP_STATICLIB;ZLIB_CONST;HAVE_SDL2;WITH_DISCORD_RPC;QT_NO_DEBUG;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB;NDEBUG;QT_WINEXTRAS_LIB;QT_CONCURRENT_LIB;QT_MULTIMEDIA_LIB;QT_MULTIMEDIAWIDGETS_LIB;QT_SVG_LIB;%(PreprocessorDefinitions) false $(IntDir)vc$(PlatformToolsetVersion).pdb true @@ -132,7 +132,7 @@ 4577;4467;4281;%(DisableSpecificWarnings) $(IntDir) Disabled - _WINDOWS;UNICODE;WIN32;WIN64;WIN32_LEAN_AND_MEAN;HAVE_VULKAN;MINIUPNP_STATICLIB;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB;QT_WINEXTRAS_LIB;QT_CONCURRENT_LIB;QT_MULTIMEDIA_LIB;QT_MULTIMEDIAWIDGETS_LIB;QT_SVG_LIB;%(PreprocessorDefinitions) + _WINDOWS;UNICODE;WIN32;WIN64;WIN32_LEAN_AND_MEAN;HAVE_VULKAN;MINIUPNP_STATICLIB;ZLIB_CONST;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB;QT_WINEXTRAS_LIB;QT_CONCURRENT_LIB;QT_MULTIMEDIA_LIB;QT_MULTIMEDIAWIDGETS_LIB;QT_SVG_LIB;%(PreprocessorDefinitions) false true true diff --git a/rpcs3/rpcs3qt/log_viewer.cpp b/rpcs3/rpcs3qt/log_viewer.cpp index d39bfd5367..8073bcee74 100644 --- a/rpcs3/rpcs3qt/log_viewer.cpp +++ b/rpcs3/rpcs3qt/log_viewer.cpp @@ -4,6 +4,7 @@ #include "gui_settings.h" #include "syntax_highlighter.h" #include "config_checker.h" +#include "Crypto/unzip.h" #include #include @@ -145,7 +146,7 @@ void log_viewer::show_context_menu(const QPoint& pos) connect(open, &QAction::triggered, this, [this]() { - const QString file_path = QFileDialog::getOpenFileName(this, tr("Select log file"), m_path_last, tr("Log files (*.log);;All files (*.*)")); + const QString file_path = QFileDialog::getOpenFileName(this, tr("Select log file"), m_path_last, tr("Log files (*.log *.gz);;All files (*.*)")); if (file_path.isEmpty()) return; m_path_last = file_path; @@ -154,14 +155,27 @@ void log_viewer::show_context_menu(const QPoint& pos) connect(save, &QAction::triggered, this, [this]() { - const QString file_path = QFileDialog::getSaveFileName(this, tr("Save to file"), m_path_last, tr("Log files (*.log);;All files (*.*)")); + const QString file_path = QFileDialog::getSaveFileName(this, tr("Save to file"), m_path_last, tr("Log files (*.log *.gz);;All files (*.*)")); if (file_path.isEmpty()) return; - if (QFile log_file(file_path); log_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) + if (fs::file log_file; log_file.open(file_path.toStdString(), fs::rewrite)) { - log_file.write(m_log_text->toPlainText().toUtf8()); - log_file.close(); + const QByteArray bytes = m_log_text->toPlainText().toUtf8(); + + if (file_path.endsWith(".gz")) + { + if (!zip(bytes.constData(), bytes.size(), log_file)) + { + gui_log.error("Failed to zip filtered log to file '%s'", file_path); + return; + } + } + else + { + log_file.write(bytes.constData(), bytes.size()); + } + gui_log.success("Exported filtered log to file '%s'", file_path); } else @@ -230,24 +244,43 @@ void log_viewer::show_log() m_log_text->setPlainText(tr("Loading file...")); QApplication::processEvents(); - if (QFile file(m_path_last); - file.exists() && file.open(QIODevice::ReadOnly | QIODevice::Text)) - { - m_gui_settings->SetValue(gui::fd_log_viewer, m_path_last); + bool failed = false; + if (m_path_last.endsWith(".gz")) + { + if (fs::file file{m_path_last.toStdString()}) + { + const std::vector decompressed = unzip(file.to_vector()); + m_full_log = QString::fromUtf8(reinterpret_cast(decompressed.data()), decompressed.size()); + } + else + { + failed = true; + } + } + else if (QFile file(m_path_last); file.exists() && file.open(QIODevice::ReadOnly | QIODevice::Text)) + { // TODO: Due to a bug in Qt 6.5.2 QTextStream::readAll is ridiculously slow to the point where it gets stuck on large files. // In Qt 5.15.2 this was much faster than QFile::readAll. Use QTextStream again once this bug is fixed upstream. //QTextStream stream(&file); //m_full_log = stream.readAll(); m_full_log = file.readAll(); - m_full_log.replace('\0', '0'); - file.close(); } else + { + failed = true; + } + + if (failed) { gui_log.error("log_viewer: Failed to open %s", m_path_last); m_log_text->setPlainText(tr("Failed to open '%0'").arg(m_path_last)); } + else + { + m_gui_settings->SetValue(gui::fd_log_viewer, m_path_last); + m_full_log.replace('\0', '0'); + } filter_log(); } @@ -403,7 +436,7 @@ bool log_viewer::is_valid_file(const QMimeData& md, bool save) const QString suffix = QFileInfo(urls[0].fileName()).suffix().toLower(); - if (suffix == "log") + if (suffix == "log" || suffix == "gz") { if (save) {