diff --git a/.gitmodules b/.gitmodules
index b62d8c35a0..04888bdf04 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -30,3 +30,6 @@
[submodule "3rdparty/xz/xz"]
path = 3rdparty/xz/xz
url = https://github.com/tukaani-project/xz.git
+[submodule "3rdparty/lz4/lz4"]
+ path = 3rdparty/lz4/lz4
+ url = https://github.com/lz4/lz4
diff --git a/3rdparty/lz4/CMakeLists.txt b/3rdparty/lz4/CMakeLists.txt
new file mode 100644
index 0000000000..d06b487346
--- /dev/null
+++ b/3rdparty/lz4/CMakeLists.txt
@@ -0,0 +1,13 @@
+add_library(pcsx2-lz4
+ lz4/lib/lz4.c
+ lz4/lib/lz4.h
+)
+
+target_include_directories(pcsx2-lz4 PUBLIC lz4/lib)
+target_compile_definitions(pcsx2-lz4 PUBLIC
+ LZ4LIB_VISIBILITY=
+)
+
+add_library(LZ4::LZ4 ALIAS pcsx2-lz4)
+
+disable_compiler_warnings_for_target(pcsx2-lz4)
diff --git a/3rdparty/lz4/lz4 b/3rdparty/lz4/lz4
new file mode 160000
index 0000000000..b8fd2d1530
--- /dev/null
+++ b/3rdparty/lz4/lz4
@@ -0,0 +1 @@
+Subproject commit b8fd2d15309dd4e605070bd4486e26b6ef814e29
diff --git a/3rdparty/lz4/lz4.vcxproj b/3rdparty/lz4/lz4.vcxproj
new file mode 100644
index 0000000000..65e8e9c67e
--- /dev/null
+++ b/3rdparty/lz4/lz4.vcxproj
@@ -0,0 +1,46 @@
+
+
+
+
+
+ {39098635-446A-4FC3-9B1C-8609D94598A8}
+
+
+
+ StaticLibrary
+ $(DefaultPlatformToolset)
+ ClangCL
+ true
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+ AllRules.ruleset
+
+
+
+ LZ4LIB_VISIBILITY=;%(PreprocessorDefinitions)
+ %(AdditionalIncludeDirectories);$(ProjectDir)\lz4\lib
+ TurnOffAllWarnings
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PCSX2_qt.sln b/PCSX2_qt.sln
index bafbd7a08d..b553b4b0a4 100644
--- a/PCSX2_qt.sln
+++ b/PCSX2_qt.sln
@@ -63,6 +63,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "demangler", "3rdparty\deman
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libwebp", "3rdparty\libwebp\libwebp.vcxproj", "{522DAF2A-1F24-4742-B2C4-A956411F6AB2}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lz4", "3rdparty\lz4\lz4.vcxproj", "{39098635-446A-4FC3-9B1C-8609D94598A8}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug AVX2|x64 = Debug AVX2|x64
@@ -763,6 +765,30 @@ Global
{522DAF2A-1F24-4742-B2C4-A956411F6AB2}.Release Clang|x64.Build.0 = Release Clang|x64
{522DAF2A-1F24-4742-B2C4-A956411F6AB2}.Release|x64.ActiveCfg = Release|x64
{522DAF2A-1F24-4742-B2C4-A956411F6AB2}.Release|x64.Build.0 = Release|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Debug AVX2|x64.ActiveCfg = Debug|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Debug AVX2|x64.Build.0 = Debug|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Debug Clang AVX2|x64.ActiveCfg = Debug Clang|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Debug Clang AVX2|x64.Build.0 = Debug Clang|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Debug Clang|x64.ActiveCfg = Debug Clang|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Debug Clang|x64.Build.0 = Debug Clang|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Debug|x64.ActiveCfg = Debug|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Debug|x64.Build.0 = Debug|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Devel AVX2|x64.ActiveCfg = Devel|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Devel AVX2|x64.Build.0 = Devel|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Devel Clang AVX2|x64.ActiveCfg = Devel Clang|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Devel Clang AVX2|x64.Build.0 = Devel Clang|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Devel Clang|x64.ActiveCfg = Devel Clang|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Devel Clang|x64.Build.0 = Devel Clang|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Devel|x64.ActiveCfg = Devel|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Devel|x64.Build.0 = Devel|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Release AVX2|x64.ActiveCfg = Release|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Release AVX2|x64.Build.0 = Release|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Release Clang AVX2|x64.ActiveCfg = Release Clang|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Release Clang AVX2|x64.Build.0 = Release Clang|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Release Clang|x64.ActiveCfg = Release Clang|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Release Clang|x64.Build.0 = Release Clang|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Release|x64.ActiveCfg = Release|x64
+ {39098635-446A-4FC3-9B1C-8609D94598A8}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -792,6 +818,7 @@ Global
{67D0160C-0FE4-44B9-AC2E-82BBCF4104DF} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
{1E3D706C-1D95-4E1B-BDF2-CA3D0007DF7F} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
{522DAF2A-1F24-4742-B2C4-A956411F6AB2} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
+ {39098635-446A-4FC3-9B1C-8609D94598A8} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0BC474EA-3628-45D3-9DBC-E22D0B7E0F77}
diff --git a/cmake/SearchForStuff.cmake b/cmake/SearchForStuff.cmake
index f605185b9e..7aa695032c 100644
--- a/cmake/SearchForStuff.cmake
+++ b/cmake/SearchForStuff.cmake
@@ -144,6 +144,7 @@ add_subdirectory(3rdparty/libzip EXCLUDE_FROM_ALL)
add_subdirectory(3rdparty/rcheevos EXCLUDE_FROM_ALL)
add_subdirectory(3rdparty/rapidjson EXCLUDE_FROM_ALL)
add_subdirectory(3rdparty/discord-rpc EXCLUDE_FROM_ALL)
+add_subdirectory(3rdparty/lz4 EXCLUDE_FROM_ALL)
if(USE_OPENGL)
add_subdirectory(3rdparty/glad EXCLUDE_FROM_ALL)
diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp
index 26abd76889..2f4fc88819 100644
--- a/pcsx2-qt/MainWindow.cpp
+++ b/pcsx2-qt/MainWindow.cpp
@@ -67,24 +67,26 @@
#endif
const char* MainWindow::OPEN_FILE_FILTER =
- QT_TRANSLATE_NOOP("MainWindow", "All File Types (*.bin *.iso *.cue *.mdf *.chd *.cso *.gz *.elf *.irx *.gs *.gs.xz *.gs.zst *.dump);;"
+ QT_TRANSLATE_NOOP("MainWindow", "All File Types (*.bin *.iso *.cue *.mdf *.chd *.cso *.zso *.gz *.elf *.irx *.gs *.gs.xz *.gs.zst *.dump);;"
"Single-Track Raw Images (*.bin *.iso);;"
"Cue Sheets (*.cue);;"
"Media Descriptor File (*.mdf);;"
"MAME CHD Images (*.chd);;"
"CSO Images (*.cso);;"
+ "ZSO Images (*.zso);;"
"GZ Images (*.gz);;"
"ELF Executables (*.elf);;"
"IRX Executables (*.irx);;"
"GS Dumps (*.gs *.gs.xz *.gs.zst);;"
"Block Dumps (*.dump)");
-const char* MainWindow::DISC_IMAGE_FILTER = QT_TRANSLATE_NOOP("MainWindow", "All File Types (*.bin *.iso *.cue *.mdf *.chd *.cso *.gz *.dump);;"
+const char* MainWindow::DISC_IMAGE_FILTER = QT_TRANSLATE_NOOP("MainWindow", "All File Types (*.bin *.iso *.cue *.mdf *.chd *.cso *.zso *.gz *.dump);;"
"Single-Track Raw Images (*.bin *.iso);;"
"Cue Sheets (*.cue);;"
"Media Descriptor File (*.mdf);;"
"MAME CHD Images (*.chd);;"
"CSO Images (*.cso);;"
+ "ZSO Images (*.zso);;"
"GZ Images (*.gz);;"
"Block Dumps (*.dump)");
diff --git a/pcsx2/CDVD/CsoFileReader.cpp b/pcsx2/CDVD/CsoFileReader.cpp
index 60c3241fcb..55ddc9d6e2 100644
--- a/pcsx2/CDVD/CsoFileReader.cpp
+++ b/pcsx2/CDVD/CsoFileReader.cpp
@@ -23,6 +23,7 @@
#include "common/StringUtil.h"
#include
+#include "lz4.h"
// Implementation of CSO compressed ISO reading, based on:
// https://github.com/unknownbrackets/maxcso/blob/master/README_CSO.md
@@ -52,7 +53,7 @@ CsoFileReader::~CsoFileReader()
bool CsoFileReader::CanHandle(const std::string& fileName, const std::string& displayName)
{
bool supported = false;
- if (StringUtil::EndsWith(displayName, ".cso"))
+ if (displayName.ends_with(".cso") || displayName.ends_with(".zso"))
{
FILE* fp = FileSystem::OpenCFile(fileName.c_str(), "rb");
CsoHeader hdr;
@@ -70,10 +71,10 @@ bool CsoFileReader::CanHandle(const std::string& fileName, const std::string& di
bool CsoFileReader::ValidateHeader(const CsoHeader& hdr, Error* error)
{
- if (hdr.magic[0] != 'C' || hdr.magic[1] != 'I' || hdr.magic[2] != 'S' || hdr.magic[3] != 'O')
+ if ((hdr.magic[0] != 'C' && hdr.magic[0] != 'Z') || hdr.magic[1] != 'I' || hdr.magic[2] != 'S' || hdr.magic[3] != 'O')
{
// Invalid magic, definitely a bad file.
- Error::SetString(error, "File is not a CHD.");
+ Error::SetString(error, "File is not a CSO or ZSO.");
return false;
}
if (hdr.ver > 1)
@@ -141,6 +142,9 @@ bool CsoFileReader::ReadFileHeader(Error* error)
m_indexShift = hdr.align;
m_totalSize = hdr.total_bytes;
+ // Check compression method (ZSO=lz4)
+ m_uselz4 = hdr.magic[0] == 'Z';
+
return true;
}
@@ -167,14 +171,18 @@ bool CsoFileReader::InitializeBuffers(Error* error)
return false;
}
- m_z_stream = std::make_unique();
- m_z_stream->zalloc = Z_NULL;
- m_z_stream->zfree = Z_NULL;
- m_z_stream->opaque = Z_NULL;
- if (inflateInit2(m_z_stream.get(), -15) != Z_OK)
+ // initialize zlib if not a ZSO
+ if (!m_uselz4)
{
- Error::SetString(error, "Unable to initialize zlib for CSO decompression.");
- return false;
+ m_z_stream = std::make_unique();
+ m_z_stream->zalloc = Z_NULL;
+ m_z_stream->zfree = Z_NULL;
+ m_z_stream->opaque = Z_NULL;
+ if (inflateInit2(m_z_stream.get(), -15) != Z_OK)
+ {
+ Error::SetString(error, "Unable to initialize zlib for CSO decompression.");
+ return false;
+ }
}
return true;
@@ -256,18 +264,34 @@ int CsoFileReader::ReadChunk(void* dst, s64 chunkID)
// This might be less bytes than frameRawSize in case of padding on the last frame.
// This is because the index positions must be aligned.
const u32 readRawBytes = fread(m_readBuffer.get(), 1, frameRawSize, m_src);
+ bool success = false;
- m_z_stream->next_in = m_readBuffer.get();
- m_z_stream->avail_in = readRawBytes;
- m_z_stream->next_out = static_cast(dst);
- m_z_stream->avail_out = m_frameSize;
+ if (m_uselz4)
+ {
+ const int src_size = static_cast(readRawBytes);
+ const int dst_size = static_cast(m_frameSize);
+ const char* src_buf = reinterpret_cast(m_readBuffer.get());
+ char* dst_buf = static_cast(dst);
+
+ const int res = LZ4_decompress_safe_partial(src_buf, dst_buf, src_size, dst_size, dst_size);
+ success = (res > 0);
+ }
+ else
+ {
+ m_z_stream->next_in = m_readBuffer.get();
+ m_z_stream->avail_in = readRawBytes;
+ m_z_stream->next_out = static_cast(dst);
+ m_z_stream->avail_out = m_frameSize;
- int status = inflate(m_z_stream.get(), Z_FINISH);
- bool success = status == Z_STREAM_END && m_z_stream->total_out == m_frameSize;
+ const int status = inflate(m_z_stream.get(), Z_FINISH);
+ success = (status == Z_STREAM_END && m_z_stream->total_out == m_frameSize);
+ }
if (!success)
- Console.Error("Unable to decompress CSO frame using zlib.");
- inflateReset(m_z_stream.get());
+ Console.Error(fmt::format("Unable to decompress CSO frame using {}", (m_uselz4)? "lz4":"zlib"));
+
+ if (!m_uselz4)
+ inflateReset(m_z_stream.get());
return success ? m_frameSize : 0;
}
diff --git a/pcsx2/CDVD/CsoFileReader.h b/pcsx2/CDVD/CsoFileReader.h
index 297dedb02e..6d035d512e 100644
--- a/pcsx2/CDVD/CsoFileReader.h
+++ b/pcsx2/CDVD/CsoFileReader.h
@@ -60,6 +60,7 @@ private:
u32 m_frameSize = 0;
u8 m_frameShift = 0;
u8 m_indexShift = 0;
+ bool m_uselz4 = false; // flag to enable LZ4 decompression (ZSO files)
std::unique_ptr m_readBuffer;
std::unique_ptr m_index;
diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt
index 525f556a5a..42dc3735d6 100644
--- a/pcsx2/CMakeLists.txt
+++ b/pcsx2/CMakeLists.txt
@@ -1136,6 +1136,7 @@ target_link_libraries(PCSX2_FLAGS INTERFACE
discord-rpc
SDL2::SDL2
ZLIB::ZLIB
+ LZ4::LZ4
SoundTouch::SoundTouch
PNG::PNG
LibLZMA::LibLZMA
diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp
index 144b0f6558..7ca3efb254 100644
--- a/pcsx2/ImGui/FullscreenUI.cpp
+++ b/pcsx2/ImGui/FullscreenUI.cpp
@@ -844,12 +844,12 @@ void FullscreenUI::DestroyResources()
ImGuiFullscreen::FileSelectorFilters FullscreenUI::GetOpenFileFilters()
{
- return {"*.bin", "*.iso", "*.cue", "*.mdf", "*.chd", "*.cso", "*.gz", "*.elf", "*.irx", "*.gs", "*.gs.xz", "*.gs.zst", "*.dump"};
+ return {"*.bin", "*.iso", "*.cue", "*.mdf", "*.chd", "*.cso", "*.zso", "*.gz", "*.elf", "*.irx", "*.gs", "*.gs.xz", "*.gs.zst", "*.dump"};
}
ImGuiFullscreen::FileSelectorFilters FullscreenUI::GetDiscImageFilters()
{
- return {"*.bin", "*.iso", "*.cue", "*.mdf", "*.chd", "*.cso", "*.gz"};
+ return {"*.bin", "*.iso", "*.cue", "*.mdf", "*.chd", "*.cso", "*.zso", "*.gz"};
}
void FullscreenUI::DoStartPath(const std::string& path, std::optional state_index, std::optional fast_boot)
diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp
index d4027578ed..cd2056cc24 100644
--- a/pcsx2/VMManager.cpp
+++ b/pcsx2/VMManager.cpp
@@ -2112,7 +2112,7 @@ bool VMManager::IsSaveStateFileName(const std::string_view& path)
bool VMManager::IsDiscFileName(const std::string_view& path)
{
- static const char* extensions[] = {".iso", ".bin", ".img", ".mdf", ".gz", ".cso", ".chd"};
+ static const char* extensions[] = {".iso", ".bin", ".img", ".mdf", ".gz", ".cso", ".zso", ".chd"};
for (const char* test_extension : extensions)
{
diff --git a/pcsx2/pcsx2.vcxproj b/pcsx2/pcsx2.vcxproj
index 4f520f1685..a659612d18 100644
--- a/pcsx2/pcsx2.vcxproj
+++ b/pcsx2/pcsx2.vcxproj
@@ -38,6 +38,7 @@
%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\wil\include
%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\xz\xz\src\liblzma\api
%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\zlib
+ %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\lz4\lz4\lib
%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\libpng
%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\libchdr\include
%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\cubeb\include
@@ -64,7 +65,7 @@
Use
PrecompiledHeader.h
PrecompiledHeader.h;%(ForcedIncludeFiles)
- LZMA_API_STATIC;ST_NO_EXCEPTION_HANDLING;ENABLE_RAINTEGRATION;ENABLE_OPENGL;ENABLE_VULKAN;%(PreprocessorDefinitions)
+ LZMA_API_STATIC;LZ4LIB_VISIBILITY=;ST_NO_EXCEPTION_HANDLING;ENABLE_RAINTEGRATION;ENABLE_OPENGL;ENABLE_VULKAN;%(PreprocessorDefinitions)
XBYAK_NO_EXCEPTION;ZYCORE_STATIC_DEFINE;ZYDIS_STATIC_DEFINE;%(PreprocessorDefinitions)
$(IntDir)%(RelativeDir)
@@ -855,6 +856,9 @@
{7e183337-a7e9-460c-9d3d-568bc9f9bcc1}
+
+ {39098635-446a-4fc3-9b1c-8609d94598a8}
+
{95dd0a0c-d14d-4cff-a593-820ef26efcc8}