Implement resource packs
This commit is contained in:
parent
e6b2758ab4
commit
71d53c922f
|
@ -66,6 +66,7 @@
|
||||||
#define MEMORYWATCHER_DIR "MemoryWatcher"
|
#define MEMORYWATCHER_DIR "MemoryWatcher"
|
||||||
#define WFSROOT_DIR "WFS"
|
#define WFSROOT_DIR "WFS"
|
||||||
#define BACKUP_DIR "Backup"
|
#define BACKUP_DIR "Backup"
|
||||||
|
#define RESOURCEPACK_DIR "ResourcePacks"
|
||||||
|
|
||||||
// This one is only used to remove it if it was present
|
// This one is only used to remove it if it was present
|
||||||
#define SHADERCACHE_LEGACY_DIR "ShaderCache"
|
#define SHADERCACHE_LEGACY_DIR "ShaderCache"
|
||||||
|
|
|
@ -780,6 +780,7 @@ static void RebuildUserDirectories(unsigned int dir_index)
|
||||||
s_user_paths[D_PIPES_IDX] = s_user_paths[D_USER_IDX] + PIPES_DIR DIR_SEP;
|
s_user_paths[D_PIPES_IDX] = s_user_paths[D_USER_IDX] + PIPES_DIR DIR_SEP;
|
||||||
s_user_paths[D_WFSROOT_IDX] = s_user_paths[D_USER_IDX] + WFSROOT_DIR DIR_SEP;
|
s_user_paths[D_WFSROOT_IDX] = s_user_paths[D_USER_IDX] + WFSROOT_DIR DIR_SEP;
|
||||||
s_user_paths[D_BACKUP_IDX] = s_user_paths[D_USER_IDX] + BACKUP_DIR DIR_SEP;
|
s_user_paths[D_BACKUP_IDX] = s_user_paths[D_USER_IDX] + BACKUP_DIR DIR_SEP;
|
||||||
|
s_user_paths[D_RESOURCEPACK_IDX] = s_user_paths[D_USER_IDX] + RESOURCEPACK_DIR DIR_SEP;
|
||||||
s_user_paths[F_DOLPHINCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + DOLPHIN_CONFIG;
|
s_user_paths[F_DOLPHINCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + DOLPHIN_CONFIG;
|
||||||
s_user_paths[F_GCPADCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + GCPAD_CONFIG;
|
s_user_paths[F_GCPADCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + GCPAD_CONFIG;
|
||||||
s_user_paths[F_WIIPADCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + WIIPAD_CONFIG;
|
s_user_paths[F_WIIPADCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + WIIPAD_CONFIG;
|
||||||
|
|
|
@ -51,6 +51,7 @@ enum
|
||||||
D_MEMORYWATCHER_IDX,
|
D_MEMORYWATCHER_IDX,
|
||||||
D_WFSROOT_IDX,
|
D_WFSROOT_IDX,
|
||||||
D_BACKUP_IDX,
|
D_BACKUP_IDX,
|
||||||
|
D_RESOURCEPACK_IDX,
|
||||||
F_DOLPHINCONFIG_IDX,
|
F_DOLPHINCONFIG_IDX,
|
||||||
F_GCPADCONFIG_IDX,
|
F_GCPADCONFIG_IDX,
|
||||||
F_WIIPADCONFIG_IDX,
|
F_WIIPADCONFIG_IDX,
|
||||||
|
@ -210,4 +211,4 @@ void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmod
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace File
|
||||||
|
|
|
@ -107,6 +107,7 @@ add_executable(dolphin-emu
|
||||||
QtUtils/WinIconHelper.cpp
|
QtUtils/WinIconHelper.cpp
|
||||||
QtUtils/WrapInScrollArea.cpp
|
QtUtils/WrapInScrollArea.cpp
|
||||||
QtUtils/AspectRatioWidget.cpp
|
QtUtils/AspectRatioWidget.cpp
|
||||||
|
ResourcePackManager.cpp
|
||||||
Settings/AdvancedPane.cpp
|
Settings/AdvancedPane.cpp
|
||||||
Settings/AudioPane.cpp
|
Settings/AudioPane.cpp
|
||||||
Settings/GameCubePane.cpp
|
Settings/GameCubePane.cpp
|
||||||
|
|
|
@ -329,6 +329,7 @@
|
||||||
<ClCompile Include="FIFO\FIFOAnalyzer.cpp" />
|
<ClCompile Include="FIFO\FIFOAnalyzer.cpp" />
|
||||||
<ClCompile Include="FIFO\FIFOPlayerWindow.cpp" />
|
<ClCompile Include="FIFO\FIFOPlayerWindow.cpp" />
|
||||||
<ClCompile Include="QtUtils\WinIconHelper.cpp" />
|
<ClCompile Include="QtUtils\WinIconHelper.cpp" />
|
||||||
|
<ClCompile Include="ResourcePackManager.cpp" />
|
||||||
<ClCompile Include="TAS\GCTASInputWindow.cpp" />
|
<ClCompile Include="TAS\GCTASInputWindow.cpp" />
|
||||||
<ClCompile Include="TAS\WiiTASInputWindow.cpp" />
|
<ClCompile Include="TAS\WiiTASInputWindow.cpp" />
|
||||||
<ClCompile Include="TAS\TASInputWindow.cpp" />
|
<ClCompile Include="TAS\TASInputWindow.cpp" />
|
||||||
|
@ -390,6 +391,7 @@
|
||||||
<ClInclude Include="QtUtils\QueueOnObject.h" />
|
<ClInclude Include="QtUtils\QueueOnObject.h" />
|
||||||
<ClInclude Include="QtUtils\RunOnObject.h" />
|
<ClInclude Include="QtUtils\RunOnObject.h" />
|
||||||
<ClInclude Include="QtUtils\WinIconHelper.h" />
|
<ClInclude Include="QtUtils\WinIconHelper.h" />
|
||||||
|
<ClInclude Include="ResourcePackManager.h" />
|
||||||
<ClInclude Include="Resources.h" />
|
<ClInclude Include="Resources.h" />
|
||||||
<ClInclude Include="Translation.h" />
|
<ClInclude Include="Translation.h" />
|
||||||
<ClInclude Include="WiiUpdate.h" />
|
<ClInclude Include="WiiUpdate.h" />
|
||||||
|
|
|
@ -87,6 +87,7 @@
|
||||||
#include "DolphinQt/QtUtils/RunOnObject.h"
|
#include "DolphinQt/QtUtils/RunOnObject.h"
|
||||||
#include "DolphinQt/QtUtils/WindowActivationEventFilter.h"
|
#include "DolphinQt/QtUtils/WindowActivationEventFilter.h"
|
||||||
#include "DolphinQt/RenderWidget.h"
|
#include "DolphinQt/RenderWidget.h"
|
||||||
|
#include "DolphinQt/ResourcePackManager.h"
|
||||||
#include "DolphinQt/Resources.h"
|
#include "DolphinQt/Resources.h"
|
||||||
#include "DolphinQt/SearchBar.h"
|
#include "DolphinQt/SearchBar.h"
|
||||||
#include "DolphinQt/Settings.h"
|
#include "DolphinQt/Settings.h"
|
||||||
|
@ -99,6 +100,10 @@
|
||||||
|
|
||||||
#include "UICommon/DiscordPresence.h"
|
#include "UICommon/DiscordPresence.h"
|
||||||
#include "UICommon/GameFile.h"
|
#include "UICommon/GameFile.h"
|
||||||
|
#include "UICommon/ResourcePack/Manager.h"
|
||||||
|
#include "UICommon/ResourcePack/Manifest.h"
|
||||||
|
#include "UICommon/ResourcePack/ResourcePack.h"
|
||||||
|
|
||||||
#include "UICommon/UICommon.h"
|
#include "UICommon/UICommon.h"
|
||||||
|
|
||||||
#include "VideoCommon/VideoConfig.h"
|
#include "VideoCommon/VideoConfig.h"
|
||||||
|
@ -208,6 +213,21 @@ MainWindow::MainWindow(std::unique_ptr<BootParameters> boot_parameters) : QMainW
|
||||||
// Restoring of window states can sometimes go wrong, resulting in widgets being visible when they
|
// Restoring of window states can sometimes go wrong, resulting in widgets being visible when they
|
||||||
// shouldn't be so we have to reapply all our rules afterwards.
|
// shouldn't be so we have to reapply all our rules afterwards.
|
||||||
Settings::Instance().RefreshWidgetVisibility();
|
Settings::Instance().RefreshWidgetVisibility();
|
||||||
|
|
||||||
|
if (!ResourcePack::Init())
|
||||||
|
QMessageBox::critical(this, tr("Error"), tr("Error occured while loading some texture packs"));
|
||||||
|
|
||||||
|
for (auto& pack : ResourcePack::GetPacks())
|
||||||
|
{
|
||||||
|
if (!pack.IsValid())
|
||||||
|
{
|
||||||
|
QMessageBox::critical(this, tr("Error"),
|
||||||
|
tr("Invalid Pack %1 provided: %2")
|
||||||
|
.arg(QString::fromStdString(pack.GetPath()))
|
||||||
|
.arg(QString::fromStdString(pack.GetError())));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow::~MainWindow()
|
MainWindow::~MainWindow()
|
||||||
|
@ -391,6 +411,8 @@ void MainWindow::ConnectMenuBar()
|
||||||
|
|
||||||
// Tools
|
// Tools
|
||||||
connect(m_menu_bar, &MenuBar::ShowMemcardManager, this, &MainWindow::ShowMemcardManager);
|
connect(m_menu_bar, &MenuBar::ShowMemcardManager, this, &MainWindow::ShowMemcardManager);
|
||||||
|
connect(m_menu_bar, &MenuBar::ShowResourcePackManager, this,
|
||||||
|
&MainWindow::ShowResourcePackManager);
|
||||||
connect(m_menu_bar, &MenuBar::ShowCheatsManager, this, &MainWindow::ShowCheatsManager);
|
connect(m_menu_bar, &MenuBar::ShowCheatsManager, this, &MainWindow::ShowCheatsManager);
|
||||||
connect(m_menu_bar, &MenuBar::BootGameCubeIPL, this, &MainWindow::OnBootGameCubeIPL);
|
connect(m_menu_bar, &MenuBar::BootGameCubeIPL, this, &MainWindow::OnBootGameCubeIPL);
|
||||||
connect(m_menu_bar, &MenuBar::ImportNANDBackup, this, &MainWindow::OnImportNANDBackup);
|
connect(m_menu_bar, &MenuBar::ImportNANDBackup, this, &MainWindow::OnImportNANDBackup);
|
||||||
|
@ -1553,6 +1575,13 @@ void MainWindow::ShowMemcardManager()
|
||||||
manager.exec();
|
manager.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::ShowResourcePackManager()
|
||||||
|
{
|
||||||
|
ResourcePackManager manager(this);
|
||||||
|
|
||||||
|
manager.exec();
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::ShowCheatsManager()
|
void MainWindow::ShowCheatsManager()
|
||||||
{
|
{
|
||||||
m_cheats_manager->show();
|
m_cheats_manager->show();
|
||||||
|
|
|
@ -131,6 +131,7 @@ private:
|
||||||
void ShowNetPlaySetupDialog();
|
void ShowNetPlaySetupDialog();
|
||||||
void ShowFIFOPlayer();
|
void ShowFIFOPlayer();
|
||||||
void ShowMemcardManager();
|
void ShowMemcardManager();
|
||||||
|
void ShowResourcePackManager();
|
||||||
void ShowCheatsManager();
|
void ShowCheatsManager();
|
||||||
|
|
||||||
void NetPlayInit();
|
void NetPlayInit();
|
||||||
|
|
|
@ -204,6 +204,9 @@ void MenuBar::AddToolsMenu()
|
||||||
m_show_cheat_manager =
|
m_show_cheat_manager =
|
||||||
tools_menu->addAction(tr("&Cheats Manager"), this, [this] { emit ShowCheatsManager(); });
|
tools_menu->addAction(tr("&Cheats Manager"), this, [this] { emit ShowCheatsManager(); });
|
||||||
|
|
||||||
|
tools_menu->addAction(tr("&Resource Pack Manager"), this,
|
||||||
|
[this] { emit ShowResourcePackManager(); });
|
||||||
|
|
||||||
connect(&Settings::Instance(), &Settings::EnableCheatsChanged, [this](bool enabled) {
|
connect(&Settings::Instance(), &Settings::EnableCheatsChanged, [this](bool enabled) {
|
||||||
m_show_cheat_manager->setEnabled(Core::GetState() != Core::State::Uninitialized && enabled);
|
m_show_cheat_manager->setEnabled(Core::GetState() != Core::State::Uninitialized && enabled);
|
||||||
});
|
});
|
||||||
|
|
|
@ -78,6 +78,7 @@ signals:
|
||||||
void ShowFIFOPlayer();
|
void ShowFIFOPlayer();
|
||||||
void ShowAboutDialog();
|
void ShowAboutDialog();
|
||||||
void ShowCheatsManager();
|
void ShowCheatsManager();
|
||||||
|
void ShowResourcePackManager();
|
||||||
void ConnectWiiRemote(int id);
|
void ConnectWiiRemote(int id);
|
||||||
|
|
||||||
// Options
|
// Options
|
||||||
|
|
|
@ -0,0 +1,325 @@
|
||||||
|
// Copyright 2018 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "DolphinQt/ResourcePackManager.h"
|
||||||
|
|
||||||
|
#include <QDesktopServices>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QGridLayout>
|
||||||
|
#include <QHeaderView>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QTableWidget>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "Common/FileUtil.h"
|
||||||
|
#include "UICommon/ResourcePack/Manager.h"
|
||||||
|
|
||||||
|
ResourcePackManager::ResourcePackManager(QWidget* widget) : QDialog(widget)
|
||||||
|
{
|
||||||
|
CreateWidgets();
|
||||||
|
ConnectWidgets();
|
||||||
|
RepopulateTable();
|
||||||
|
|
||||||
|
setWindowTitle(tr("Resource Pack Manager"));
|
||||||
|
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||||
|
|
||||||
|
resize(QSize(900, 600));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcePackManager::CreateWidgets()
|
||||||
|
{
|
||||||
|
auto* layout = new QGridLayout;
|
||||||
|
|
||||||
|
m_table_widget = new QTableWidget;
|
||||||
|
|
||||||
|
m_open_directory_button = new QPushButton(tr("Open Directory..."));
|
||||||
|
m_change_button = new QPushButton(tr("Install"));
|
||||||
|
m_remove_button = new QPushButton(tr("Remove"));
|
||||||
|
m_refresh_button = new QPushButton(tr("Refresh"));
|
||||||
|
m_priority_up_button = new QPushButton(tr("Up"));
|
||||||
|
m_priority_down_button = new QPushButton(tr("Down"));
|
||||||
|
|
||||||
|
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok);
|
||||||
|
|
||||||
|
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||||
|
|
||||||
|
layout->addWidget(m_table_widget, 0, 0, 7, 1);
|
||||||
|
layout->addWidget(m_open_directory_button, 0, 1);
|
||||||
|
layout->addWidget(m_change_button, 1, 1);
|
||||||
|
layout->addWidget(m_remove_button, 2, 1);
|
||||||
|
layout->addWidget(m_refresh_button, 3, 1);
|
||||||
|
layout->addWidget(m_priority_up_button, 4, 1);
|
||||||
|
layout->addWidget(m_priority_down_button, 5, 1);
|
||||||
|
|
||||||
|
layout->addWidget(buttons, 7, 1, Qt::AlignRight);
|
||||||
|
setLayout(layout);
|
||||||
|
setLayout(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcePackManager::ConnectWidgets()
|
||||||
|
{
|
||||||
|
connect(m_open_directory_button, &QPushButton::pressed, this,
|
||||||
|
&ResourcePackManager::OpenResourcePackDir);
|
||||||
|
connect(m_refresh_button, &QPushButton::pressed, this, &ResourcePackManager::Refresh);
|
||||||
|
connect(m_change_button, &QPushButton::pressed, this, &ResourcePackManager::Change);
|
||||||
|
connect(m_remove_button, &QPushButton::pressed, this, &ResourcePackManager::Remove);
|
||||||
|
connect(m_priority_up_button, &QPushButton::pressed, this, &ResourcePackManager::PriorityUp);
|
||||||
|
connect(m_priority_down_button, &QPushButton::pressed, this, &ResourcePackManager::PriorityDown);
|
||||||
|
|
||||||
|
connect(m_table_widget, &QTableWidget::itemSelectionChanged, this,
|
||||||
|
&ResourcePackManager::SelectionChanged);
|
||||||
|
|
||||||
|
connect(m_table_widget, &QTableWidget::itemDoubleClicked, this,
|
||||||
|
&ResourcePackManager::ItemDoubleClicked);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcePackManager::OpenResourcePackDir()
|
||||||
|
{
|
||||||
|
QDesktopServices::openUrl(
|
||||||
|
QUrl::fromLocalFile(QString::fromStdString(File::GetUserPath(D_RESOURCEPACK_IDX))));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcePackManager::RepopulateTable()
|
||||||
|
{
|
||||||
|
m_table_widget->clear();
|
||||||
|
m_table_widget->setColumnCount(6);
|
||||||
|
|
||||||
|
m_table_widget->setHorizontalHeaderLabels({QStringLiteral(""), tr("Name"), tr("Version"),
|
||||||
|
tr("Description"), tr("Author"), tr("Website")});
|
||||||
|
|
||||||
|
auto* header = m_table_widget->horizontalHeader();
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
header->setSectionResizeMode(i, QHeaderView::ResizeToContents);
|
||||||
|
|
||||||
|
header->setStretchLastSection(true);
|
||||||
|
|
||||||
|
int size = static_cast<int>(ResourcePack::GetPacks().size());
|
||||||
|
|
||||||
|
m_table_widget->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||||
|
m_table_widget->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
|
|
||||||
|
m_table_widget->setRowCount(size);
|
||||||
|
m_table_widget->setIconSize(QSize(32, 32));
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
{
|
||||||
|
const auto& pack = ResourcePack::GetPacks()[size - 1 - i];
|
||||||
|
auto* manifest = pack.GetManifest();
|
||||||
|
|
||||||
|
auto* logo_item = new QTableWidgetItem;
|
||||||
|
auto* name_item = new QTableWidgetItem(QString::fromStdString(manifest->GetName()));
|
||||||
|
auto* version_item = new QTableWidgetItem(QString::fromStdString(manifest->GetVersion()));
|
||||||
|
auto* author_item = new QTableWidgetItem(
|
||||||
|
QString::fromStdString(manifest->GetAuthors().value_or("Unknown author")));
|
||||||
|
auto* description_item =
|
||||||
|
new QTableWidgetItem(QString::fromStdString(manifest->GetDescription().value_or("")));
|
||||||
|
auto* website_item =
|
||||||
|
new QTableWidgetItem(QString::fromStdString(manifest->GetWebsite().value_or("")));
|
||||||
|
|
||||||
|
QPixmap logo;
|
||||||
|
|
||||||
|
logo.loadFromData(reinterpret_cast<const uchar*>(pack.GetLogo().data()),
|
||||||
|
(int)pack.GetLogo().size());
|
||||||
|
|
||||||
|
logo_item->setIcon(QIcon(logo));
|
||||||
|
|
||||||
|
QFont link_font = website_item->font();
|
||||||
|
|
||||||
|
link_font.setUnderline(true);
|
||||||
|
|
||||||
|
website_item->setFont(link_font);
|
||||||
|
website_item->setForeground(QBrush(Qt::blue));
|
||||||
|
website_item->setData(Qt::UserRole, website_item->text());
|
||||||
|
|
||||||
|
for (auto* item :
|
||||||
|
{logo_item, name_item, version_item, author_item, description_item, website_item})
|
||||||
|
{
|
||||||
|
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
|
||||||
|
|
||||||
|
if (ResourcePack::IsInstalled(pack))
|
||||||
|
{
|
||||||
|
item->setBackgroundColor(QColor(Qt::green));
|
||||||
|
|
||||||
|
auto font = item->font();
|
||||||
|
font.setBold(true);
|
||||||
|
item->setFont(font);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_table_widget->setItem(i, 0, logo_item);
|
||||||
|
m_table_widget->setItem(i, 1, name_item);
|
||||||
|
m_table_widget->setItem(i, 2, version_item);
|
||||||
|
m_table_widget->setItem(i, 3, description_item);
|
||||||
|
m_table_widget->setItem(i, 4, author_item);
|
||||||
|
m_table_widget->setItem(i, 5, website_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectionChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcePackManager::Change()
|
||||||
|
{
|
||||||
|
auto items = m_table_widget->selectedItems();
|
||||||
|
|
||||||
|
if (items.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ResourcePack::IsInstalled(ResourcePack::GetPacks()[items[0]->row()]))
|
||||||
|
Uninstall();
|
||||||
|
else
|
||||||
|
Install();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcePackManager::Install()
|
||||||
|
{
|
||||||
|
auto items = m_table_widget->selectedItems();
|
||||||
|
|
||||||
|
if (items.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto& item = ResourcePack::GetPacks()[m_table_widget->rowCount() - 1 - items[0]->row()];
|
||||||
|
|
||||||
|
bool success = item.Install(File::GetUserPath(D_USER_IDX));
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
QMessageBox::critical(
|
||||||
|
this, tr("Error"),
|
||||||
|
tr("Failed to install pack: %1").arg(QString::fromStdString(item.GetError())));
|
||||||
|
}
|
||||||
|
|
||||||
|
RepopulateTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcePackManager::Uninstall()
|
||||||
|
{
|
||||||
|
auto items = m_table_widget->selectedItems();
|
||||||
|
|
||||||
|
if (items.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto& item = ResourcePack::GetPacks()[m_table_widget->rowCount() - 1 - items[0]->row()];
|
||||||
|
|
||||||
|
bool success = item.Uninstall(File::GetUserPath(D_USER_IDX));
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
QMessageBox::critical(
|
||||||
|
this, tr("Error"),
|
||||||
|
tr("Failed to uninstall pack: %1").arg(QString::fromStdString(item.GetError())));
|
||||||
|
}
|
||||||
|
|
||||||
|
RepopulateTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcePackManager::Remove()
|
||||||
|
{
|
||||||
|
auto items = m_table_widget->selectedItems();
|
||||||
|
|
||||||
|
if (items.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QMessageBox box(this);
|
||||||
|
box.setWindowTitle(tr("Confirmation"));
|
||||||
|
box.setText(tr("Are you sure you want to delete this pack?"));
|
||||||
|
box.setIcon(QMessageBox::Warning);
|
||||||
|
box.setStandardButtons(QMessageBox::Yes | QMessageBox::Abort);
|
||||||
|
|
||||||
|
if (box.exec() != QMessageBox::Yes)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Uninstall();
|
||||||
|
File::Delete(
|
||||||
|
ResourcePack::GetPacks()[m_table_widget->rowCount() - 1 - items[0]->row()].GetPath());
|
||||||
|
RepopulateTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcePackManager::PriorityDown()
|
||||||
|
{
|
||||||
|
auto items = m_table_widget->selectedItems();
|
||||||
|
|
||||||
|
if (items.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
int row = m_table_widget->rowCount() - 1 - items[0]->row();
|
||||||
|
|
||||||
|
if (items[0]->row() >= m_table_widget->rowCount())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto& pack = ResourcePack::GetPacks()[row];
|
||||||
|
std::string path = pack.GetPath();
|
||||||
|
|
||||||
|
row--;
|
||||||
|
|
||||||
|
ResourcePack::Remove(pack);
|
||||||
|
ResourcePack::Add(path, row);
|
||||||
|
|
||||||
|
RepopulateTable();
|
||||||
|
|
||||||
|
m_table_widget->selectRow(row == 0 ? m_table_widget->rowCount() - 1 : row);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcePackManager::PriorityUp()
|
||||||
|
{
|
||||||
|
auto items = m_table_widget->selectedItems();
|
||||||
|
|
||||||
|
if (items.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
int row = m_table_widget->rowCount() - 1 - items[0]->row();
|
||||||
|
|
||||||
|
if (items[0]->row() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto& pack = ResourcePack::GetPacks()[row];
|
||||||
|
std::string path = pack.GetPath();
|
||||||
|
|
||||||
|
row++;
|
||||||
|
|
||||||
|
ResourcePack::Remove(pack);
|
||||||
|
ResourcePack::Add(path, items[0]->row() == m_table_widget->rowCount() ? -1 : row);
|
||||||
|
|
||||||
|
RepopulateTable();
|
||||||
|
|
||||||
|
m_table_widget->selectRow(row == m_table_widget->rowCount() - 1 ? 0 : row);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcePackManager::Refresh()
|
||||||
|
{
|
||||||
|
ResourcePack::Init();
|
||||||
|
RepopulateTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcePackManager::SelectionChanged()
|
||||||
|
{
|
||||||
|
auto items = m_table_widget->selectedItems();
|
||||||
|
|
||||||
|
const bool has_selection = !items.empty();
|
||||||
|
|
||||||
|
if (has_selection)
|
||||||
|
{
|
||||||
|
m_change_button->setText(ResourcePack::IsInstalled(ResourcePack::GetPacks()[items[0]->row()]) ?
|
||||||
|
tr("Uninstall") :
|
||||||
|
tr("Install"));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto* item : {m_change_button, m_remove_button})
|
||||||
|
item->setEnabled(has_selection);
|
||||||
|
|
||||||
|
m_priority_down_button->setEnabled(has_selection &&
|
||||||
|
items[0]->row() < m_table_widget->rowCount() - 1);
|
||||||
|
m_priority_up_button->setEnabled(has_selection && items[0]->row() != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcePackManager::ItemDoubleClicked(QTableWidgetItem* item)
|
||||||
|
{
|
||||||
|
auto item_data = item->data(Qt::UserRole);
|
||||||
|
|
||||||
|
if (item_data.isNull())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QDesktopServices::openUrl(QUrl(item_data.toString()));
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright 2018 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
|
||||||
|
class QPushButton;
|
||||||
|
class QTableWidget;
|
||||||
|
class QTableWidgetItem;
|
||||||
|
|
||||||
|
class ResourcePackManager : public QDialog
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit ResourcePackManager(QWidget* parent = nullptr);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void CreateWidgets();
|
||||||
|
void ConnectWidgets();
|
||||||
|
void OpenResourcePackDir();
|
||||||
|
void RepopulateTable();
|
||||||
|
void Change();
|
||||||
|
void Install();
|
||||||
|
void Uninstall();
|
||||||
|
void Remove();
|
||||||
|
void PriorityUp();
|
||||||
|
void PriorityDown();
|
||||||
|
void Refresh();
|
||||||
|
|
||||||
|
void SelectionChanged();
|
||||||
|
void ItemDoubleClicked(QTableWidgetItem* item);
|
||||||
|
|
||||||
|
QPushButton* m_open_directory_button;
|
||||||
|
QPushButton* m_change_button;
|
||||||
|
QPushButton* m_remove_button;
|
||||||
|
QPushButton* m_refresh_button;
|
||||||
|
QPushButton* m_priority_up_button;
|
||||||
|
QPushButton* m_priority_down_button;
|
||||||
|
|
||||||
|
QTableWidget* m_table_widget;
|
||||||
|
};
|
|
@ -5,6 +5,9 @@ add_library(uicommon
|
||||||
DiscordPresence.cpp
|
DiscordPresence.cpp
|
||||||
GameFile.cpp
|
GameFile.cpp
|
||||||
GameFileCache.cpp
|
GameFileCache.cpp
|
||||||
|
ResourcePack/Manager.cpp
|
||||||
|
ResourcePack/Manifest.cpp
|
||||||
|
ResourcePack/ResourcePack.cpp
|
||||||
UICommon.cpp
|
UICommon.cpp
|
||||||
USBUtils.cpp
|
USBUtils.cpp
|
||||||
VideoUtils.cpp
|
VideoUtils.cpp
|
||||||
|
@ -14,6 +17,7 @@ target_link_libraries(uicommon
|
||||||
PUBLIC
|
PUBLIC
|
||||||
common
|
common
|
||||||
cpp-optparse
|
cpp-optparse
|
||||||
|
minizip
|
||||||
|
|
||||||
PRIVATE
|
PRIVATE
|
||||||
$<$<BOOL:APPLE>:${IOK_LIBRARY}>
|
$<$<BOOL:APPLE>:${IOK_LIBRARY}>
|
||||||
|
|
|
@ -0,0 +1,185 @@
|
||||||
|
// Copyright 2018 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "UICommon/ResourcePack/Manager.h"
|
||||||
|
|
||||||
|
#include "Common/CommonTypes.h"
|
||||||
|
#include "Common/FileSearch.h"
|
||||||
|
#include "Common/FileUtil.h"
|
||||||
|
#include "Common/IniFile.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
std::vector<ResourcePack::ResourcePack> packs;
|
||||||
|
|
||||||
|
std::string packs_path;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace ResourcePack
|
||||||
|
{
|
||||||
|
IniFile GetPackConfig()
|
||||||
|
{
|
||||||
|
packs_path = File::GetUserPath(D_RESOURCEPACK_IDX) + "/Packs.ini";
|
||||||
|
|
||||||
|
IniFile file;
|
||||||
|
file.Load(packs_path);
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Init()
|
||||||
|
{
|
||||||
|
packs.clear();
|
||||||
|
auto pack_list = Common::DoFileSearch({File::GetUserPath(D_RESOURCEPACK_IDX)}, {".zip"});
|
||||||
|
|
||||||
|
bool error = false;
|
||||||
|
|
||||||
|
IniFile file = GetPackConfig();
|
||||||
|
|
||||||
|
auto* order = file.GetOrCreateSection("Order");
|
||||||
|
|
||||||
|
std::sort(pack_list.begin(), pack_list.end(), [order](std::string& a, std::string& b) {
|
||||||
|
std::string order_a = a, order_b = b;
|
||||||
|
|
||||||
|
order->Get(ResourcePack(a).GetManifest()->GetID(), &order_a);
|
||||||
|
order->Get(ResourcePack(b).GetManifest()->GetID(), &order_b);
|
||||||
|
|
||||||
|
return order_a < order_b;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (size_t i = 0; i < pack_list.size(); i++)
|
||||||
|
{
|
||||||
|
const auto& path = pack_list[i];
|
||||||
|
|
||||||
|
error |= !Add(path);
|
||||||
|
|
||||||
|
order->Set(packs[i].GetManifest()->GetID(), static_cast<u64>(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
file.Save(packs_path);
|
||||||
|
|
||||||
|
return !error;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ResourcePack>& GetPacks()
|
||||||
|
{
|
||||||
|
return packs;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ResourcePack*> GetLowerPriorityPacks(ResourcePack& pack)
|
||||||
|
{
|
||||||
|
std::vector<ResourcePack*> list;
|
||||||
|
for (auto it = std::find(packs.begin(), packs.end(), pack) + 1; it != packs.end(); it++)
|
||||||
|
{
|
||||||
|
auto& entry = *it;
|
||||||
|
if (!IsInstalled(pack))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
list.push_back(&entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ResourcePack*> GetHigherPriorityPacks(ResourcePack& pack)
|
||||||
|
{
|
||||||
|
std::vector<ResourcePack*> list;
|
||||||
|
auto end = std::find(packs.begin(), packs.end(), pack);
|
||||||
|
|
||||||
|
for (auto it = packs.begin(); it != end; it++)
|
||||||
|
{
|
||||||
|
auto& entry = *it;
|
||||||
|
if (!IsInstalled(entry))
|
||||||
|
continue;
|
||||||
|
list.push_back(&entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Add(const std::string& path, int offset)
|
||||||
|
{
|
||||||
|
if (offset == -1)
|
||||||
|
offset = static_cast<int>(packs.size());
|
||||||
|
|
||||||
|
ResourcePack pack(path);
|
||||||
|
|
||||||
|
IniFile file = GetPackConfig();
|
||||||
|
|
||||||
|
auto* order = file.GetOrCreateSection("Order");
|
||||||
|
|
||||||
|
order->Set(pack.GetManifest()->GetID(), offset);
|
||||||
|
|
||||||
|
for (int i = offset; i < static_cast<int>(packs.size()); i++)
|
||||||
|
order->Set(packs[i].GetManifest()->GetID(), i + 1);
|
||||||
|
|
||||||
|
file.Save(packs_path);
|
||||||
|
|
||||||
|
packs.insert(packs.begin() + offset, std::move(pack));
|
||||||
|
|
||||||
|
return pack.IsValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Remove(ResourcePack& pack)
|
||||||
|
{
|
||||||
|
const auto result = pack.Uninstall(File::GetUserPath(D_USER_IDX));
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto pack_iterator = std::find(packs.begin(), packs.end(), pack);
|
||||||
|
|
||||||
|
if (pack_iterator == packs.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::string filename;
|
||||||
|
|
||||||
|
IniFile file = GetPackConfig();
|
||||||
|
|
||||||
|
auto* order = file.GetOrCreateSection("Order");
|
||||||
|
|
||||||
|
order->Delete(pack.GetManifest()->GetID());
|
||||||
|
|
||||||
|
int offset = pack_iterator - packs.begin();
|
||||||
|
|
||||||
|
for (int i = offset + 1; i < static_cast<int>(packs.size()); i++)
|
||||||
|
order->Set(packs[i].GetManifest()->GetID(), i - 1);
|
||||||
|
|
||||||
|
file.Save(packs_path);
|
||||||
|
|
||||||
|
packs.erase(pack_iterator);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetInstalled(const ResourcePack& pack, bool installed)
|
||||||
|
{
|
||||||
|
IniFile file = GetPackConfig();
|
||||||
|
|
||||||
|
auto* install = file.GetOrCreateSection("Installed");
|
||||||
|
|
||||||
|
if (installed)
|
||||||
|
install->Set(pack.GetManifest()->GetID(), installed);
|
||||||
|
else
|
||||||
|
install->Delete(pack.GetManifest()->GetID());
|
||||||
|
|
||||||
|
file.Save(packs_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsInstalled(const ResourcePack& pack)
|
||||||
|
{
|
||||||
|
IniFile file = GetPackConfig();
|
||||||
|
|
||||||
|
auto* install = file.GetOrCreateSection("Installed");
|
||||||
|
|
||||||
|
bool installed;
|
||||||
|
|
||||||
|
install->Get(pack.GetManifest()->GetID(), &installed, false);
|
||||||
|
|
||||||
|
return installed;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ResourcePack
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright 2018 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "UICommon/ResourcePack/ResourcePack.h"
|
||||||
|
|
||||||
|
namespace ResourcePack
|
||||||
|
{
|
||||||
|
bool Init();
|
||||||
|
|
||||||
|
bool Add(const std::string& path, int offset = -1);
|
||||||
|
bool Remove(ResourcePack& pack);
|
||||||
|
void SetInstalled(const ResourcePack& pack, bool installed);
|
||||||
|
bool IsInstalled(const ResourcePack& pack);
|
||||||
|
|
||||||
|
std::vector<ResourcePack>& GetPacks();
|
||||||
|
|
||||||
|
std::vector<ResourcePack*> GetHigherPriorityPacks(ResourcePack& pack);
|
||||||
|
std::vector<ResourcePack*> GetLowerPriorityPacks(ResourcePack& pack);
|
||||||
|
} // namespace ResourcePack
|
|
@ -0,0 +1,102 @@
|
||||||
|
// Copyright 2018 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "UICommon/ResourcePack/Manifest.h"
|
||||||
|
|
||||||
|
#include <picojson/picojson.h>
|
||||||
|
|
||||||
|
namespace ResourcePack
|
||||||
|
{
|
||||||
|
Manifest::Manifest(const std::string& json)
|
||||||
|
{
|
||||||
|
picojson::value out;
|
||||||
|
auto error = picojson::parse(out, json);
|
||||||
|
|
||||||
|
if (!error.empty())
|
||||||
|
{
|
||||||
|
m_error = "Failed to parse manifest.";
|
||||||
|
m_valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required fields
|
||||||
|
picojson::value& name = out.get("name");
|
||||||
|
picojson::value& version = out.get("version");
|
||||||
|
picojson::value& id = out.get("id");
|
||||||
|
|
||||||
|
// Optional fields
|
||||||
|
picojson::value& authors = out.get("authors");
|
||||||
|
picojson::value& description = out.get("description");
|
||||||
|
picojson::value& website = out.get("website");
|
||||||
|
|
||||||
|
if (!name.is<std::string>() || !id.is<std::string>() || !version.is<std::string>())
|
||||||
|
{
|
||||||
|
m_error = "Some objects have a bad type.";
|
||||||
|
m_valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_name = name.to_str();
|
||||||
|
m_version = version.to_str();
|
||||||
|
m_id = id.to_str();
|
||||||
|
|
||||||
|
if (authors.is<picojson::array>())
|
||||||
|
{
|
||||||
|
std::string author_list;
|
||||||
|
for (const auto& o : authors.get<picojson::array>())
|
||||||
|
{
|
||||||
|
author_list += o.to_str() + ", ";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!author_list.empty())
|
||||||
|
m_authors = author_list.substr(0, author_list.size() - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (description.is<std::string>())
|
||||||
|
m_description = description.to_str();
|
||||||
|
|
||||||
|
if (website.is<std::string>())
|
||||||
|
m_website = website.to_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Manifest::IsValid() const
|
||||||
|
{
|
||||||
|
return m_valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& Manifest::GetName() const
|
||||||
|
{
|
||||||
|
return m_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& Manifest::GetVersion() const
|
||||||
|
{
|
||||||
|
return m_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& Manifest::GetID() const
|
||||||
|
{
|
||||||
|
return m_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& Manifest::GetError() const
|
||||||
|
{
|
||||||
|
return m_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::optional<std::string>& Manifest::GetAuthors() const
|
||||||
|
{
|
||||||
|
return m_authors;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::optional<std::string>& Manifest::GetDescription() const
|
||||||
|
{
|
||||||
|
return m_description;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::optional<std::string>& Manifest::GetWebsite() const
|
||||||
|
{
|
||||||
|
return m_website;
|
||||||
|
}
|
||||||
|
} // namespace ResourcePack
|
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright 2018 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace ResourcePack
|
||||||
|
{
|
||||||
|
class Manifest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit Manifest(const std::string& text);
|
||||||
|
|
||||||
|
bool IsValid() const;
|
||||||
|
|
||||||
|
const std::string& GetName() const;
|
||||||
|
const std::string& GetVersion() const;
|
||||||
|
const std::string& GetID() const;
|
||||||
|
const std::string& GetError() const;
|
||||||
|
|
||||||
|
const std::optional<std::string>& GetAuthors() const;
|
||||||
|
const std::optional<std::string>& GetDescription() const;
|
||||||
|
const std::optional<std::string>& GetWebsite() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_valid = true;
|
||||||
|
|
||||||
|
std::string m_name;
|
||||||
|
std::string m_version;
|
||||||
|
std::string m_id;
|
||||||
|
std::string m_error;
|
||||||
|
|
||||||
|
std::optional<std::string> m_authors;
|
||||||
|
std::optional<std::string> m_description;
|
||||||
|
std::optional<std::string> m_website;
|
||||||
|
};
|
||||||
|
} // namespace ResourcePack
|
|
@ -0,0 +1,326 @@
|
||||||
|
// Copyright 2018 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "UICommon/ResourcePack/ResourcePack.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <minizip/unzip.h>
|
||||||
|
|
||||||
|
#include "Common/FileSearch.h"
|
||||||
|
#include "Common/FileUtil.h"
|
||||||
|
#include "Common/StringUtil.h"
|
||||||
|
|
||||||
|
#include "UICommon/ResourcePack/Manager.h"
|
||||||
|
#include "UICommon/ResourcePack/Manifest.h"
|
||||||
|
|
||||||
|
static const char* TEXTURE_PATH = "Load/Textures/";
|
||||||
|
|
||||||
|
namespace ResourcePack
|
||||||
|
{
|
||||||
|
// Since minzip doesn't provide a way to unzip a file of a length > 65535, we have to implement
|
||||||
|
// this ourselves
|
||||||
|
static bool ReadCurrentFileUnlimited(unzFile file, std::vector<char>& destination)
|
||||||
|
{
|
||||||
|
const uint32_t MAX_BUFFER_SIZE = 65535;
|
||||||
|
|
||||||
|
if (unzOpenCurrentFile(file) != UNZ_OK)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
uint32_t bytes_to_go = static_cast<uint32_t>(destination.size());
|
||||||
|
|
||||||
|
while (bytes_to_go > 0)
|
||||||
|
{
|
||||||
|
int bytes_read = unzReadCurrentFile(file, &destination[destination.size() - bytes_to_go],
|
||||||
|
std::min(bytes_to_go, MAX_BUFFER_SIZE));
|
||||||
|
|
||||||
|
if (bytes_read < 0)
|
||||||
|
{
|
||||||
|
unzCloseCurrentFile(file);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes_to_go -= bytes_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
unzCloseCurrentFile(file);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourcePack::ResourcePack(const std::string& path) : m_path(path)
|
||||||
|
{
|
||||||
|
auto file = unzOpen(path.c_str());
|
||||||
|
|
||||||
|
if (file == nullptr)
|
||||||
|
{
|
||||||
|
m_valid = false;
|
||||||
|
m_error = "Failed to open resource pack";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unzLocateFile(file, "manifest.json", 0) == UNZ_END_OF_LIST_OF_FILE)
|
||||||
|
{
|
||||||
|
m_valid = false;
|
||||||
|
m_error = "Resource pack is missing a manifest.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unz_file_info manifest_info;
|
||||||
|
|
||||||
|
unzGetCurrentFileInfo(file, &manifest_info, nullptr, 0, nullptr, 0, nullptr, 0);
|
||||||
|
|
||||||
|
std::vector<char> manifest_contents;
|
||||||
|
|
||||||
|
manifest_contents.resize(manifest_info.uncompressed_size);
|
||||||
|
|
||||||
|
if (!ReadCurrentFileUnlimited(file, manifest_contents))
|
||||||
|
{
|
||||||
|
m_valid = false;
|
||||||
|
m_error = "Failed to read manifest.json";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unzCloseCurrentFile(file);
|
||||||
|
|
||||||
|
m_manifest =
|
||||||
|
std::make_shared<Manifest>(std::string(manifest_contents.begin(), manifest_contents.end()));
|
||||||
|
|
||||||
|
if (!m_manifest->IsValid())
|
||||||
|
{
|
||||||
|
m_valid = false;
|
||||||
|
m_error = "Manifest error: " + m_manifest->GetError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unzLocateFile(file, "logo.png", 0) != UNZ_END_OF_LIST_OF_FILE)
|
||||||
|
{
|
||||||
|
unz_file_info logo_info;
|
||||||
|
|
||||||
|
unzGetCurrentFileInfo(file, &logo_info, nullptr, 0, nullptr, 0, nullptr, 0);
|
||||||
|
|
||||||
|
m_logo_data.resize(logo_info.uncompressed_size);
|
||||||
|
|
||||||
|
if (!ReadCurrentFileUnlimited(file, m_logo_data))
|
||||||
|
{
|
||||||
|
m_valid = false;
|
||||||
|
m_error = "Failed to read logo.png";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unzGoToFirstFile(file);
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
std::string filename;
|
||||||
|
|
||||||
|
filename.resize(256);
|
||||||
|
|
||||||
|
unz_file_info texture_info;
|
||||||
|
|
||||||
|
unzGetCurrentFileInfo(file, &texture_info, &filename[0], static_cast<uint16_t>(filename.size()),
|
||||||
|
nullptr, 0, nullptr, 0);
|
||||||
|
|
||||||
|
if (filename.compare(0, 9, "textures/") != 0 || texture_info.uncompressed_size == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// If a texture is compressed, abort.
|
||||||
|
if (texture_info.compression_method != 0)
|
||||||
|
{
|
||||||
|
m_valid = false;
|
||||||
|
m_error = "Texture " + filename + " is compressed!";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_textures.push_back(filename.substr(9));
|
||||||
|
} while (unzGoToNextFile(file) != UNZ_END_OF_LIST_OF_FILE);
|
||||||
|
|
||||||
|
unzClose(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourcePack::IsValid() const
|
||||||
|
{
|
||||||
|
return m_valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<char>& ResourcePack::GetLogo() const
|
||||||
|
{
|
||||||
|
return m_logo_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& ResourcePack::GetPath() const
|
||||||
|
{
|
||||||
|
return m_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& ResourcePack::GetError() const
|
||||||
|
{
|
||||||
|
return m_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Manifest* ResourcePack::GetManifest() const
|
||||||
|
{
|
||||||
|
return m_manifest.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<std::string>& ResourcePack::GetTextures() const
|
||||||
|
{
|
||||||
|
return m_textures;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourcePack::Install(const std::string& path)
|
||||||
|
{
|
||||||
|
if (!IsValid())
|
||||||
|
{
|
||||||
|
m_error = "Invalid pack";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto file = unzOpen(m_path.c_str());
|
||||||
|
|
||||||
|
for (const auto& texture : m_textures)
|
||||||
|
{
|
||||||
|
bool provided_by_other_pack = false;
|
||||||
|
|
||||||
|
// Check if a higher priority pack already provides a given texture, don't overwrite it
|
||||||
|
for (const auto& pack : GetHigherPriorityPacks(*this))
|
||||||
|
{
|
||||||
|
if (std::find(pack->GetTextures().begin(), pack->GetTextures().end(), texture) !=
|
||||||
|
pack->GetTextures().end())
|
||||||
|
{
|
||||||
|
provided_by_other_pack = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provided_by_other_pack)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (unzLocateFile(file, ("textures/" + texture).c_str(), 0) != UNZ_OK)
|
||||||
|
{
|
||||||
|
m_error = "Failed to locate texture " + texture;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string m_full_dir;
|
||||||
|
|
||||||
|
SplitPath(path + TEXTURE_PATH + texture, &m_full_dir, nullptr, nullptr);
|
||||||
|
|
||||||
|
if (!File::CreateFullPath(m_full_dir))
|
||||||
|
{
|
||||||
|
m_error = "Failed to create full path " + m_full_dir;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
unz_file_info texture_info;
|
||||||
|
|
||||||
|
unzGetCurrentFileInfo(file, &texture_info, nullptr, 0, nullptr, 0, nullptr, 0);
|
||||||
|
|
||||||
|
std::vector<char> data;
|
||||||
|
data.resize(texture_info.uncompressed_size);
|
||||||
|
|
||||||
|
if (!ReadCurrentFileUnlimited(file, data))
|
||||||
|
{
|
||||||
|
m_error = "Failed to read texture " + texture;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream out(path + TEXTURE_PATH + texture, std::ios::trunc | std::ios::binary);
|
||||||
|
|
||||||
|
if (!out.good())
|
||||||
|
{
|
||||||
|
m_error = "Failed to write " + texture;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.write(data.data(), data.size());
|
||||||
|
out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
unzClose(file);
|
||||||
|
|
||||||
|
SetInstalled(*this, true);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourcePack::Uninstall(const std::string& path)
|
||||||
|
{
|
||||||
|
if (!IsValid())
|
||||||
|
{
|
||||||
|
m_error = "Invalid pack";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lower = GetLowerPriorityPacks(*this);
|
||||||
|
|
||||||
|
SetInstalled(*this, false);
|
||||||
|
|
||||||
|
for (const auto& texture : m_textures)
|
||||||
|
{
|
||||||
|
bool provided_by_other_pack = false;
|
||||||
|
|
||||||
|
// Check if a higher priority pack already provides a given texture, don't delete it
|
||||||
|
for (const auto& pack : GetHigherPriorityPacks(*this))
|
||||||
|
{
|
||||||
|
if (std::find(pack->GetTextures().begin(), pack->GetTextures().end(), texture) !=
|
||||||
|
pack->GetTextures().end())
|
||||||
|
{
|
||||||
|
provided_by_other_pack = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provided_by_other_pack)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Check if a lower priority pack provides a given texture - if so, install it.
|
||||||
|
for (auto& pack : lower)
|
||||||
|
{
|
||||||
|
if (std::find(pack->GetTextures().rbegin(), pack->GetTextures().rend(), texture) !=
|
||||||
|
pack->GetTextures().rend())
|
||||||
|
{
|
||||||
|
pack->Install(path);
|
||||||
|
|
||||||
|
provided_by_other_pack = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provided_by_other_pack)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (File::Exists(path + TEXTURE_PATH + texture) && !File::Delete(path + TEXTURE_PATH + texture))
|
||||||
|
{
|
||||||
|
m_error = "Failed to delete texture " + texture;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively delete empty directories
|
||||||
|
|
||||||
|
std::string dir;
|
||||||
|
|
||||||
|
SplitPath(path + TEXTURE_PATH + texture, &dir, nullptr, nullptr);
|
||||||
|
|
||||||
|
while (dir.length() > (path + TEXTURE_PATH).length())
|
||||||
|
{
|
||||||
|
auto is_empty = Common::DoFileSearch({dir}).empty();
|
||||||
|
|
||||||
|
if (is_empty)
|
||||||
|
File::DeleteDir(dir);
|
||||||
|
|
||||||
|
SplitPath(dir.substr(0, dir.size() - 2), &dir, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourcePack::operator==(const ResourcePack& pack)
|
||||||
|
{
|
||||||
|
return pack.GetPath() == m_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ResourcePack
|
|
@ -0,0 +1,45 @@
|
||||||
|
// Copyright 2018 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Common/CommonTypes.h"
|
||||||
|
|
||||||
|
#include "UICommon/ResourcePack/Manifest.h"
|
||||||
|
|
||||||
|
namespace ResourcePack
|
||||||
|
{
|
||||||
|
class ResourcePack
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit ResourcePack(const std::string& path);
|
||||||
|
|
||||||
|
bool IsValid() const;
|
||||||
|
const std::vector<char>& GetLogo() const;
|
||||||
|
|
||||||
|
const std::string& GetPath() const;
|
||||||
|
const std::string& GetError() const;
|
||||||
|
const Manifest* GetManifest() const;
|
||||||
|
const std::vector<std::string>& GetTextures() const;
|
||||||
|
|
||||||
|
bool Install(const std::string& path);
|
||||||
|
bool Uninstall(const std::string& path);
|
||||||
|
|
||||||
|
bool operator==(const ResourcePack& pack);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_valid = true;
|
||||||
|
|
||||||
|
std::string m_path;
|
||||||
|
std::string m_error;
|
||||||
|
|
||||||
|
std::shared_ptr<Manifest> m_manifest;
|
||||||
|
std::vector<std::string> m_textures;
|
||||||
|
std::vector<char> m_logo_data;
|
||||||
|
};
|
||||||
|
} // namespace ResourcePack
|
|
@ -156,6 +156,7 @@ void SetLocale(std::string locale_name)
|
||||||
|
|
||||||
void CreateDirectories()
|
void CreateDirectories()
|
||||||
{
|
{
|
||||||
|
File::CreateFullPath(File::GetUserPath(D_RESOURCEPACK_IDX));
|
||||||
File::CreateFullPath(File::GetUserPath(D_USER_IDX));
|
File::CreateFullPath(File::GetUserPath(D_USER_IDX));
|
||||||
File::CreateFullPath(File::GetUserPath(D_CACHE_IDX));
|
File::CreateFullPath(File::GetUserPath(D_CACHE_IDX));
|
||||||
File::CreateFullPath(File::GetUserPath(D_COVERCACHE_IDX));
|
File::CreateFullPath(File::GetUserPath(D_COVERCACHE_IDX));
|
||||||
|
|
|
@ -53,6 +53,9 @@
|
||||||
<ClCompile Include="AutoUpdate.cpp" />
|
<ClCompile Include="AutoUpdate.cpp" />
|
||||||
<ClCompile Include="CommandLineParse.cpp" />
|
<ClCompile Include="CommandLineParse.cpp" />
|
||||||
<ClCompile Include="DiscordPresence.cpp" />
|
<ClCompile Include="DiscordPresence.cpp" />
|
||||||
|
<ClCompile Include="ResourcePack\Manager.cpp" />
|
||||||
|
<ClCompile Include="ResourcePack\Manifest.cpp" />
|
||||||
|
<ClCompile Include="ResourcePack\ResourcePack.cpp" />
|
||||||
<ClCompile Include="UICommon.cpp" />
|
<ClCompile Include="UICommon.cpp" />
|
||||||
<ClCompile Include="Disassembler.cpp" />
|
<ClCompile Include="Disassembler.cpp" />
|
||||||
<ClCompile Include="USBUtils.cpp">
|
<ClCompile Include="USBUtils.cpp">
|
||||||
|
@ -66,6 +69,9 @@
|
||||||
<ClInclude Include="AutoUpdate.h" />
|
<ClInclude Include="AutoUpdate.h" />
|
||||||
<ClInclude Include="CommandLineParse.h" />
|
<ClInclude Include="CommandLineParse.h" />
|
||||||
<ClInclude Include="DiscordPresence.h" />
|
<ClInclude Include="DiscordPresence.h" />
|
||||||
|
<ClInclude Include="ResourcePack\Manager.h" />
|
||||||
|
<ClInclude Include="ResourcePack\Manifest.h" />
|
||||||
|
<ClInclude Include="ResourcePack\ResourcePack.h" />
|
||||||
<ClInclude Include="UICommon.h" />
|
<ClInclude Include="UICommon.h" />
|
||||||
<ClInclude Include="Disassembler.h" />
|
<ClInclude Include="Disassembler.h" />
|
||||||
<ClInclude Include="USBUtils.h" />
|
<ClInclude Include="USBUtils.h" />
|
||||||
|
|
Loading…
Reference in New Issue