[APP] Create and Extract Zarchive packages

This commit is contained in:
Adrian 2024-04-10 15:57:15 +01:00 committed by Radosław Gliński
parent 06d7a5f0a3
commit 0fcdc12cb9
7 changed files with 389 additions and 34 deletions

View File

@ -553,17 +553,26 @@ bool EmulatorWindow::Initialize() {
auto main_menu = MenuItem::Create(MenuItem::Type::kNormal);
auto file_menu = MenuItem::Create(MenuItem::Type::kPopup, "&File");
auto recent_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Open Recent");
auto zar_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Zar Package");
FillRecentlyLaunchedTitlesMenu(recent_menu.get());
{
file_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, "&Open...", "Ctrl+O",
std::bind(&EmulatorWindow::FileOpen, this)));
file_menu->AddChild(std::move(recent_menu));
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
file_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, "Install Content...",
std::bind(&EmulatorWindow::InstallContent, this)));
zar_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, "Create",
std::bind(&EmulatorWindow::CreateZarchive, this)));
zar_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, "Extract",
std::bind(&EmulatorWindow::ExtractZarchive, this)));
file_menu->AddChild(std::move(zar_menu));
#ifdef DEBUG
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
file_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, "Close",
std::bind(&EmulatorWindow::FileClose, this)));
@ -958,7 +967,10 @@ void EmulatorWindow::InstallContent() {
paths = file_picker->selected_files();
}
if (!paths.empty()) {
if (paths.empty()) {
return;
}
for (auto path : paths) {
// Normalize the path and make absolute.
auto abs_path = std::filesystem::absolute(path);
@ -974,6 +986,141 @@ void EmulatorWindow::InstallContent() {
}
}
}
void EmulatorWindow::ExtractZarchive() {
std::vector<std::filesystem::path> zarchive_files;
std::filesystem::path extract_dir;
auto file_picker = xe::ui::FilePicker::Create();
file_picker->set_mode(ui::FilePicker::Mode::kOpen);
file_picker->set_type(ui::FilePicker::Type::kFile);
file_picker->set_multi_selection(true);
file_picker->set_title("Select Zar Package");
file_picker->set_extensions({
{"Zarchive Files (*.zar)", "*.zar"},
});
if (file_picker->Show(window_.get())) {
zarchive_files = file_picker->selected_files();
}
if (zarchive_files.empty()) {
return;
}
file_picker->set_type(ui::FilePicker::Type::kDirectory);
file_picker->set_title("Select Directory to Extract");
if (file_picker->Show(window_.get())) {
extract_dir = file_picker->selected_files().front();
}
if (extract_dir.empty()) {
return;
}
for (auto& zarchive_file_path : zarchive_files) {
// Normalize the path and make absolute.
auto abs_path = std::filesystem::absolute(zarchive_file_path);
std::filesystem::path abs_extract_dir;
if (zarchive_files.size() > 1) {
abs_extract_dir =
std::filesystem::absolute((extract_dir / abs_path.stem()));
} else {
abs_extract_dir = std::filesystem::absolute(extract_dir);
}
XELOGI("Extracting zar package: {}\n",
zarchive_file_path.filename().string());
auto result = emulator_->ExtractZarchivePackage(abs_path, abs_extract_dir);
if (result != X_STATUS_SUCCESS) {
std::error_code ec;
// delete incomplete output file
std::filesystem::remove(abs_extract_dir, ec);
XELOGE("Failed to extract Zarchive package.", result);
xe::ui::ImGuiDialog::ShowMessageBox(
imgui_drawer_.get(), "Failed to extract Zarchive package.",
"Failed to extract Zarchive package.");
}
}
}
void EmulatorWindow::CreateZarchive() {
std::vector<std::filesystem::path> content_dirs;
std::filesystem::path zarchive_dir;
auto file_picker = xe::ui::FilePicker::Create();
file_picker->set_mode(ui::FilePicker::Mode::kOpen);
file_picker->set_type(ui::FilePicker::Type::kDirectory);
file_picker->set_multi_selection(true);
file_picker->set_title("Select Contents");
if (file_picker->Show(window_.get())) {
content_dirs = file_picker->selected_files();
}
if (content_dirs.empty()) {
return;
}
if (content_dirs.size() == 1) {
file_picker->set_mode(ui::FilePicker::Mode::kSave);
file_picker->set_type(ui::FilePicker::Type::kFile);
file_picker->set_multi_selection(false);
file_picker->set_file_name(content_dirs.front().stem().string());
file_picker->set_default_extension("zar");
file_picker->set_title("Zarchive File");
file_picker->set_extensions({
{"Zarchive File (*.zar)", "*.zar"},
});
} else {
file_picker->set_title("Output Directory");
}
if (file_picker->Show(window_.get())) {
zarchive_dir = file_picker->selected_files().front();
}
if (zarchive_dir.empty()) {
return;
}
for (auto& content_path : content_dirs) {
// Normalize the path and make absolute.
auto abs_content_dir = std::filesystem::absolute(content_path);
std::filesystem::path abs_zarchive_file;
if (content_dirs.size() > 1) {
abs_zarchive_file = std::filesystem::absolute(
(zarchive_dir / abs_content_dir.stem()).replace_extension("zar"));
} else {
abs_zarchive_file = std::filesystem::absolute(zarchive_dir);
}
XELOGI("Creating zar package: {}\n", abs_zarchive_file.filename().string());
auto result =
emulator_->CreateZarchivePackage(abs_content_dir, abs_zarchive_file);
if (result != X_ERROR_SUCCESS) {
std::error_code ec;
// delete incomplete output file
std::filesystem::remove(abs_zarchive_file, ec);
XELOGE("Failed to create Zarchive package.", result);
xe::ui::ImGuiDialog::ShowMessageBox(imgui_drawer_.get(),
"Failed to create Zarchive package.",
"Failed to create Zarchive package.");
}
}
}
void EmulatorWindow::ShowContentDirectory() {

View File

@ -206,6 +206,8 @@ class EmulatorWindow {
void FileOpen();
void FileClose();
void InstallContent();
void ExtractZarchive();
void CreateZarchive();
void ShowContentDirectory();
void CpuTimeScalarReset();
void CpuTimeScalarSetHalf();

View File

@ -16,6 +16,8 @@
#include "third_party/fmt/include/fmt/format.h"
#include "third_party/tabulate/single_include/tabulate/tabulate.hpp"
#include "third_party/zarchive/include/zarchive/zarchivecommon.h"
#include "third_party/zarchive/include/zarchive/zarchivewriter.h"
#include "third_party/zarchive/src/sha_256.h"
#include "xenia/apu/audio_system.h"
#include "xenia/base/assert.h"
#include "xenia/base/byte_stream.h"
@ -613,6 +615,124 @@ X_STATUS Emulator::InstallContentPackage(const std::filesystem::path& path) {
installation_path);
}
X_STATUS Emulator::ExtractZarchivePackage(
const std::filesystem::path& path,
const std::filesystem::path& extract_dir) {
std::unique_ptr<vfs::Device> device =
std::make_unique<vfs::DiscZarchiveDevice>("", path);
if (!device->Initialize()) {
XELOGE("Failed to initialize device");
return X_STATUS_INVALID_PARAMETER;
}
if (std::filesystem::exists(extract_dir)) {
// TODO(Gliniak): Popup
// Do you want to overwrite already existing data?
} else {
std::error_code error_code;
std::filesystem::create_directories(extract_dir, error_code);
if (error_code) {
return error_code.value();
}
}
return vfs::VirtualFileSystem::ExtractContentFiles(device.get(), extract_dir);
}
X_STATUS Emulator::CreateZarchivePackage(
const std::filesystem::path& inputDirectory,
const std::filesystem::path& outputFile) {
std::vector<uint8_t> buffer;
buffer.resize(64 * 1024);
std::error_code ec;
PackContext packContext;
packContext.outputFilePath = outputFile;
ZArchiveWriter zWriter(
[](int32_t partIndex, void* ctx) {
PackContext* packContext = reinterpret_cast<PackContext*>(ctx);
packContext->currentOutputFile =
std::ofstream(packContext->outputFilePath, std::ios::binary);
if (!packContext->currentOutputFile.is_open()) {
XELOGI("Failed to create output file: {}\n",
packContext->outputFilePath.string());
packContext->hasError = true;
}
},
[](const void* data, size_t length, void* ctx) {
PackContext* packContext = reinterpret_cast<PackContext*>(ctx);
packContext->currentOutputFile.write(
reinterpret_cast<const char*>(data), length);
},
&packContext);
if (packContext.hasError) {
return X_STATUS_UNSUCCESSFUL;
}
for (auto const& dirEntry :
std::filesystem::recursive_directory_iterator(inputDirectory)) {
std::filesystem::path pathEntry =
std::filesystem::relative(dirEntry.path(), inputDirectory, ec);
if (ec) {
XELOGI("Failed to get relative path {}\n", pathEntry.string());
return X_STATUS_UNSUCCESSFUL;
}
if (dirEntry.is_directory()) {
if (!zWriter.MakeDir(pathEntry.generic_string().c_str(), false)) {
XELOGI("Failed to create directory {}\n", pathEntry.string());
return X_STATUS_UNSUCCESSFUL;
}
} else if (dirEntry.is_regular_file()) {
// Don't pack itself to prevent infinite packing.
if (dirEntry == outputFile) {
continue;
}
XELOGI("Adding file: {}\n", pathEntry.string());
if (!zWriter.StartNewFile(pathEntry.generic_string().c_str())) {
XELOGI("Failed to create archive file {}\n", pathEntry.string());
return X_STATUS_UNSUCCESSFUL;
}
std::filesystem::path file_to_pack_path = inputDirectory / pathEntry;
FILE* file = xe::filesystem::OpenFile(file_to_pack_path, "rb");
if (!file) {
XELOGI("Failed to open input file {}\n", pathEntry.string());
return X_STATUS_UNSUCCESSFUL;
}
const uint64_t file_size = std::filesystem::file_size(file_to_pack_path);
uint64_t total_bytes_read = 0;
while (total_bytes_read < file_size) {
uint64_t bytes_read =
fread_s(buffer.data(), buffer.size(), 1, buffer.size(), file);
total_bytes_read += bytes_read;
zWriter.AppendData(buffer.data(), bytes_read);
}
fclose(file);
}
if (packContext.hasError) {
return X_STATUS_UNSUCCESSFUL;
}
}
zWriter.Finalize();
return X_STATUS_SUCCESS;
}
void Emulator::Pause() {
if (paused_) {
return;

View File

@ -226,6 +226,21 @@ class Emulator {
// Extract content of package to content specific directory.
X_STATUS InstallContentPackage(const std::filesystem::path& path);
// Extract content of zar package to desired directory.
X_STATUS Emulator::ExtractZarchivePackage(
const std::filesystem::path& path,
const std::filesystem::path& extract_dir);
// Pack contents of a folder into a zar package.
X_STATUS CreateZarchivePackage(const std::filesystem::path& inputDirectory,
const std::filesystem::path& outputFile);
struct PackContext {
std::filesystem::path outputFilePath;
std::ofstream currentOutputFile;
bool hasError{false};
};
void Pause();
void Resume();
bool is_paused() const { return paused_; }

View File

@ -50,6 +50,16 @@ class FilePicker {
const std::string& title() const { return title_; }
void set_title(std::string title) { title_ = std::move(title); }
const std::string& default_extension() const { return default_extension_; }
void set_default_extension(std::string default_extension) {
default_extension_ = std::move(default_extension);
}
const std::string& file_name() const { return file_name_; }
void set_file_name(std::string file_name) {
file_name_ = std::move(file_name);
}
std::vector<std::pair<std::string, std::string>> extensions() const {
return extensions_;
}
@ -76,6 +86,8 @@ class FilePicker {
Mode mode_;
Type type_;
std::string title_;
std::string default_extension_;
std::string file_name_;
std::vector<std::pair<std::string, std::string>> extensions_;
bool multi_selection_;

View File

@ -111,22 +111,57 @@ Win32FilePicker::Win32FilePicker() = default;
Win32FilePicker::~Win32FilePicker() = default;
bool Win32FilePicker::Show(Window* parent_window) {
// TODO(benvanik): FileSaveDialog.
assert_true(mode() == Mode::kOpen);
Microsoft::WRL::ComPtr<IFileDialog> file_dialog;
Microsoft::WRL::ComPtr<IFileOpenDialog> file_dialog;
HRESULT hr =
CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER,
Microsoft::WRL::ComPtr<IFileOpenDialog> open_dialog;
Microsoft::WRL::ComPtr<IFileSaveDialog> save_dialog;
HRESULT hr;
if (mode() == Mode::kOpen) {
hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&file_dialog));
if (!SUCCEEDED(hr)) {
return false;
}
hr = file_dialog.As(&open_dialog);
} else {
hr = CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&file_dialog));
if (!SUCCEEDED(hr)) {
return false;
}
hr = file_dialog.As(&save_dialog);
}
if (!SUCCEEDED(hr)) {
return false;
}
hr = file_dialog->SetDefaultExtension(
(LPCWSTR)xe::to_utf16(default_extension()).c_str());
if (!SUCCEEDED(hr)) {
return false;
}
hr = file_dialog->SetTitle((LPCWSTR)xe::to_utf16(title()).c_str());
if (!SUCCEEDED(hr)) {
return false;
}
hr = file_dialog->SetDefaultExtension(
(LPCWSTR)xe::to_utf16(default_extension()).c_str());
if (!SUCCEEDED(hr)) {
return false;
}
hr = file_dialog->SetFileName((LPCWSTR)xe::to_utf16(file_name()).c_str());
if (!SUCCEEDED(hr)) {
return false;
}
DWORD flags;
hr = file_dialog->GetOptions(&flags);
if (!SUCCEEDED(hr)) {
@ -192,10 +227,11 @@ bool Win32FilePicker::Show(Window* parent_window) {
return false;
}
if (mode() == Mode::kOpen) {
// Obtain the result once the user clicks the 'Open' button.
// The result is an IShellItem object.
Microsoft::WRL::ComPtr<IShellItemArray> shell_items;
hr = file_dialog->GetResults(&shell_items);
hr = open_dialog->GetResults(&shell_items);
if (!SUCCEEDED(hr)) {
return false;
}
@ -218,6 +254,27 @@ bool Win32FilePicker::Show(Window* parent_window) {
CoTaskMemFree(file_path);
}
set_selected_files(selected_files);
} else {
// Obtain the result once the user clicks the 'Save' button.
// The result is an IShellItem object.
Microsoft::WRL::ComPtr<IShellItem> shell_item;
hr = save_dialog->GetResult(&shell_item);
if (!SUCCEEDED(hr)) {
return false;
}
std::vector<std::filesystem::path> selected_files;
PWSTR file_path = nullptr;
hr = shell_item->GetDisplayName(SIGDN_FILESYSPATH, &file_path);
if (!SUCCEEDED(hr)) {
return false;
}
selected_files.push_back(std::filesystem::path(file_path));
CoTaskMemFree(file_path);
set_selected_files(selected_files);
}
return true;
}

View File

@ -18,4 +18,6 @@ project("zarchive")
"zarchive/include/zarchive/zarchivewriter.h",
"zarchive/src/zarchivereader.cpp",
"zarchive/src/zarchivewriter.cpp",
"zarchive/src/sha_256.c",
"zarchive/src/sha_256.h",
})