Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[VFS] Add support for loading ZArchive files #2157

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,9 @@
[submodule "third_party/VulkanMemoryAllocator"]
path = third_party/VulkanMemoryAllocator
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
[submodule "third_party/zstd"]
path = third_party/zstd
url = https://github.com/facebook/zstd.git
[submodule "third_party/zarchive"]
path = third_party/zarchive
url = https://github.com/exzap/ZArchive/
2 changes: 2 additions & 0 deletions premake5.lua
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ workspace("xenia")
include("third_party/mspack.lua")
include("third_party/snappy.lua")
include("third_party/xxhash.lua")
include("third_party/zarchive.lua")
include("third_party/zstd.lua")

if not os.istarget("android") then
-- SDL2 requires sdl2-config, and as of November 2020 isn't high-quality on
Expand Down
3 changes: 2 additions & 1 deletion src/xenia/app/emulator_window.cc
Original file line number Diff line number Diff line change
Expand Up @@ -825,8 +825,9 @@ void EmulatorWindow::FileOpen() {
file_picker->set_multi_selection(false);
file_picker->set_title("Select Content Package");
file_picker->set_extensions({
{"Supported Files", "*.iso;*.xex;*.*"},
{"Supported Files", "*.iso;*.xex;*.zar;*.*"},
{"Disc Image (*.iso)", "*.iso"},
{"Disc Archive (*.zar)", "*.zar"},
{"Xbox Executable (*.xex)", "*.xex"},
//{"Content Package (*.xcp)", "*.xcp" },
{"All Files (*.*)", "*.*"},
Expand Down
27 changes: 27 additions & 0 deletions src/xenia/emulator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
#include "xenia/ui/window.h"
#include "xenia/ui/windowed_app_context.h"
#include "xenia/vfs/devices/disc_image_device.h"
#include "xenia/vfs/devices/disc_zarchive_device.h"
#include "xenia/vfs/devices/host_path_device.h"
#include "xenia/vfs/devices/null_device.h"
#include "xenia/vfs/devices/stfs_container_device.h"
Expand Down Expand Up @@ -285,6 +286,9 @@ X_STATUS Emulator::LaunchPath(const std::filesystem::path& path) {
if (extension == ".xex" || extension == ".elf" || extension == ".exe") {
// Treat as a naked xex file.
return LaunchXexFile(path);
} else if (extension == ".zar") {
// Assume a disc image.
return LaunchDiscArchive(path);
} else {
// Assume a disc image.
return LaunchDiscImage(path);
Expand Down Expand Up @@ -349,6 +353,29 @@ X_STATUS Emulator::LaunchDiscImage(const std::filesystem::path& path) {
return CompleteLaunch(path, module_path);
}

X_STATUS Emulator::LaunchDiscArchive(const std::filesystem::path& path) {
auto mount_path = "\\Device\\Cdrom0";

// Register the disc image in the virtual filesystem.
auto device = std::make_unique<vfs::DiscZarchiveDevice>(mount_path, path);
if (!device->Initialize()) {
xe::FatalError("Unable to mount disc image; file not found or corrupt.");
return X_STATUS_NO_SUCH_FILE;
}
if (!file_system_->RegisterDevice(std::move(device))) {
xe::FatalError("Unable to register disc image.");
return X_STATUS_NO_SUCH_FILE;
}

// Create symlinks to the device.
file_system_->RegisterSymbolicLink("game:", mount_path);
file_system_->RegisterSymbolicLink("d:", mount_path);

// Launch the game.
auto module_path(FindLaunchModule());
return CompleteLaunch(path, module_path);
}

X_STATUS Emulator::LaunchStfsContainer(const std::filesystem::path& path) {
auto mount_path = "\\Device\\Cdrom0";

Expand Down
3 changes: 3 additions & 0 deletions src/xenia/emulator.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ class Emulator {
// Launches a game from a disc image file (.iso, etc).
X_STATUS LaunchDiscImage(const std::filesystem::path& path);

// Launches a game from a disc archive file (.zar, etc).
X_STATUS LaunchDiscArchive(const std::filesystem::path& path);

// Launches a game from an STFS container file.
X_STATUS LaunchStfsContainer(const std::filesystem::path& path);

Expand Down
139 changes: 139 additions & 0 deletions src/xenia/vfs/devices/disc_zarchive_device.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/

#include "xenia/vfs/devices/disc_zarchive_device.h"

#include "xenia/base/literals.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/vfs/devices/disc_zarchive_entry.h"

#include "third_party/zarchive/include/zarchive/zarchivereader.h"

namespace xe {
namespace vfs {

using namespace xe::literals;

const size_t kXESectorSize = 2_KiB;

DiscZarchiveDevice::DiscZarchiveDevice(const std::string_view mount_path,
const std::filesystem::path& host_path)
: Device(mount_path),
name_("GDFX"),
host_path_(host_path),
opaque_(nullptr) {}

DiscZarchiveDevice::~DiscZarchiveDevice() {
ZArchiveReader* reader = static_cast<ZArchiveReader*>(opaque_);
if (reader != nullptr) delete reader;
};

bool DiscZarchiveDevice::Initialize() {
ZArchiveReader* reader = nullptr;
reader = ZArchiveReader::OpenFromFile(host_path_);

if (!reader) {
XELOGE("Disc ZArchive could not be opened");
return false;
}

opaque_ = static_cast<void*>(reader);
bool result = false;

result = reader->IsFile(reader->LookUp("default.xex", true, false));
if (!result) XELOGE("Failed to verify disc ZArchive (no default.xex)");

const std::string root_path = std::string("/");
ZArchiveNodeHandle handle = reader->LookUp(root_path);
auto root_entry = new DiscZarchiveEntry(this, nullptr, root_path, reader);
root_entry->attributes_ = kFileAttributeDirectory;
root_entry->handle_ = static_cast<uint32_t>(handle);
root_entry->name_ = root_path;
root_entry->absolute_path_ = root_path;
root_entry_ = std::unique_ptr<Entry>(root_entry);
result = ReadAllEntries(reader, "", root_entry, nullptr);

return result;
}

void DiscZarchiveDevice::Dump(StringBuffer* string_buffer) {
auto global_lock = global_critical_region_.Acquire();
root_entry_->Dump(string_buffer, 0);
}

Entry* DiscZarchiveDevice::ResolvePath(const std::string_view path) {
// The filesystem will have stripped our prefix off already, so the path will
// be in the form:
// some\PATH.foo
XELOGFS("DiscZarchiveDevice::ResolvePath({})", path);

ZArchiveReader* reader = static_cast<ZArchiveReader*>(opaque_);
if (!reader) return nullptr;

ZArchiveNodeHandle handle = reader->LookUp(path);
bool result = (handle != ZARCHIVE_INVALID_NODE);

if (!result) return nullptr;

return root_entry_->ResolvePath(path);
}

bool DiscZarchiveDevice::ReadAllEntries(void* opaque, const std::string& path,
DiscZarchiveEntry* node,
DiscZarchiveEntry* parent) {
ZArchiveReader* reader = static_cast<ZArchiveReader*>(opaque);
ZArchiveNodeHandle handle = node->handle_;
if (handle == ZARCHIVE_INVALID_NODE) return false;
if (reader->IsDirectory(handle)) {
uint32_t count = reader->GetDirEntryCount(handle);
for (uint32_t i = 0; i < count; i++) {
ZArchiveReader::DirEntry dirEntry;
if (!reader->GetDirEntry(handle, i, dirEntry)) return false;
std::string full_path = path + std::string(dirEntry.name);
ZArchiveNodeHandle fileHandle = reader->LookUp(full_path);
if (handle == ZARCHIVE_INVALID_NODE) return false;
auto entry = new DiscZarchiveEntry(this, parent, full_path, opaque);
entry->handle_ = static_cast<uint32_t>(fileHandle);
entry->data_offset_ = 0;
// Set to January 1, 1970 (UTC) in 100-nanosecond intervals
entry->create_timestamp_ = 10000 * 11644473600000LL;
entry->access_timestamp_ = 10000 * 11644473600000LL;
entry->write_timestamp_ = 10000 * 11644473600000LL;
entry->parent_ = node;
if (dirEntry.isDirectory) {
entry->data_size_ = 0;
entry->size_ = dirEntry.size;
entry->attributes_ = kFileAttributeDirectory | kFileAttributeReadOnly;
node->children_.push_back(std::unique_ptr<Entry>(entry));
if (!ReadAllEntries(reader, full_path + "\\", entry, node))
return false;
} else if (dirEntry.isFile) {
entry->data_size_ = entry->size_ = reader->GetFileSize(fileHandle);
entry->attributes_ = kFileAttributeReadOnly;
entry->allocation_size_ =
xe::round_up(entry->size_, bytes_per_sector());
node->children_.push_back(std::unique_ptr<Entry>(entry));
}
}
return true;
} else if (reader->IsFile(handle)) {
auto entry = new DiscZarchiveEntry(this, parent, path, opaque);
entry->attributes_ = kFileAttributeReadOnly;
entry->handle_ = static_cast<uint32_t>(handle);
entry->parent_ = parent;
entry->children_.push_back(std::unique_ptr<Entry>(entry));
return true;
}

return false;
}

} // namespace vfs
} // namespace xe
56 changes: 56 additions & 0 deletions src/xenia/vfs/devices/disc_zarchive_device.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/

#ifndef XENIA_VFS_DEVICES_DISC_ZARCHIVE_DEVICE_H_
#define XENIA_VFS_DEVICES_DISC_ZARCHIVE_DEVICE_H_

#include <memory>
#include <string>

#include "xenia/base/mapped_memory.h"
#include "xenia/vfs/device.h"

namespace xe {
namespace vfs {

class DiscZarchiveEntry;

class DiscZarchiveDevice : public Device {
public:
DiscZarchiveDevice(const std::string_view mount_path,
const std::filesystem::path& host_path);
~DiscZarchiveDevice() override;

bool Initialize() override;
void Dump(StringBuffer* string_buffer) override;
Entry* ResolvePath(const std::string_view path) override;

const std::string& name() const override { return name_; }
uint32_t attributes() const override { return 0; }
uint32_t component_name_max_length() const override { return 255; }

uint32_t total_allocation_units() const override { return 128 * 1024; }
uint32_t available_allocation_units() const override { return 0; }
uint32_t sectors_per_allocation_unit() const override { return 1; }
uint32_t bytes_per_sector() const override { return 0x200; }

private:
bool ReadAllEntries(void* opaque, const std::string& path,
DiscZarchiveEntry* node, DiscZarchiveEntry* parent);

std::string name_;
std::filesystem::path host_path_;
std::unique_ptr<Entry> root_entry_;
void* opaque_;
};

} // namespace vfs
} // namespace xe

#endif // XENIA_VFS_DEVICES_DISC_ZARCHIVE_DEVICE_H_
50 changes: 50 additions & 0 deletions src/xenia/vfs/devices/disc_zarchive_entry.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/

#include "xenia/vfs/devices/disc_zarchive_entry.h"

#include <algorithm>

#include "xenia/base/math.h"
#include "xenia/vfs/devices/disc_zarchive_file.h"

#include "third_party/zarchive/include/zarchive/zarchivereader.h"
namespace xe {
namespace vfs {

DiscZarchiveEntry::DiscZarchiveEntry(Device* device, Entry* parent,
const std::string_view path, void* opaque)
: Entry(device, parent, path),
opaque_(opaque),
data_offset_(0),
data_size_(0),
handle_(ZARCHIVE_INVALID_NODE) {}

DiscZarchiveEntry::~DiscZarchiveEntry() = default;

std::unique_ptr<DiscZarchiveEntry> DiscZarchiveEntry::Create(
Device* device, Entry* parent, const std::string_view name, void* opaque) {
auto path = name; // xe::utf8::join_guest_paths(parent->path(), name);
auto entry =
std::make_unique<DiscZarchiveEntry>(device, parent, path, opaque);
return std::move(entry);
}

X_STATUS DiscZarchiveEntry::Open(uint32_t desired_access, File** out_file) {
*out_file = new DiscZarchiveFile(desired_access, this);
return X_STATUS_SUCCESS;
}

std::unique_ptr<MappedMemory> DiscZarchiveEntry::OpenMapped(
MappedMemory::Mode mode, size_t offset, size_t length) {
return nullptr;
}

} // namespace vfs
} // namespace xe
59 changes: 59 additions & 0 deletions src/xenia/vfs/devices/disc_zarchive_entry.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/

#ifndef XENIA_VFS_DEVICES_DISC_ZARCHIVE_ENTRY_H_
#define XENIA_VFS_DEVICES_DISC_ZARCHIVE_ENTRY_H_

#include <string>
#include <vector>

#include "xenia/base/mapped_memory.h"
#include "xenia/vfs/entry.h"

namespace xe {
namespace vfs {

class DiscZarchiveDevice;

class DiscZarchiveEntry : public Entry {
public:
DiscZarchiveEntry(Device* device, Entry* parent, const std::string_view path,
void* opaque);
~DiscZarchiveEntry() override;

static std::unique_ptr<DiscZarchiveEntry> Create(Device* device,
Entry* parent,
const std::string_view name,
void* opaque);

MappedMemory* mmap() const { return nullptr; }
size_t data_offset() const { return data_offset_; }
size_t data_size() const { return data_size_; }

X_STATUS Open(uint32_t desired_access, File** out_file) override;

bool can_map() const override { return false; }
std::unique_ptr<MappedMemory> OpenMapped(MappedMemory::Mode mode,
size_t offset,
size_t length) override;

private:
friend class DiscZarchiveDevice;
friend class DiscZarchiveFile;

void* opaque_;
uint32_t handle_;
size_t data_offset_;
size_t data_size_;
};

} // namespace vfs
} // namespace xe

#endif // XENIA_VFS_DEVICES_DISC_ZARCHIVE_ENTRY_H_
Loading