mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2025-12-16 12:08:49 +00:00
qt: Add batch Z3DS compression/decompression
This commit is contained in:
parent
6b05944116
commit
4fdc45a771
@ -2290,8 +2290,8 @@ void GMainWindow::OnMenuBootHomeMenu(u32 region) {
|
|||||||
void GMainWindow::InstallCIA(QStringList filepaths) {
|
void GMainWindow::InstallCIA(QStringList filepaths) {
|
||||||
ui->action_Install_CIA->setEnabled(false);
|
ui->action_Install_CIA->setEnabled(false);
|
||||||
game_list->SetDirectoryWatcherEnabled(false);
|
game_list->SetDirectoryWatcherEnabled(false);
|
||||||
progress_bar->show();
|
|
||||||
progress_bar->setMaximum(INT_MAX);
|
emit UpdateProgress(0, 0);
|
||||||
|
|
||||||
(void)QtConcurrent::run([&, filepaths] {
|
(void)QtConcurrent::run([&, filepaths] {
|
||||||
Service::AM::InstallStatus status;
|
Service::AM::InstallStatus status;
|
||||||
@ -2307,6 +2307,11 @@ void GMainWindow::InstallCIA(QStringList filepaths) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::OnUpdateProgress(std::size_t written, std::size_t total) {
|
void GMainWindow::OnUpdateProgress(std::size_t written, std::size_t total) {
|
||||||
|
if (written == 0 and total == 0) {
|
||||||
|
progress_bar->show();
|
||||||
|
progress_bar->setValue(0);
|
||||||
|
progress_bar->setMaximum(INT_MAX);
|
||||||
|
}
|
||||||
progress_bar->setValue(
|
progress_bar->setValue(
|
||||||
static_cast<int>(INT_MAX * (static_cast<double>(written) / static_cast<double>(total))));
|
static_cast<int>(INT_MAX * (static_cast<double>(written) / static_cast<double>(total))));
|
||||||
}
|
}
|
||||||
@ -2350,11 +2355,19 @@ void GMainWindow::OnCompressFinished(bool is_compress, bool success) {
|
|||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
if (is_compress) {
|
if (is_compress) {
|
||||||
QMessageBox::critical(this, tr("Error compressing file"),
|
QMessageBox::critical(this, tr("Z3DS Compression"),
|
||||||
tr("File compress operation failed, check log for details."));
|
tr("Failed to compress some files, check log for details."));
|
||||||
} else {
|
} else {
|
||||||
QMessageBox::critical(this, tr("Error decompressing file"),
|
QMessageBox::critical(this, tr("Z3DS Compression"),
|
||||||
tr("File decompress operation failed, check log for details."));
|
tr("Failed to decompress some files, check log for details."));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (is_compress) {
|
||||||
|
QMessageBox::information(this, tr("Z3DS Compression"),
|
||||||
|
tr("All files have been compressed successfully."));
|
||||||
|
} else {
|
||||||
|
QMessageBox::information(this, tr("Z3DS Compression"),
|
||||||
|
tr("All files have been decompressed successfully."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3066,43 +3079,27 @@ void GMainWindow::OnDumpVideo() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::OnCompressFile() {
|
static std::optional<std::pair<Loader::AppLoader::CompressFileInfo, size_t>> GetCompressFileInfo(
|
||||||
// NOTE: Encrypted files SHOULD NEVER be compressed, otherwise the resulting
|
const std::string& filepath, bool compress) {
|
||||||
// compressed file will have very poor compression ratios, due to the high
|
|
||||||
// entropy caused by encryption. This may cause confusion to the user as they
|
|
||||||
// will see the files do not compress well and blame the emulator.
|
|
||||||
//
|
|
||||||
// This is enforced using the loaders as they already return an error on encryption.
|
|
||||||
|
|
||||||
QString filepath = QFileDialog::getOpenFileName(
|
|
||||||
this, tr("Load 3DS ROM File"), UISettings::values.roms_path,
|
|
||||||
tr("3DS ROM Files (*.cia *cci *3dsx *cxi)") + QStringLiteral(";;") + tr("All Files (*.*)"));
|
|
||||||
|
|
||||||
if (filepath.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
std::string in_path = filepath.toStdString();
|
|
||||||
|
|
||||||
// Identify file type
|
|
||||||
Loader::AppLoader::CompressFileInfo compress_info{};
|
Loader::AppLoader::CompressFileInfo compress_info{};
|
||||||
compress_info.is_supported = false;
|
compress_info.is_supported = false;
|
||||||
size_t frame_size{};
|
size_t frame_size{};
|
||||||
{
|
auto loader = Loader::GetLoader(filepath);
|
||||||
auto loader = Loader::GetLoader(in_path);
|
if (loader) {
|
||||||
if (loader) {
|
compress_info = loader->GetCompressFileInfo();
|
||||||
compress_info = loader->GetCompressFileInfo();
|
frame_size = FileUtil::Z3DSWriteIOFile::DEFAULT_FRAME_SIZE;
|
||||||
frame_size = FileUtil::Z3DSWriteIOFile::DEFAULT_FRAME_SIZE;
|
} else {
|
||||||
} else {
|
bool is_compressed = false;
|
||||||
bool is_compressed = false;
|
if (Service::AM::CheckCIAToInstall(filepath, is_compressed, compress ? true : false) ==
|
||||||
if (Service::AM::CheckCIAToInstall(in_path, is_compressed, true) ==
|
Service::AM::InstallStatus::Success) {
|
||||||
Service::AM::InstallStatus::Success) {
|
compress_info.is_supported = true;
|
||||||
auto meta_info = Service::AM::GetCIAInfos(in_path);
|
compress_info.is_compressed = is_compressed;
|
||||||
compress_info.is_supported = true;
|
compress_info.recommended_compressed_extension = "zcia";
|
||||||
compress_info.is_compressed = is_compressed;
|
compress_info.recommended_uncompressed_extension = "cia";
|
||||||
compress_info.recommended_compressed_extension = "zcia";
|
compress_info.underlying_magic = std::array<u8, 4>({'C', 'I', 'A', '\0'});
|
||||||
compress_info.recommended_uncompressed_extension = "cia";
|
frame_size = FileUtil::Z3DSWriteIOFile::DEFAULT_CIA_FRAME_SIZE;
|
||||||
compress_info.underlying_magic = std::array<u8, 4>({'C', 'I', 'A', '\0'});
|
if (compress) {
|
||||||
frame_size = FileUtil::Z3DSWriteIOFile::DEFAULT_CIA_FRAME_SIZE;
|
auto meta_info = Service::AM::GetCIAInfos(filepath);
|
||||||
if (meta_info.Succeeded()) {
|
if (meta_info.Succeeded()) {
|
||||||
const auto& meta_info_val = meta_info.Unwrap();
|
const auto& meta_info_val = meta_info.Unwrap();
|
||||||
std::vector<u8> value(sizeof(Service::AM::TitleInfo));
|
std::vector<u8> value(sizeof(Service::AM::TitleInfo));
|
||||||
@ -3117,122 +3114,218 @@ void GMainWindow::OnCompressFile() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!compress_info.is_supported) {
|
if (!compress_info.is_supported) {
|
||||||
QMessageBox::critical(
|
LOG_ERROR(Frontend,
|
||||||
this, tr("Error compressing file"),
|
"Error {} file {}, the selected file is not a compatible 3DS ROM format or is "
|
||||||
tr("The selected file is not a compatible 3DS ROM format. Make sure you have "
|
"encrypted.",
|
||||||
"chosen the right file, and that it is not encrypted."));
|
compress ? "compressing" : "decompressing", filepath);
|
||||||
return;
|
return {};
|
||||||
}
|
}
|
||||||
if (compress_info.is_compressed) {
|
if (compress_info.is_compressed && compress) {
|
||||||
QMessageBox::warning(this, tr("Error compressing file"),
|
LOG_ERROR(Frontend, "Error compressing file {}, the selected file is already compressed",
|
||||||
tr("The selected file is already compressed."));
|
filepath);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (!compress_info.is_compressed && !compress) {
|
||||||
|
LOG_ERROR(Frontend,
|
||||||
|
"Error decompressing file {}, the selected file is already decompressed",
|
||||||
|
filepath);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::pair(compress_info, frame_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GMainWindow::OnCompressFile() {
|
||||||
|
// NOTE: Encrypted files SHOULD NEVER be compressed, otherwise the resulting
|
||||||
|
// compressed file will have very poor compression ratios, due to the high
|
||||||
|
// entropy caused by encryption. This may cause confusion to the user as they
|
||||||
|
// will see the files do not compress well and blame the emulator.
|
||||||
|
//
|
||||||
|
// This is enforced using the loaders as they already return an error on encryption.
|
||||||
|
|
||||||
|
QStringList filepaths =
|
||||||
|
QFileDialog::getOpenFileNames(this, tr("Load 3DS ROM Files"), UISettings::values.roms_path,
|
||||||
|
tr("3DS ROM Files (*.cia *.cci *.3dsx *.cxi)") +
|
||||||
|
QStringLiteral(";;") + tr("All Files (*.*)"));
|
||||||
|
|
||||||
|
QString out_path;
|
||||||
|
|
||||||
|
if (filepaths.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString out_filter =
|
bool single_file = filepaths.size() == 1;
|
||||||
tr("3DS Compressed ROM File (*.%1)")
|
if (single_file) {
|
||||||
.arg(QString::fromStdString(compress_info.recommended_compressed_extension));
|
// If it's a single file, ask the user for the output file.
|
||||||
|
auto compress_info = GetCompressFileInfo(filepaths[0].toStdString(), true);
|
||||||
QFileInfo fileinfo(filepath);
|
if (!compress_info.has_value()) {
|
||||||
QString final_path = fileinfo.path() + QStringLiteral(DIR_SEP) + fileinfo.completeBaseName() +
|
emit CompressFinished(true, false);
|
||||||
QStringLiteral(".") +
|
return;
|
||||||
QString::fromStdString(compress_info.recommended_compressed_extension);
|
|
||||||
|
|
||||||
filepath = QFileDialog::getSaveFileName(this, tr("Save 3DS Compressed ROM File"), final_path,
|
|
||||||
out_filter);
|
|
||||||
if (filepath.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
std::string out_path = filepath.toStdString();
|
|
||||||
|
|
||||||
progress_bar->show();
|
|
||||||
progress_bar->setMaximum(INT_MAX);
|
|
||||||
|
|
||||||
(void)QtConcurrent::run([&, in_path, out_path, compress_info, frame_size] {
|
|
||||||
const auto progress = [&](std::size_t written, std::size_t total) {
|
|
||||||
emit UpdateProgress(written, total);
|
|
||||||
};
|
|
||||||
bool success =
|
|
||||||
FileUtil::CompressZ3DSFile(in_path, out_path, compress_info.underlying_magic,
|
|
||||||
frame_size, progress, compress_info.default_metadata);
|
|
||||||
if (!success) {
|
|
||||||
FileUtil::Delete(out_path);
|
|
||||||
}
|
}
|
||||||
emit OnCompressFinished(true, success);
|
|
||||||
|
QFileInfo fileinfo(filepaths[0]);
|
||||||
|
QString final_path =
|
||||||
|
fileinfo.path() + QStringLiteral(DIR_SEP) + fileinfo.completeBaseName() +
|
||||||
|
QStringLiteral(".") +
|
||||||
|
QString::fromStdString(compress_info.value().first.recommended_compressed_extension);
|
||||||
|
|
||||||
|
QString out_filter = tr("3DS Compressed ROM File (*.%1)")
|
||||||
|
.arg(QString::fromStdString(
|
||||||
|
compress_info.value().first.recommended_compressed_extension));
|
||||||
|
out_path = QFileDialog::getSaveFileName(this, tr("Save 3DS Compressed ROM File"),
|
||||||
|
final_path, out_filter);
|
||||||
|
if (out_path.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise, ask the user the directory to output the files.
|
||||||
|
out_path = QFileDialog::getExistingDirectory(
|
||||||
|
this, tr("Select Output 3DS Compressed ROM Folder"), UISettings::values.roms_path,
|
||||||
|
QFileDialog::ShowDirsOnly);
|
||||||
|
if (out_path.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)QtConcurrent::run([&, filepaths, out_path] {
|
||||||
|
bool single_file = filepaths.size() == 1;
|
||||||
|
QString out_filepath;
|
||||||
|
bool total_success = true;
|
||||||
|
|
||||||
|
for (const QString& filepath : filepaths) {
|
||||||
|
|
||||||
|
std::string in_path = filepath.toStdString();
|
||||||
|
|
||||||
|
// Identify file type
|
||||||
|
auto compress_info = GetCompressFileInfo(filepath.toStdString(), true);
|
||||||
|
if (!compress_info.has_value()) {
|
||||||
|
total_success = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (single_file) {
|
||||||
|
out_filepath = out_path;
|
||||||
|
} else {
|
||||||
|
QFileInfo fileinfo(filepath);
|
||||||
|
out_filepath = out_path + QStringLiteral(DIR_SEP) + fileinfo.completeBaseName() +
|
||||||
|
QStringLiteral(".") +
|
||||||
|
QString::fromStdString(
|
||||||
|
compress_info.value().first.recommended_compressed_extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string out_path = out_filepath.toStdString();
|
||||||
|
|
||||||
|
emit UpdateProgress(0, 0);
|
||||||
|
|
||||||
|
const auto progress = [&](std::size_t written, std::size_t total) {
|
||||||
|
emit UpdateProgress(written, total);
|
||||||
|
};
|
||||||
|
bool success = FileUtil::CompressZ3DSFile(in_path, out_path,
|
||||||
|
compress_info.value().first.underlying_magic,
|
||||||
|
compress_info.value().second, progress,
|
||||||
|
compress_info.value().first.default_metadata);
|
||||||
|
if (!success) {
|
||||||
|
total_success = false;
|
||||||
|
FileUtil::Delete(out_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit CompressFinished(true, total_success);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::OnDecompressFile() {
|
void GMainWindow::OnDecompressFile() {
|
||||||
QString filepath = QFileDialog::getOpenFileName(
|
|
||||||
this, tr("Load 3DS Compressed ROM File"), UISettings::values.roms_path,
|
QStringList filepaths = QFileDialog::getOpenFileNames(
|
||||||
|
this, tr("Load 3DS Compressed ROM Files"), UISettings::values.roms_path,
|
||||||
tr("3DS Compressed ROM Files (*.zcia *zcci *z3dsx *zcxi)") + QStringLiteral(";;") +
|
tr("3DS Compressed ROM Files (*.zcia *zcci *z3dsx *zcxi)") + QStringLiteral(";;") +
|
||||||
tr("All Files (*.*)"));
|
tr("All Files (*.*)"));
|
||||||
|
|
||||||
if (filepath.isEmpty()) {
|
QString out_path;
|
||||||
|
|
||||||
|
if (filepaths.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::string in_path = filepath.toStdString();
|
|
||||||
|
|
||||||
// Identify file type
|
bool single_file = filepaths.size() == 1;
|
||||||
Loader::AppLoader::CompressFileInfo compress_info{};
|
if (single_file) {
|
||||||
compress_info.is_supported = false;
|
// If it's a single file, ask the user for the output file.
|
||||||
{
|
auto compress_info = GetCompressFileInfo(filepaths[0].toStdString(), false);
|
||||||
auto loader = Loader::GetLoader(in_path);
|
if (!compress_info.has_value()) {
|
||||||
if (loader) {
|
emit CompressFinished(false, false);
|
||||||
compress_info = loader->GetCompressFileInfo();
|
return;
|
||||||
} else {
|
}
|
||||||
bool is_compressed = false;
|
|
||||||
if (Service::AM::CheckCIAToInstall(in_path, is_compressed, false) ==
|
QFileInfo fileinfo(filepaths[0]);
|
||||||
Service::AM::InstallStatus::Success) {
|
QString final_path =
|
||||||
compress_info.is_supported = true;
|
fileinfo.path() + QStringLiteral(DIR_SEP) + fileinfo.completeBaseName() +
|
||||||
compress_info.is_compressed = is_compressed;
|
QStringLiteral(".") +
|
||||||
compress_info.recommended_compressed_extension = "zcia";
|
QString::fromStdString(compress_info.value().first.recommended_uncompressed_extension);
|
||||||
compress_info.recommended_uncompressed_extension = "cia";
|
|
||||||
compress_info.underlying_magic = std::array<u8, 4>({'C', 'I', 'A', '\0'});
|
QString out_filter =
|
||||||
|
tr("3DS ROM File (*.%1)")
|
||||||
|
.arg(QString::fromStdString(
|
||||||
|
compress_info.value().first.recommended_uncompressed_extension));
|
||||||
|
out_path =
|
||||||
|
QFileDialog::getSaveFileName(this, tr("Save 3DS ROM File"), final_path, out_filter);
|
||||||
|
if (out_path.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise, ask the user the directory to output the files.
|
||||||
|
out_path = QFileDialog::getExistingDirectory(this, tr("Select Output 3DS ROM Folder"),
|
||||||
|
UISettings::values.roms_path,
|
||||||
|
QFileDialog::ShowDirsOnly);
|
||||||
|
if (out_path.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)QtConcurrent::run([&, filepaths, out_path] {
|
||||||
|
bool single_file = filepaths.size() == 1;
|
||||||
|
QString out_filepath;
|
||||||
|
bool total_success = true;
|
||||||
|
|
||||||
|
for (const QString& filepath : filepaths) {
|
||||||
|
|
||||||
|
std::string in_path = filepath.toStdString();
|
||||||
|
|
||||||
|
// Identify file type
|
||||||
|
auto compress_info = GetCompressFileInfo(filepath.toStdString(), false);
|
||||||
|
if (!compress_info.has_value()) {
|
||||||
|
total_success = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (single_file) {
|
||||||
|
out_filepath = out_path;
|
||||||
|
} else {
|
||||||
|
QFileInfo fileinfo(filepath);
|
||||||
|
out_filepath = out_path + QStringLiteral(DIR_SEP) + fileinfo.completeBaseName() +
|
||||||
|
QStringLiteral(".") +
|
||||||
|
QString::fromStdString(
|
||||||
|
compress_info.value().first.recommended_uncompressed_extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string out_path = out_filepath.toStdString();
|
||||||
|
|
||||||
|
emit UpdateProgress(0, 0);
|
||||||
|
|
||||||
|
const auto progress = [&](std::size_t written, std::size_t total) {
|
||||||
|
emit UpdateProgress(written, total);
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO(PabloMK7): What should we do with the metadata?
|
||||||
|
bool success = FileUtil::DeCompressZ3DSFile(in_path, out_path, progress);
|
||||||
|
if (!success) {
|
||||||
|
total_success = false;
|
||||||
|
FileUtil::Delete(out_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (!compress_info.is_supported) {
|
|
||||||
QMessageBox::critical(this, tr("Error decompressing file"),
|
|
||||||
tr("The selected file is not a compatible compressed 3DS ROM format. "
|
|
||||||
"Make sure you have "
|
|
||||||
"chosen the right file."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!compress_info.is_compressed) {
|
|
||||||
QMessageBox::warning(this, tr("Error decompressing file"),
|
|
||||||
tr("The selected file is already decompressed."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString out_filter =
|
emit CompressFinished(false, total_success);
|
||||||
tr("3DS ROM File (*.%1)")
|
|
||||||
.arg(QString::fromStdString(compress_info.recommended_uncompressed_extension));
|
|
||||||
|
|
||||||
QFileInfo fileinfo(filepath);
|
|
||||||
QString final_path = fileinfo.path() + QStringLiteral(DIR_SEP) + fileinfo.completeBaseName() +
|
|
||||||
QStringLiteral(".") +
|
|
||||||
QString::fromStdString(compress_info.recommended_uncompressed_extension);
|
|
||||||
|
|
||||||
filepath = QFileDialog::getSaveFileName(this, tr("Save 3DS ROM File"), final_path, out_filter);
|
|
||||||
if (filepath.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
std::string out_path = filepath.toStdString();
|
|
||||||
|
|
||||||
progress_bar->show();
|
|
||||||
progress_bar->setMaximum(INT_MAX);
|
|
||||||
|
|
||||||
(void)QtConcurrent::run([&, in_path, out_path, compress_info] {
|
|
||||||
const auto progress = [&](std::size_t written, std::size_t total) {
|
|
||||||
emit UpdateProgress(written, total);
|
|
||||||
};
|
|
||||||
// TODO(PabloMK7): What should we do with the metadata?
|
|
||||||
bool success = FileUtil::DeCompressZ3DSFile(in_path, out_path, progress);
|
|
||||||
if (!success) {
|
|
||||||
FileUtil::Delete(out_path);
|
|
||||||
}
|
|
||||||
emit OnCompressFinished(false, success);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -164,7 +164,11 @@ std::unique_ptr<AppLoader> GetLoader(const std::string& filename) {
|
|||||||
FileType filename_type = GuessFromExtension(filename_extension);
|
FileType filename_type = GuessFromExtension(filename_extension);
|
||||||
|
|
||||||
if (type != filename_type) {
|
if (type != filename_type) {
|
||||||
LOG_WARNING(Loader, "File {} has a different type than its extension.", filename);
|
// Do not show the error for CIA files, as their type cannot be determined.
|
||||||
|
if (!(type == FileType::Unknown && filename_type == FileType::CIA)) {
|
||||||
|
LOG_WARNING(Loader, "File {} has a different type than its extension.", filename);
|
||||||
|
}
|
||||||
|
|
||||||
if (FileType::Unknown == type)
|
if (FileType::Unknown == type)
|
||||||
type = filename_type;
|
type = filename_type;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user