diff --git a/.github/workflows/build-ubuntu-aarch64.yml b/.github/workflows/build-ubuntu-aarch64.yml
index c06b50ed..096bc0b9 100644
--- a/.github/workflows/build-ubuntu-aarch64.yml
+++ b/.github/workflows/build-ubuntu-aarch64.yml
@@ -33,7 +33,7 @@ jobs:
rm /etc/apt/sources.list
mv /etc/apt/sources.list{.new,}
apt update
- DEBIAN_FRONTEND=noninteractive apt install -y {gcc-10,g++-10,pkg-config}-aarch64-linux-gnu {libsdl2,qtbase5,qtbase5-private,qtmultimedia5,libslirp,libarchive}-dev:arm64 cmake extra-cmake-modules dpkg-dev
+ DEBIAN_FRONTEND=noninteractive apt install -y {gcc-10,g++-10,pkg-config}-aarch64-linux-gnu {libsdl2,qtbase5,qtbase5-private,qtmultimedia5,libslirp,libarchive,libzstd}-dev:arm64 zstd:arm64 cmake extra-cmake-modules dpkg-dev
- name: Configure
shell: bash
run: |
diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml
index 26f172ef..b6c50e9a 100644
--- a/.github/workflows/build-ubuntu.yml
+++ b/.github/workflows/build-ubuntu.yml
@@ -19,7 +19,7 @@ jobs:
run: |
sudo rm -f /etc/apt/sources.list.d/dotnetdev.list /etc/apt/sources.list.d/microsoft-prod.list
sudo apt update
- sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default qtbase5-private-dev qtmultimedia5-dev libslirp0 libslirp-dev libarchive-dev --allow-downgrades
+ sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default qtbase5-private-dev qtmultimedia5-dev libslirp0 libslirp-dev libarchive-dev zstd libzstd-dev --allow-downgrades
- name: Create build environment
run: mkdir ${{runner.workspace}}/build
- name: Configure
diff --git a/README.md b/README.md
index 7fca3ddf..1d3732fc 100644
--- a/README.md
+++ b/README.md
@@ -35,9 +35,9 @@ As for the rest, the interface should be pretty straightforward. If you have a q
### Linux
1. Install dependencies:
- * Ubuntu 22.04: `sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qtbase5-dev qtbase5-private-dev qtmultimedia5-dev libslirp-dev libarchive-dev`
- * Older Ubuntu: `sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default qtbase5-private-dev qtmultimedia5-dev libslirp-dev libarchive-dev`
- * Arch Linux: `sudo pacman -S base-devel cmake extra-cmake-modules git libpcap sdl2 qt5-base qt5-multimedia libslirp libarchive`
+ * Ubuntu 22.04: `sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qtbase5-dev qtbase5-private-dev qtmultimedia5-dev libslirp-dev libarchive-dev libzstd-dev`
+ * Older Ubuntu: `sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default qtbase5-private-dev qtmultimedia5-dev libslirp-dev libarchive-dev libzstd-dev`
+ * Arch Linux: `sudo pacman -S base-devel cmake extra-cmake-modules git libpcap sdl2 qt5-base qt5-multimedia libslirp libarchive zstd`
3. Download the melonDS repository and prepare:
```bash
git clone https://github.com/melonDS-emu/melonDS
@@ -64,7 +64,7 @@ As for the rest, the interface should be pretty straightforward. If you have a q
cd melonDS
```
#### Dynamic builds (with DLLs)
-5. Install dependencies: `pacman -S mingw-w64-x86_64-{cmake,SDL2,toolchain,qt5-base,qt5-svg,qt5-multimedia,libslirp,libarchive}`
+5. Install dependencies: `pacman -S mingw-w64-x86_64-{cmake,SDL2,toolchain,qt5-base,qt5-svg,qt5-multimedia,libslirp,libarchive,zstd}`
6. Compile:
```bash
cmake -B build
@@ -75,7 +75,7 @@ As for the rest, the interface should be pretty straightforward. If you have a q
If everything went well, melonDS and the libraries it needs should now be in the `dist` folder.
#### Static builds (without DLLs, standalone executable)
-5. Install dependencies: `pacman -S mingw-w64-x86_64-{cmake,SDL2,toolchain,qt5-static,libslirp,libarchive}`
+5. Install dependencies: `pacman -S mingw-w64-x86_64-{cmake,SDL2,toolchain,qt5-static,libslirp,libarchive,zstd}`
6. Compile:
```bash
cmake -B build -DBUILD_STATIC=ON -DCMAKE_PREFIX_PATH=/mingw64/qt5-static
@@ -85,7 +85,7 @@ If everything went well, melonDS should now be in the `build` folder.
### macOS
1. Install the [Homebrew Package Manager](https://brew.sh)
-2. Install dependencies: `brew install git pkg-config cmake sdl2 qt@6 libslirp libarchive`
+2. Install dependencies: `brew install git pkg-config cmake sdl2 qt@6 libslirp libarchive zstd`
3. Download the melonDS repository and prepare:
```zsh
git clone https://github.com/melonDS-emu/melonDS
diff --git a/res/melon.plist.in b/res/melon.plist.in
index 4c87eb97..3cfa3970 100644
--- a/res/melon.plist.in
+++ b/res/melon.plist.in
@@ -39,6 +39,10 @@
srl
dsi
ids
+ nds.zst
+ srl.zst
+ dsi.zst
+ ids.zst
CFBundleTypeRole
Viewer
@@ -50,6 +54,8 @@
gba
agb
+ gba.zst
+ agb.zst
CFBundleTypeRole
Viewer
diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt
index 0387c58e..00e4c036 100644
--- a/src/frontend/qt_sdl/CMakeLists.txt
+++ b/src/frontend/qt_sdl/CMakeLists.txt
@@ -82,6 +82,7 @@ endif()
pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2)
pkg_check_modules(Slirp REQUIRED IMPORTED_TARGET slirp)
pkg_check_modules(LibArchive REQUIRED IMPORTED_TARGET libarchive)
+pkg_check_modules(Zstd REQUIRED IMPORTED_TARGET libzstd)
fix_interface_includes(PkgConfig::SDL2 PkgConfig::Slirp PkgConfig::LibArchive)
@@ -154,7 +155,7 @@ else()
target_include_directories(melonDS PUBLIC ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
endif()
target_link_libraries(melonDS PRIVATE core)
-target_link_libraries(melonDS PRIVATE PkgConfig::SDL2 PkgConfig::Slirp PkgConfig::LibArchive)
+target_link_libraries(melonDS PRIVATE PkgConfig::SDL2 PkgConfig::Slirp PkgConfig::LibArchive PkgConfig::Zstd)
target_link_libraries(melonDS PRIVATE ${QT_LINK_LIBS} ${CMAKE_DL_LIBS})
if (UNIX)
diff --git a/src/frontend/qt_sdl/ROMManager.cpp b/src/frontend/qt_sdl/ROMManager.cpp
index cb671e2c..95337e11 100644
--- a/src/frontend/qt_sdl/ROMManager.cpp
+++ b/src/frontend/qt_sdl/ROMManager.cpp
@@ -22,6 +22,7 @@
#include
#include
+#include
#ifdef ARCHIVE_SUPPORT_ENABLED
#include "ArchiveUtil.h"
#endif
@@ -491,6 +492,27 @@ bool LoadBIOS()
return true;
}
+u32 DecompressROM(const u8* inContent, const u32 inSize, u8** outContent)
+{
+ u64 realSize = ZSTD_getFrameContentSize(inContent, inSize);
+
+ if (realSize == ZSTD_CONTENTSIZE_UNKNOWN || realSize == ZSTD_CONTENTSIZE_ERROR || realSize > 0x40000000)
+ {
+ return 0;
+ }
+
+ u8* realContent = new u8[realSize];
+ u64 decompressed = ZSTD_decompress(realContent, realSize, inContent, inSize);
+
+ if (ZSTD_isError(decompressed))
+ {
+ delete[] realContent;
+ return 0;
+ }
+
+ *outContent = realContent;
+ return realSize;
+}
bool LoadROM(QStringList filepath, bool reset)
{
@@ -533,6 +555,25 @@ bool LoadROM(QStringList filepath, bool reset)
fclose(f);
filelen = (u32)len;
+ if (filename.length() > 4 && filename.substr(filename.length() - 4) == ".zst")
+ {
+ u8* outContent = nullptr;
+ u32 decompressed = DecompressROM(filedata, len, &outContent);
+
+ if (decompressed > 0)
+ {
+ delete[] filedata;
+ filedata = outContent;
+ filelen = decompressed;
+ filename = filename.substr(0, filename.length() - 4);
+ }
+ else
+ {
+ delete[] filedata;
+ return false;
+ }
+ }
+
int pos = LastSep(filename);
if(pos != -1)
basepath = filename.substr(0, pos);
@@ -543,14 +584,14 @@ bool LoadROM(QStringList filepath, bool reset)
{
// file inside archive
- s32 lenread = Archive::ExtractFileFromArchive(filepath.at(0), filepath.at(1), &filedata, &filelen);
- if (lenread < 0) return false;
- if (!filedata) return false;
- if (lenread != filelen)
- {
- delete[] filedata;
- return false;
- }
+ s32 lenread = Archive::ExtractFileFromArchive(filepath.at(0), filepath.at(1), &filedata, &filelen);
+ if (lenread < 0) return false;
+ if (!filedata) return false;
+ if (lenread != filelen)
+ {
+ delete[] filedata;
+ return false;
+ }
std::string std_archivepath = filepath.at(0).toStdString();
basepath = std_archivepath.substr(0, LastSep(std_archivepath));
@@ -695,6 +736,25 @@ bool LoadGBAROM(QStringList filepath)
fclose(f);
filelen = (u32)len;
+ if (filename.length() > 4 && filename.substr(filename.length() - 4) == ".zst")
+ {
+ u8* outContent = nullptr;
+ u32 decompressed = DecompressROM(filedata, len, &outContent);
+
+ if (decompressed > 0)
+ {
+ delete[] filedata;
+ filedata = outContent;
+ filelen = decompressed;
+ filename = filename.substr(0, filename.length() - 4);
+ }
+ else
+ {
+ delete[] filedata;
+ return false;
+ }
+ }
+
int pos = LastSep(filename);
basepath = filename.substr(0, pos);
romname = filename.substr(pos+1);
diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp
index 518d5f5b..9200538c 100644
--- a/src/frontend/qt_sdl/main.cpp
+++ b/src/frontend/qt_sdl/main.cpp
@@ -146,7 +146,7 @@ const QStringList ArchiveExtensions
".tar.lz",
".tar.lzma", ".tlz",
".tar.lrz", ".tlrz",
- ".tar.lzo", ".tzo",
+ ".tar.lzo", ".tzo"
#endif
};
@@ -1568,9 +1568,23 @@ static bool SupportedArchiveByMimetype(const QMimeType& mimetype)
return MimeTypeInList(mimetype, ArchiveMimeTypes);
}
+static bool ZstdNdsRomByExtension(const QString& filename)
+{
+ if (filename.endsWith(".zst", Qt::CaseInsensitive))
+ return NdsRomByExtension(filename.left(filename.size() - 4));
+}
+
+static bool ZstdGbaRomByExtension(const QString& filename)
+{
+ if (filename.endsWith(".zst", Qt::CaseInsensitive))
+ return GbaRomByExtension(filename.left(filename.size() - 4));
+}
static bool FileIsSupportedFiletype(const QString& filename, bool insideArchive = false)
{
+ if (ZstdNdsRomByExtension(filename) || ZstdGbaRomByExtension(filename))
+ return true;
+
if (NdsRomByExtension(filename) || GbaRomByExtension(filename) || SupportedArchiveByExtension(filename))
return true;
@@ -2207,7 +2221,12 @@ void MainWindow::dropEvent(QDropEvent* event)
const auto matchMode = romInsideArchive ? QMimeDatabase::MatchExtension : QMimeDatabase::MatchDefault;
const QMimeType mimetype = QMimeDatabase().mimeTypeForFile(filename, matchMode);
- if (NdsRomByExtension(filename) || NdsRomByMimetype(mimetype))
+ bool isNdsRom = NdsRomByExtension(filename) || NdsRomByMimetype(mimetype);
+ bool isGbaRom = GbaRomByExtension(filename) || GbaRomByMimetype(mimetype);
+ isNdsRom |= ZstdNdsRomByExtension(filename);
+ isGbaRom |= ZstdGbaRomByExtension(filename);
+
+ if (isNdsRom)
{
if (!ROMManager::LoadROM(file, true))
{
@@ -2227,7 +2246,7 @@ void MainWindow::dropEvent(QDropEvent* event)
updateCartInserted(false);
}
- else if (GbaRomByExtension(filename) || GbaRomByMimetype(mimetype))
+ else if (isGbaRom)
{
if (!ROMManager::LoadGBAROM(file))
{
@@ -2452,14 +2471,25 @@ QStringList MainWindow::pickROM(bool gba)
const QString console = gba ? "GBA" : "DS";
const QStringList& romexts = gba ? GbaRomExtensions : NdsRomExtensions;
- static const QString filterSuffix = ArchiveExtensions.empty()
- ? ");;Any file (*.*)"
- : " *" + ArchiveExtensions.join(" *") + ");;Any file (*.*)";
+ QString rawROMs = romexts.join(" *");
+ QString extraFilters = ";;" + console + " ROMs (*" + rawROMs;
+ QString allROMs = rawROMs;
+
+ QString zstdROMs = "*" + romexts.join(".zst *") + ".zst";
+ extraFilters += ");;Zstandard-compressed " + console + " ROMs (" + zstdROMs + ")";
+ allROMs += " " + zstdROMs;
+
+#ifdef ARCHIVE_SUPPORT_ENABLED
+ QString archives = "*" + ArchiveExtensions.join(" *");
+ extraFilters += ";;Archives (" + archives + ")";
+ allROMs += " " + archives;
+#endif
+ extraFilters += ";;All files (*.*)";
const QString filename = QFileDialog::getOpenFileName(
this, "Open " + console + " ROM",
QString::fromStdString(Config::LastROMFolder),
- console + " ROMs (*" + romexts.join(" *") + filterSuffix
+ "All supported files (*" + allROMs + ")" + extraFilters
);
if (filename.isEmpty()) return {};