mirror of
https://github.com/RPCS3/rpcs3.git
synced 2024-11-22 02:32:36 +01:00
Qt: Allow to use .gz files in Log viewer
Log viewer can open .gz files Log viewer can save log as .gz Refactored most instances of zip and unzip code to seperate functions
This commit is contained in:
parent
e95cff0bde
commit
76629e1b52
2
3rdparty/zlib/CMakeLists.txt
vendored
2
3rdparty/zlib/CMakeLists.txt
vendored
@ -5,6 +5,7 @@ if (USE_SYSTEM_ZLIB)
|
|||||||
add_library(3rdparty_zlib INTERFACE)
|
add_library(3rdparty_zlib INTERFACE)
|
||||||
target_link_libraries(3rdparty_zlib INTERFACE ${ZLIB_LIBRARIES})
|
target_link_libraries(3rdparty_zlib INTERFACE ${ZLIB_LIBRARIES})
|
||||||
target_include_directories(3rdparty_zlib INTERFACE ${ZLIB_INCLUDE_DIRS})
|
target_include_directories(3rdparty_zlib INTERFACE ${ZLIB_INCLUDE_DIRS})
|
||||||
|
target_compile_definitions(3rdparty_zlib INTERFACE -DZLIB_CONST=1)
|
||||||
else()
|
else()
|
||||||
message(STATUS "RPCS3: Using builtin ZLIB")
|
message(STATUS "RPCS3: Using builtin ZLIB")
|
||||||
set(SKIP_INSTALL_ALL ON)
|
set(SKIP_INSTALL_ALL ON)
|
||||||
@ -13,4 +14,5 @@ else()
|
|||||||
add_library(3rdparty_zlib INTERFACE)
|
add_library(3rdparty_zlib INTERFACE)
|
||||||
target_link_libraries(3rdparty_zlib INTERFACE zlibstatic)
|
target_link_libraries(3rdparty_zlib INTERFACE zlibstatic)
|
||||||
target_include_directories(3rdparty_zlib INTERFACE zlib ${CMAKE_CURRENT_BINARY_DIR}/zlib)
|
target_include_directories(3rdparty_zlib INTERFACE zlib ${CMAKE_CURRENT_BINARY_DIR}/zlib)
|
||||||
|
target_compile_definitions(3rdparty_zlib INTERFACE -DZLIB_CONST=1)
|
||||||
endif()
|
endif()
|
||||||
|
21
3rdparty/zlib/zlib.vcxproj
vendored
21
3rdparty/zlib/zlib.vcxproj
vendored
@ -22,6 +22,23 @@
|
|||||||
<ClCompile Include="$(ZLibSrcDir)\trees.c" />
|
<ClCompile Include="$(ZLibSrcDir)\trees.c" />
|
||||||
<ClCompile Include="$(ZLibSrcDir)\uncompr.c" />
|
<ClCompile Include="$(ZLibSrcDir)\uncompr.c" />
|
||||||
<ClCompile Include="$(ZLibSrcDir)\zutil.c" />
|
<ClCompile Include="$(ZLibSrcDir)\zutil.c" />
|
||||||
|
<ClCompile Include="zlib\gzclose.c" />
|
||||||
|
<ClCompile Include="zlib\gzlib.c" />
|
||||||
|
<ClCompile Include="zlib\gzread.c" />
|
||||||
|
<ClCompile Include="zlib\gzwrite.c" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="zlib\crc32.h" />
|
||||||
|
<ClInclude Include="zlib\deflate.h" />
|
||||||
|
<ClInclude Include="zlib\gzguts.h" />
|
||||||
|
<ClInclude Include="zlib\inffast.h" />
|
||||||
|
<ClInclude Include="zlib\inffixed.h" />
|
||||||
|
<ClInclude Include="zlib\inflate.h" />
|
||||||
|
<ClInclude Include="zlib\inftrees.h" />
|
||||||
|
<ClInclude Include="zlib\trees.h" />
|
||||||
|
<ClInclude Include="zlib\zconf.h" />
|
||||||
|
<ClInclude Include="zlib\zlib.h" />
|
||||||
|
<ClInclude Include="zlib\zutil.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Globals">
|
<PropertyGroup Label="Globals">
|
||||||
<ProjectGuid>{60F89955-91C6-3A36-8000-13C592FEC2DF}</ProjectGuid>
|
<ProjectGuid>{60F89955-91C6-3A36-8000-13C592FEC2DF}</ProjectGuid>
|
||||||
@ -63,7 +80,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
<ClCompile>
|
<ClCompile>
|
||||||
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
<PreprocessorDefinitions>WIN32;ZLIB_CONST;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
<WarningLevel>$(WarningLevel)</WarningLevel>
|
<WarningLevel>$(WarningLevel)</WarningLevel>
|
||||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||||
<Optimization>Disabled</Optimization>
|
<Optimization>Disabled</Optimization>
|
||||||
@ -90,7 +107,7 @@
|
|||||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
<DisableSpecificWarnings>$(DisableSpecificWarnings);4127;4131;4242;4244</DisableSpecificWarnings>
|
<DisableSpecificWarnings>$(DisableSpecificWarnings);4127;4131;4242;4244</DisableSpecificWarnings>
|
||||||
<TreatWarningAsError>$(TreatWarningAsError)</TreatWarningAsError>
|
<TreatWarningAsError>$(TreatWarningAsError)</TreatWarningAsError>
|
||||||
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
<PreprocessorDefinitions>WIN32;ZLIB_CONST;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
@ -9,8 +9,8 @@
|
|||||||
#include "util/asm.hpp"
|
#include "util/asm.hpp"
|
||||||
#include "util/v128.hpp"
|
#include "util/v128.hpp"
|
||||||
#include "util/simd.hpp"
|
#include "util/simd.hpp"
|
||||||
|
#include "Crypto/unzip.h"
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
#include <zlib.h>
|
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
#define CAN_OVERCOMMIT
|
#define CAN_OVERCOMMIT
|
||||||
@ -136,7 +136,7 @@ static atomic_t<u64> s_code_pos{0}, s_data_pos{0};
|
|||||||
static std::vector<u8> s_code_init, s_data_init;
|
static std::vector<u8> s_code_init, s_data_init;
|
||||||
|
|
||||||
template <atomic_t<u64>& Ctr, uint Off, utils::protection Prot>
|
template <atomic_t<u64>& 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
|
// Select subrange
|
||||||
u8* pointer = get_jit_memory() + Off;
|
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);
|
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)
|
if (exec)
|
||||||
{
|
{
|
||||||
@ -1170,42 +1170,18 @@ public:
|
|||||||
//fs::file(name, fs::rewrite).write(obj.getBufferStart(), obj.getBufferSize());
|
//fs::file(name, fs::rewrite).write(obj.getBufferStart(), obj.getBufferSize());
|
||||||
name.append(".gz");
|
name.append(".gz");
|
||||||
|
|
||||||
z_stream zs{};
|
const std::vector<u8> zbuf = zip(reinterpret_cast<const u8*>(obj.getBufferStart()), obj.getBufferSize());
|
||||||
uLong zsz = compressBound(::narrow<u32>(obj.getBufferSize())) + 256;
|
|
||||||
auto zbuf = std::make_unique<uchar[]>(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<uInt>(obj.getBufferSize());
|
|
||||||
zs.next_in = reinterpret_cast<uchar*>(const_cast<char*>(obj.getBufferStart()));
|
|
||||||
zs.avail_out = static_cast<uInt>(zsz);
|
|
||||||
zs.next_out = zbuf.get();
|
|
||||||
|
|
||||||
switch (deflate(&zs, Z_FINISH))
|
if (zbuf.empty())
|
||||||
{
|
|
||||||
case Z_OK:
|
|
||||||
case Z_STREAM_END:
|
|
||||||
{
|
|
||||||
deflateEnd(&zs);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
{
|
||||||
jit_log.error("LLVM: Failed to compress module: %s", _module->getName().data());
|
jit_log.error("LLVM: Failed to compress module: %s", _module->getName().data());
|
||||||
deflateEnd(&zs);
|
|
||||||
return;
|
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);
|
jit_log.error("LLVM: Failed to create module file: %s (%s)", name, fs::g_tls_error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
jit_log.notice("LLVM: Created module: %s", _module->getName().data());
|
jit_log.notice("LLVM: Created module: %s", _module->getName().data());
|
||||||
@ -1215,57 +1191,21 @@ public:
|
|||||||
{
|
{
|
||||||
if (fs::file cached{path + ".gz", fs::read})
|
if (fs::file cached{path + ".gz", fs::read})
|
||||||
{
|
{
|
||||||
std::vector<uchar> gz = cached.to_vector<uchar>();
|
const std::vector<u8> cached_data = cached.to_vector<u8>();
|
||||||
std::vector<uchar> out;
|
|
||||||
z_stream zs{};
|
|
||||||
|
|
||||||
if (gz.empty()) [[unlikely]]
|
if (cached_data.empty()) [[unlikely]]
|
||||||
{
|
{
|
||||||
return nullptr;
|
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<uInt>(gz.size());
|
|
||||||
zs.next_in = gz.data();
|
|
||||||
out.resize(gz.size() * 6);
|
|
||||||
zs.avail_out = static_cast<uInt>(out.size());
|
|
||||||
zs.next_out = out.data();
|
|
||||||
|
|
||||||
while (zs.avail_in)
|
const std::vector<u8> out = unzip(cached_data);
|
||||||
|
|
||||||
|
if (out.empty())
|
||||||
{
|
{
|
||||||
switch (inflate(&zs, Z_FINISH))
|
jit_log.error("LLVM: Failed to unzip module: '%s'", path);
|
||||||
{
|
return nullptr;
|
||||||
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<uInt>(out.size() - cur_size);
|
|
||||||
zs.next_out = out.data() + cur_size;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
out.resize(zs.next_out - out.data());
|
|
||||||
inflateEnd(&zs);
|
|
||||||
|
|
||||||
auto buf = llvm::WritableMemoryBuffer::getNewUninitMemBuffer(out.size());
|
auto buf = llvm::WritableMemoryBuffer::getNewUninitMemBuffer(out.size());
|
||||||
std::memcpy(buf->getBufferStart(), out.data(), out.size());
|
std::memcpy(buf->getBufferStart(), out.data(), out.size());
|
||||||
return buf;
|
return buf;
|
||||||
|
@ -90,7 +90,7 @@ struct jit_runtime final : jit_runtime_base
|
|||||||
uchar* _alloc(usz size, usz align) noexcept override;
|
uchar* _alloc(usz size, usz align) noexcept override;
|
||||||
|
|
||||||
// Allocate memory
|
// 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
|
// Should be called at least once after global initialization
|
||||||
static void initialize();
|
static void initialize();
|
||||||
|
@ -5,9 +5,9 @@
|
|||||||
#include "Emu/VFS.h"
|
#include "Emu/VFS.h"
|
||||||
#include "Emu/System.h"
|
#include "Emu/System.h"
|
||||||
#include "Emu/system_utils.hpp"
|
#include "Emu/system_utils.hpp"
|
||||||
|
#include "Crypto/unzip.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <zlib.h>
|
|
||||||
|
|
||||||
inline u8 Read8(const fs::file& f)
|
inline u8 Read8(const fs::file& f)
|
||||||
{
|
{
|
||||||
@ -767,72 +767,32 @@ std::vector<fs::file> SCEDecrypter::MakeFile()
|
|||||||
u32 data_buf_offset = 0;
|
u32 data_buf_offset = 0;
|
||||||
|
|
||||||
// Write data.
|
// 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<std::vector<u8>>();
|
fs::file out_f = fs::make_stream<std::vector<u8>>();
|
||||||
|
|
||||||
bool isValid = true;
|
bool is_valid = true;
|
||||||
|
|
||||||
// Decompress if necessary.
|
// Decompress if necessary.
|
||||||
if (meta_shdr[i].compressed == 2)
|
if (hdr.compressed == 2)
|
||||||
{
|
{
|
||||||
const usz BUFSIZE = 32 * 1024;
|
is_valid = unzip(src, hdr.data_size, out_f);
|
||||||
u8 tempbuf[BUFSIZE];
|
|
||||||
z_stream strm;
|
|
||||||
strm.zalloc = Z_NULL;
|
|
||||||
strm.zfree = Z_NULL;
|
|
||||||
strm.opaque = Z_NULL;
|
|
||||||
strm.avail_in = ::narrow<uInt>(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);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Write the data.
|
// 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.
|
// Advance the data buffer offset by data size.
|
||||||
data_buf_offset += ::narrow<u32>(meta_shdr[i].data_size);
|
data_buf_offset += ::narrow<u32>(hdr.data_size);
|
||||||
|
|
||||||
if (out_f.pos() != out_f.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());
|
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;
|
return vec;
|
||||||
|
185
rpcs3/Crypto/unzip.cpp
Normal file
185
rpcs3/Crypto/unzip.cpp
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
#include "stdafx.h"
|
||||||
|
#include "unzip.h"
|
||||||
|
|
||||||
|
#include <zlib.h>
|
||||||
|
|
||||||
|
std::vector<u8> unzip(const void* src, usz size)
|
||||||
|
{
|
||||||
|
if (!src || !size) [[unlikely]]
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uchar> 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<uInt>(size);
|
||||||
|
zs.next_in = reinterpret_cast<const u8*>(src);
|
||||||
|
zs.avail_out = static_cast<uInt>(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<uInt>(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<u8> tempbuf(BUFSIZE);
|
||||||
|
z_stream strm{};
|
||||||
|
strm.avail_in = ::narrow<uInt>(size);
|
||||||
|
strm.avail_out = static_cast<uInt>(tempbuf.size());
|
||||||
|
strm.next_in = reinterpret_cast<const u8*>(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<uInt>(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<u8> zip(const void* src, usz size)
|
||||||
|
{
|
||||||
|
if (!src || !size)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const uLong zsz = compressBound(::narrow<u32>(size)) + 256;
|
||||||
|
std::vector<u8> 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<u32>(size);
|
||||||
|
zs.next_in = reinterpret_cast<const u8*>(src);
|
||||||
|
zs.avail_out = static_cast<u32>(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;
|
||||||
|
}
|
33
rpcs3/Crypto/unzip.h
Normal file
33
rpcs3/Crypto/unzip.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
std::vector<u8> unzip(const void* src, usz size);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
std::vector<u8> unzip(const T& src)
|
||||||
|
{
|
||||||
|
return unzip(src.data(), src.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool unzip(const void* src, usz size, fs::file& out);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
bool unzip(const std::vector<u8>& src, fs::file& out)
|
||||||
|
{
|
||||||
|
return unzip(src.data(), src.size(), out);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> zip(const void* src, usz size);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
std::vector<u8> zip(const T& src)
|
||||||
|
{
|
||||||
|
return zip(src.data(), src.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool zip(const void* src, usz size, fs::file& out);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
bool zip(const T& src, fs::file& out)
|
||||||
|
{
|
||||||
|
return zip(src.data(), src.size(), out);
|
||||||
|
}
|
@ -116,6 +116,7 @@ target_sources(rpcs3_emu PRIVATE
|
|||||||
../Crypto/unedat.cpp
|
../Crypto/unedat.cpp
|
||||||
../Crypto/unpkg.cpp
|
../Crypto/unpkg.cpp
|
||||||
../Crypto/unself.cpp
|
../Crypto/unself.cpp
|
||||||
|
../Crypto/unzip.cpp
|
||||||
../Crypto/utils.cpp
|
../Crypto/utils.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -42,8 +42,8 @@
|
|||||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||||
<AdditionalIncludeDirectories>..\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</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>..\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</AdditionalIncludeDirectories>
|
||||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Release|x64'">MaxSpeed</Optimization>
|
<Optimization Condition="'$(Configuration)|$(Platform)'=='Release|x64'">MaxSpeed</Optimization>
|
||||||
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL2;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL2;ZLIB_CONST;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL2;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL2;ZLIB_CONST;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<PreBuildEvent>
|
<PreBuildEvent>
|
||||||
<Command>cmd.exe /c "$(SolutionDir)\Utilities\git-version-gen.cmd"</Command>
|
<Command>cmd.exe /c "$(SolutionDir)\Utilities\git-version-gen.cmd"</Command>
|
||||||
@ -54,6 +54,7 @@
|
|||||||
<ClCompile Include="..\Utilities\cheat_info.cpp" />
|
<ClCompile Include="..\Utilities\cheat_info.cpp" />
|
||||||
<ClCompile Include="..\Utilities\stack_trace.cpp" />
|
<ClCompile Include="..\Utilities\stack_trace.cpp" />
|
||||||
<ClCompile Include="Crypto\decrypt_binaries.cpp" />
|
<ClCompile Include="Crypto\decrypt_binaries.cpp" />
|
||||||
|
<ClCompile Include="Crypto\unzip.cpp" />
|
||||||
<ClCompile Include="Emu\Audio\audio_resampler.cpp" />
|
<ClCompile Include="Emu\Audio\audio_resampler.cpp" />
|
||||||
<ClCompile Include="Emu\Audio\FAudio\FAudioBackend.cpp">
|
<ClCompile Include="Emu\Audio\FAudio\FAudioBackend.cpp">
|
||||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||||
@ -484,6 +485,7 @@
|
|||||||
<ClInclude Include="..\Utilities\stack_trace.h" />
|
<ClInclude Include="..\Utilities\stack_trace.h" />
|
||||||
<ClInclude Include="..\Utilities\transactional_storage.h" />
|
<ClInclude Include="..\Utilities\transactional_storage.h" />
|
||||||
<ClInclude Include="Crypto\decrypt_binaries.h" />
|
<ClInclude Include="Crypto\decrypt_binaries.h" />
|
||||||
|
<ClInclude Include="Crypto\unzip.h" />
|
||||||
<ClInclude Include="Emu\Audio\audio_resampler.h" />
|
<ClInclude Include="Emu\Audio\audio_resampler.h" />
|
||||||
<ClInclude Include="Emu\Audio\audio_device_enumerator.h" />
|
<ClInclude Include="Emu\Audio\audio_device_enumerator.h" />
|
||||||
<ClInclude Include="Emu\Audio\FAudio\FAudioBackend.h">
|
<ClInclude Include="Emu\Audio\FAudio\FAudioBackend.h">
|
||||||
|
@ -1171,6 +1171,9 @@
|
|||||||
<ClCompile Include="Emu\RSX\Core\RSXContext.cpp">
|
<ClCompile Include="Emu\RSX\Core\RSXContext.cpp">
|
||||||
<Filter>Emu\GPU\RSX\Core</Filter>
|
<Filter>Emu\GPU\RSX\Core</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="Crypto\unzip.cpp">
|
||||||
|
<Filter>Crypto</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="Crypto\aes.h">
|
<ClInclude Include="Crypto\aes.h">
|
||||||
@ -2383,6 +2386,9 @@
|
|||||||
<ClInclude Include="Emu\RSX\Core\RSXContext.h">
|
<ClInclude Include="Emu\RSX\Core\RSXContext.h">
|
||||||
<Filter>Emu\GPU\RSX\Core</Filter>
|
<Filter>Emu\GPU\RSX\Core</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="Crypto\unzip.h">
|
||||||
|
<Filter>Crypto</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="Emu\RSX\Program\GLSLSnippets\GPUDeswizzle.glsl">
|
<None Include="Emu\RSX\Program\GLSLSnippets\GPUDeswizzle.glsl">
|
||||||
|
@ -79,7 +79,7 @@
|
|||||||
<DisableSpecificWarnings>4577;4467;4281;%(DisableSpecificWarnings)</DisableSpecificWarnings>
|
<DisableSpecificWarnings>4577;4467;4281;%(DisableSpecificWarnings)</DisableSpecificWarnings>
|
||||||
<ObjectFileName>$(IntDir)</ObjectFileName>
|
<ObjectFileName>$(IntDir)</ObjectFileName>
|
||||||
<Optimization>MaxSpeed</Optimization>
|
<Optimization>MaxSpeed</Optimization>
|
||||||
<PreprocessorDefinitions>_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)</PreprocessorDefinitions>
|
<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)</PreprocessorDefinitions>
|
||||||
<PreprocessToFile>false</PreprocessToFile>
|
<PreprocessToFile>false</PreprocessToFile>
|
||||||
<ProgramDataBaseFileName>$(IntDir)vc$(PlatformToolsetVersion).pdb</ProgramDataBaseFileName>
|
<ProgramDataBaseFileName>$(IntDir)vc$(PlatformToolsetVersion).pdb</ProgramDataBaseFileName>
|
||||||
<RuntimeTypeInfo>true</RuntimeTypeInfo>
|
<RuntimeTypeInfo>true</RuntimeTypeInfo>
|
||||||
@ -132,7 +132,7 @@
|
|||||||
<DisableSpecificWarnings>4577;4467;4281;%(DisableSpecificWarnings)</DisableSpecificWarnings>
|
<DisableSpecificWarnings>4577;4467;4281;%(DisableSpecificWarnings)</DisableSpecificWarnings>
|
||||||
<ObjectFileName>$(IntDir)</ObjectFileName>
|
<ObjectFileName>$(IntDir)</ObjectFileName>
|
||||||
<Optimization>Disabled</Optimization>
|
<Optimization>Disabled</Optimization>
|
||||||
<PreprocessorDefinitions>_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)</PreprocessorDefinitions>
|
<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)</PreprocessorDefinitions>
|
||||||
<PreprocessToFile>false</PreprocessToFile>
|
<PreprocessToFile>false</PreprocessToFile>
|
||||||
<RuntimeTypeInfo>true</RuntimeTypeInfo>
|
<RuntimeTypeInfo>true</RuntimeTypeInfo>
|
||||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include "gui_settings.h"
|
#include "gui_settings.h"
|
||||||
#include "syntax_highlighter.h"
|
#include "syntax_highlighter.h"
|
||||||
#include "config_checker.h"
|
#include "config_checker.h"
|
||||||
|
#include "Crypto/unzip.h"
|
||||||
|
|
||||||
#include <QActionGroup>
|
#include <QActionGroup>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
@ -145,7 +146,7 @@ void log_viewer::show_context_menu(const QPoint& pos)
|
|||||||
|
|
||||||
connect(open, &QAction::triggered, this, [this]()
|
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())
|
if (file_path.isEmpty())
|
||||||
return;
|
return;
|
||||||
m_path_last = file_path;
|
m_path_last = file_path;
|
||||||
@ -154,14 +155,27 @@ void log_viewer::show_context_menu(const QPoint& pos)
|
|||||||
|
|
||||||
connect(save, &QAction::triggered, this, [this]()
|
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())
|
if (file_path.isEmpty())
|
||||||
return;
|
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());
|
const QByteArray bytes = m_log_text->toPlainText().toUtf8();
|
||||||
log_file.close();
|
|
||||||
|
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);
|
gui_log.success("Exported filtered log to file '%s'", file_path);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -230,24 +244,43 @@ void log_viewer::show_log()
|
|||||||
m_log_text->setPlainText(tr("Loading file..."));
|
m_log_text->setPlainText(tr("Loading file..."));
|
||||||
QApplication::processEvents();
|
QApplication::processEvents();
|
||||||
|
|
||||||
if (QFile file(m_path_last);
|
bool failed = false;
|
||||||
file.exists() && file.open(QIODevice::ReadOnly | QIODevice::Text))
|
|
||||||
{
|
|
||||||
m_gui_settings->SetValue(gui::fd_log_viewer, m_path_last);
|
|
||||||
|
|
||||||
|
if (m_path_last.endsWith(".gz"))
|
||||||
|
{
|
||||||
|
if (fs::file file{m_path_last.toStdString()})
|
||||||
|
{
|
||||||
|
const std::vector<u8> decompressed = unzip(file.to_vector<u8>());
|
||||||
|
m_full_log = QString::fromUtf8(reinterpret_cast<const char*>(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.
|
// 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.
|
// In Qt 5.15.2 this was much faster than QFile::readAll. Use QTextStream again once this bug is fixed upstream.
|
||||||
//QTextStream stream(&file);
|
//QTextStream stream(&file);
|
||||||
//m_full_log = stream.readAll();
|
//m_full_log = stream.readAll();
|
||||||
m_full_log = file.readAll();
|
m_full_log = file.readAll();
|
||||||
m_full_log.replace('\0', '0');
|
|
||||||
file.close();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failed)
|
||||||
{
|
{
|
||||||
gui_log.error("log_viewer: Failed to open %s", m_path_last);
|
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));
|
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();
|
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();
|
const QString suffix = QFileInfo(urls[0].fileName()).suffix().toLower();
|
||||||
|
|
||||||
if (suffix == "log")
|
if (suffix == "log" || suffix == "gz")
|
||||||
{
|
{
|
||||||
if (save)
|
if (save)
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user