[APP] Create and Extract Zarchive packages
This commit is contained in:
parent
06d7a5f0a3
commit
0fcdc12cb9
|
@ -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() {
|
||||
|
|
|
@ -206,6 +206,8 @@ class EmulatorWindow {
|
|||
void FileOpen();
|
||||
void FileClose();
|
||||
void InstallContent();
|
||||
void ExtractZarchive();
|
||||
void CreateZarchive();
|
||||
void ShowContentDirectory();
|
||||
void CpuTimeScalarReset();
|
||||
void CpuTimeScalarSetHalf();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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_; }
|
||||
|
|
|
@ -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_;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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",
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue