Merge pull request #678 from WaluigiWare64/feature/zip-support

Add support for loading ROMs from a variety of compressed files
This commit is contained in:
Arisotura
2021-01-07 18:30:12 +01:00
committed by GitHub
10 changed files with 230 additions and 14 deletions

View File

@ -21,7 +21,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
working-directory: ${{runner.workspace}} working-directory: ${{runner.workspace}}
run: | run: |
brew install cmake sdl2 qt5 libslirp brew install cmake sdl2 qt5 libslirp libarchive
- name: Create build environment - name: Create build environment
run: mkdir ${{runner.workspace}}/build run: mkdir ${{runner.workspace}}/build
- name: Configure - name: Configure

View File

@ -36,7 +36,7 @@ jobs:
sudo mv /etc/apt/sources.list{.new,} sudo mv /etc/apt/sources.list{.new,}
sudo apt update sudo apt update
sudo apt install aptitude sudo apt install aptitude
sudo aptitude install -y {gcc-10,g++-10,pkg-config}-aarch64-linux-gnu libsdl2-dev:arm64 qtbase5-dev:arm64 libslirp-dev:arm64 sudo aptitude install -y {gcc-10,g++-10,pkg-config}-aarch64-linux-gnu libsdl2-dev:arm64 qtbase5-dev:arm64 libslirp-dev:arm64 libarchive-dev:arm64
- name: Create build environment - name: Create build environment
run: mkdir ${{runner.workspace}}/build run: mkdir ${{runner.workspace}}/build
- name: Configure - name: Configure

View File

@ -19,7 +19,7 @@ jobs:
run: | run: |
sudo rm -f /etc/apt/sources.list.d/dotnetdev.list /etc/apt/sources.list.d/microsoft-prod.list sudo rm -f /etc/apt/sources.list.d/dotnetdev.list /etc/apt/sources.list.d/microsoft-prod.list
sudo apt update sudo apt update
sudo apt install cmake libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default libslirp0 libslirp-dev --allow-downgrades sudo apt install cmake libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default libslirp0 libslirp-dev libarchive-dev --allow-downgrades
- name: Create build environment - name: Create build environment
run: mkdir ${{runner.workspace}}/build run: mkdir ${{runner.workspace}}/build
- name: Configure - name: Configure

View File

@ -24,7 +24,7 @@ jobs:
choco install msys2 choco install msys2
C:\tools\msys64\usr\bin\bash.exe -lc "pacman -Syuq --noconfirm" C:\tools\msys64\usr\bin\bash.exe -lc "pacman -Syuq --noconfirm"
- name: Install dependencies - name: Install dependencies
run: C:\tools\msys64\usr\bin\bash.exe -lc "pacman -Sq --noconfirm git make mingw-w64-x86_64-{cmake,mesa,SDL2,qt5-static,libslirp,toolchain}" run: C:\tools\msys64\usr\bin\bash.exe -lc "pacman -Sq --noconfirm git make mingw-w64-x86_64-{cmake,mesa,SDL2,qt5-static,libslirp,libarchive,toolchain}"
- name: Create build environment - name: Create build environment
run: | run: |
New-Item -ItemType directory -Path ${{runner.workspace}}\melonDS\build New-Item -ItemType directory -Path ${{runner.workspace}}\melonDS\build

View File

@ -38,7 +38,7 @@ As for the rest, the interface should be pretty straightforward. If you have a q
* Install dependencies: * Install dependencies:
```sh ```sh
sudo apt-get install cmake libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qtbase5-dev qtdeclarative5-dev libslirp-dev sudo apt-get install cmake libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qtbase5-dev qtdeclarative5-dev libslirp-dev libarchive-dev
``` ```
* Compile: * Compile:
@ -57,7 +57,7 @@ make -j$(nproc --all)
3. Update the packages using `pacman -Syu` and reopen the terminal if it asks you to 3. Update the packages using `pacman -Syu` and reopen the terminal if it asks you to
#### Dynamic builds (with DLLs) #### Dynamic builds (with DLLs)
4. Install dependencies: `pacman -S git make mingw-w64-x86_64-{cmake,mesa,SDL2,toolchain,qt5,libslirp}` 4. Install dependencies: `pacman -S git make mingw-w64-x86_64-{cmake,mesa,SDL2,toolchain,qt5,libslirp,libarchive}`
5. Run the following commands 5. Run the following commands
```bash ```bash
git clone https://github.com/Arisotura/melonDS.git git clone https://github.com/Arisotura/melonDS.git
@ -71,7 +71,7 @@ make -j$(nproc --all)
If everything went well, melonDS and the libraries it needs should now be in the `dist` folder. If everything went well, melonDS and the libraries it needs should now be in the `dist` folder.
#### Static builds (without DLLs, standalone executable) #### Static builds (without DLLs, standalone executable)
4. Install dependencies: `pacman -S git make mingw-w64-x86_64-{cmake,mesa,SDL2,toolchain,qt5-static,libslirp}` 4. Install dependencies: `pacman -S git make mingw-w64-x86_64-{cmake,mesa,SDL2,toolchain,qt5-static,libslirp,libarchive}`
5. Run the following commands 5. Run the following commands
```bash ```bash
git clone https://github.com/Arisotura/melonDS.git git clone https://github.com/Arisotura/melonDS.git
@ -86,7 +86,7 @@ If everything went well, melonDS should now be in the `dist` folder.
### macOS: ### macOS:
1. Install the [Homebrew Package Manager](https://brew.sh) 1. Install the [Homebrew Package Manager](https://brew.sh)
2. Install dependencies: `brew install git pkg-config cmake sdl2 qt5 libslirp` 2. Install dependencies: `brew install git pkg-config cmake sdl2 qt5 libslirp libarchive`
3. Compile: 3. Compile:
```zsh ```zsh
git clone https://github.com/Arisotura/melonDS.git git clone https://github.com/Arisotura/melonDS.git
@ -129,4 +129,4 @@ If everything went well, melonDS.app should now be in the `dist` folder.
melonDS is free software: you can redistribute it and/or modify melonDS is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.

View File

@ -0,0 +1,109 @@
/*
Copyright 2016-2020 Arisotura, WaluigiWare64
This file is part of melonDS.
melonDS is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
#include "ArchiveUtil.h"
#ifdef _WIN32
#include <direct.h>
#define mkdir(dir, mode) _mkdir(dir)
#endif
namespace Archive
{
QVector<QString> ListArchive(const char* path)
{
struct archive *a;
struct archive_entry *entry;
int r;
QVector<QString> fileList = {"OK"};
a = archive_read_new();
archive_read_support_filter_all(a);
archive_read_support_format_all(a);
r = archive_read_open_filename(a, path, 10240);
if (r != ARCHIVE_OK)
{
return QVector<QString> {"Err"};
}
while (archive_read_next_header(a, &entry) == ARCHIVE_OK)
{
fileList.push_back(archive_entry_pathname(entry));
archive_read_data_skip(a);
}
archive_read_close(a);
archive_read_free(a);
if (r != ARCHIVE_OK)
{
return QVector<QString> {"Err"};
}
return fileList;
}
QVector<QString> ExtractFileFromArchive(const char* path, const char* wantedFile)
{
struct archive *a = archive_read_new();
struct archive_entry *entry;
int r;
archive_read_support_format_all(a);
archive_read_support_filter_all(a);
r = archive_read_open_filename(a, path, 10240);
if (r != ARCHIVE_OK)
{
return QVector<QString> {"Err"};
}
while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
if (wantedFile == nullptr)
{
break;
}
if (strcmp(wantedFile, archive_entry_pathname(entry)) == 0)
{
break;
}
}
size_t bytesToWrite = archive_entry_size(entry);
auto archiveBuffer = std::make_unique<u8[]>(bytesToWrite);
ssize_t bytesRead = archive_read_data(a, archiveBuffer.get(), bytesToWrite);
if (bytesRead < 0)
{
printf(archive_error_string(a));
archiveBuffer.reset(nullptr);
return QVector<QString> {"Err", archive_error_string(a)};
}
QString nameToWrite = QFileInfo(path).absolutePath() + "/" + QFileInfo(path).baseName() + "/" + archive_entry_pathname(entry);
mkdir(QFileInfo(path).baseName().toUtf8().constData(), 600); // Create directory otherwise fopen will not open the file
FILE* fileToWrite = fopen(nameToWrite.toUtf8().constData(), "wb");
fwrite((char*)archiveBuffer.get(), bytesToWrite, 1, fileToWrite);
fclose(fileToWrite);
archiveBuffer.reset(nullptr);
archive_read_close(a);
archive_read_free(a);
return QVector<QString> {nameToWrite};
}
}

View File

@ -0,0 +1,25 @@
#ifndef ARCHIVEUTIL_H
#define ARCHIVEUTIL_H
#include <stdio.h>
#include <string>
#include <memory>
#include <QVector>
#include <QDir>
#include <archive.h>
#include <archive_entry.h>
#include "types.h"
namespace Archive
{
QVector<QString> ListArchive(const char* path);
QVector<QString> ExtractFileFromArchive(const char* path, const char* wantedFile);
}
#endif // ARCHIVEUTIL_H

View File

@ -17,6 +17,9 @@ SET(SOURCES_QT_SDL
font.h font.h
Platform.cpp Platform.cpp
PlatformConfig.cpp PlatformConfig.cpp
ArchiveUtil.h
ArchiveUtil.cpp
../Util_ROM.cpp ../Util_ROM.cpp
../Util_Video.cpp ../Util_Video.cpp
@ -51,6 +54,15 @@ find_package(Iconv REQUIRED)
pkg_check_modules(SDL2 REQUIRED sdl2) pkg_check_modules(SDL2 REQUIRED sdl2)
pkg_check_modules(SLIRP REQUIRED slirp) pkg_check_modules(SLIRP REQUIRED slirp)
if (APPLE)
# Find libarchive on macOS, because macOS only provides the library, not the headers
execute_process(COMMAND brew --prefix libarchive
OUTPUT_VARIABLE LIBARCHIVE_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE)
list(APPEND CMAKE_PREFIX_PATH "${LIBARCHIVE_DIR}")
endif()
pkg_check_modules(LIBARCHIVE REQUIRED libarchive)
if (WIN32 AND (CMAKE_BUILD_TYPE STREQUAL Release)) if (WIN32 AND (CMAKE_BUILD_TYPE STREQUAL Release))
add_executable(melonDS WIN32 ${SOURCES_QT_SDL}) add_executable(melonDS WIN32 ${SOURCES_QT_SDL})
else() else()
@ -61,15 +73,16 @@ target_link_libraries(melonDS ${CMAKE_THREAD_LIBS_INIT})
target_include_directories(melonDS PRIVATE ${SDL2_INCLUDE_DIRS}) target_include_directories(melonDS PRIVATE ${SDL2_INCLUDE_DIRS})
target_include_directories(melonDS PRIVATE ${SLIRP_INCLUDE_DIRS}) target_include_directories(melonDS PRIVATE ${SLIRP_INCLUDE_DIRS})
target_include_directories(melonDS PRIVATE ${LIBARCHIVE_INCLUDE_DIRS})
target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..") target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..")
target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../..") target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../..")
target_link_libraries(melonDS core) target_link_libraries(melonDS core)
if (BUILD_STATIC) if (BUILD_STATIC)
target_link_libraries(melonDS -static ${SDL2_STATIC_LIBRARIES} ${SLIRP_STATIC_LIBRARIES}) target_link_libraries(melonDS -static ${SDL2_STATIC_LIBRARIES} ${SLIRP_STATIC_LIBRARIES} ${LIBARCHIVE_STATIC_LIBRARIES})
else() else()
target_link_libraries(melonDS ${SDL2_LIBRARIES} ${SLIRP_LIBRARIES}) target_link_libraries(melonDS ${SDL2_LIBRARIES} ${SLIRP_LIBRARIES} ${LIBARCHIVE_LIBRARIES})
endif() endif()
if (NOT Iconv_IS_BUILT_IN) if (NOT Iconv_IS_BUILT_IN)
@ -85,7 +98,7 @@ elseif (WIN32)
target_link_libraries(melonDS comctl32 d2d1 dwrite uxtheme ws2_32 iphlpapi gdi32) target_link_libraries(melonDS comctl32 d2d1 dwrite uxtheme ws2_32 iphlpapi gdi32)
if (BUILD_STATIC) if (BUILD_STATIC)
target_link_libraries(melonDS imm32 winmm version setupapi -static Qt5::Core Qt5::Gui Qt5::Widgets z zstd) target_link_libraries(melonDS imm32 winmm version setupapi -static Qt5::Core Qt5::Gui Qt5::Widgets zstd)
else() else()
target_link_libraries(melonDS Qt5::Core Qt5::Gui Qt5::Widgets) target_link_libraries(melonDS Qt5::Core Qt5::Gui Qt5::Widgets)
endif() endif()

View File

@ -21,14 +21,20 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <vector>
#include <string>
#include <algorithm>
#include <QApplication> #include <QApplication>
#include <QMessageBox> #include <QMessageBox>
#include <QMenuBar> #include <QMenuBar>
#include <QFileDialog> #include <QFileDialog>
#include <QInputDialog>
#include <QPaintEvent> #include <QPaintEvent>
#include <QPainter> #include <QPainter>
#include <QKeyEvent> #include <QKeyEvent>
#include <QMimeData> #include <QMimeData>
#include <QVector>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
@ -63,6 +69,7 @@
#include "main_shaders.h" #include "main_shaders.h"
#include "ArchiveUtil.h"
// TODO: uniform variable spelling // TODO: uniform variable spelling
@ -1020,6 +1027,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
actOpenROM = menu->addAction("Open ROM..."); actOpenROM = menu->addAction("Open ROM...");
connect(actOpenROM, &QAction::triggered, this, &MainWindow::onOpenFile); connect(actOpenROM, &QAction::triggered, this, &MainWindow::onOpenFile);
actOpenROMArchive = menu->addAction("Open ROM inside Archive...");
connect(actOpenROMArchive, &QAction::triggered, this, &MainWindow::onOpenFileArchive);
recentMenu = menu->addMenu("Open Recent"); recentMenu = menu->addMenu("Open Recent");
for(int i = 0; i < 10; ++i) for(int i = 0; i < 10; ++i)
@ -1462,7 +1472,7 @@ void MainWindow::loadROM(QString filename)
recentFileList.removeAll(filename); recentFileList.removeAll(filename);
recentFileList.prepend(filename); recentFileList.prepend(filename);
updateRecentFilesMenu(); updateRecentFilesMenu();
// TODO: validate the input file!! // TODO: validate the input file!!
// * check that it is a proper ROM // * check that it is a proper ROM
// * ensure the binary offsets are sane // * ensure the binary offsets are sane
@ -1515,7 +1525,7 @@ void MainWindow::onOpenFile()
QString filename = QFileDialog::getOpenFileName(this, QString filename = QFileDialog::getOpenFileName(this,
"Open ROM", "Open ROM",
Config::LastROMFolder, Config::LastROMFolder,
"DS ROMs (*.nds *.dsi *.srl);;GBA ROMs (*.gba);;Any file (*.*)"); "DS ROMs (*.nds *.dsi *.srl);;GBA ROMs (*.gba *.zip);;Any file (*.*)");
if (filename.isEmpty()) if (filename.isEmpty())
{ {
emuThread->emuUnpause(); emuThread->emuUnpause();
@ -1525,6 +1535,63 @@ void MainWindow::onOpenFile()
loadROM(filename); loadROM(filename);
} }
void MainWindow::onOpenFileArchive()
{
emuThread->emuPause();
QString filename = QFileDialog::getOpenFileName(this,
"Open ROM Archive",
Config::LastROMFolder,
"Archived ROMs (*.zip *.7z *.rar *.tar *.tar.gz *.tar.xz *.tar.bz2);;Any file (*.*)");
if (filename.isEmpty())
{
emuThread->emuUnpause();
return;
}
printf("Finding list of ROMs...\n");
QVector<QString> archiveROMList = Archive::ListArchive(filename.toUtf8().constData());
if (archiveROMList.size() > 2)
{
archiveROMList.removeFirst();
QString toLoad = QInputDialog::getItem(this, "melonDS",
"The archive was found to have multiple files. Select which ROM you want to load.", archiveROMList.toList(), 0, false);
printf("Extracting '%s'\n", toLoad.toUtf8().constData());
QVector<QString> extractResult = Archive::ExtractFileFromArchive(filename.toUtf8().constData(), toLoad.toUtf8().constData());
if (extractResult[0] != QString("Err"))
{
filename = extractResult[0];
}
else
{
QMessageBox::critical(this, "melonDS", QString("There was an error while trying to extract the ROM from the archive: ") + extractResult[1]);
}
}
else if (archiveROMList.size() == 2)
{
printf("Extracting the only ROM in archive\n");
QVector<QString> extractResult = Archive::ExtractFileFromArchive(filename.toUtf8().constData(), nullptr);
if (extractResult[0] != QString("Err"))
{
filename = extractResult[0];
}
else
{
QMessageBox::critical(this, "melonDS", QString("There was an error while trying to extract the ROM from the archive: ") + extractResult[1]);
}
}
else if ((archiveROMList.size() == 1) && (archiveROMList[0] == QString("OK")))
{
QMessageBox::warning(this, "melonDS", "The archive is intact, but there are no files inside.");
}
else
{
QMessageBox::critical(this, "melonDS", "The archive could not be read. It may be corrupt or you don't have the permissions.");
}
loadROM(filename);
}
void MainWindow::onClearRecentFiles() void MainWindow::onClearRecentFiles()
{ {
recentFileList.clear(); recentFileList.clear();

View File

@ -191,6 +191,7 @@ signals:
private slots: private slots:
void onOpenFile(); void onOpenFile();
void onOpenFileArchive();
void onClickRecentFile(); void onClickRecentFile();
void onClearRecentFiles(); void onClearRecentFiles();
void onBootFirmware(); void onBootFirmware();
@ -251,6 +252,7 @@ public:
QWidget* panel; QWidget* panel;
QAction* actOpenROM; QAction* actOpenROM;
QAction* actOpenROMArchive;
QAction* actBootFirmware; QAction* actBootFirmware;
QAction* actSaveState[9]; QAction* actSaveState[9];
QAction* actLoadState[9]; QAction* actLoadState[9];