mirror of
https://github.com/izzy2lost/xenia-edge.git
synced 2026-07-06 00:20:26 -07:00
[Emulator] Zar creation fixes
Make sure to respect user selected zar name and properly extract STFS
This commit is contained in:
@@ -1521,11 +1521,53 @@ void EmulatorWindow::CreateZarchive() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Scan for STFS content to get game name/icon before showing save dialog.
|
||||
struct SourceInfo {
|
||||
std::filesystem::path stfs_path;
|
||||
std::string title_name;
|
||||
std::vector<uint8_t> icon_data;
|
||||
};
|
||||
std::vector<SourceInfo> source_infos(content_dirs.size());
|
||||
|
||||
for (size_t i = 0; i < content_dirs.size(); i++) {
|
||||
auto abs_dir = std::filesystem::absolute(content_dirs[i]);
|
||||
std::error_code ec;
|
||||
// Top-level only to avoid false-positives on embedded STFS (DLC, etc.)
|
||||
for (auto const& dirEntry :
|
||||
std::filesystem::directory_iterator(abs_dir, ec)) {
|
||||
if (!source_infos[i].icon_data.empty()) break;
|
||||
if (dirEntry.is_regular_file()) {
|
||||
const auto header =
|
||||
vfs::XContentContainerDevice::ReadContainerHeader(dirEntry.path());
|
||||
if (header && header->content_header.is_magic_valid()) {
|
||||
source_infos[i].stfs_path = dirEntry.path();
|
||||
|
||||
source_infos[i].title_name = xe::to_utf8(
|
||||
header->content_metadata.display_name(XLanguage::kEnglish));
|
||||
|
||||
if (header->content_metadata.title_thumbnail_size > 0 &&
|
||||
header->content_metadata.title_thumbnail_size <=
|
||||
vfs::XContentMetadata::kThumbLengthV1) {
|
||||
source_infos[i].icon_data.assign(
|
||||
header->content_metadata.title_thumbnail,
|
||||
header->content_metadata.title_thumbnail +
|
||||
header->content_metadata.title_thumbnail_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string default_name = content_dirs.front().stem().string();
|
||||
if (content_dirs.size() == 1 && !source_infos[0].title_name.empty()) {
|
||||
default_name = source_infos[0].title_name;
|
||||
}
|
||||
|
||||
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_file_name(default_name);
|
||||
file_picker->set_default_extension("zar");
|
||||
file_picker->set_title("Zarchive File");
|
||||
file_picker->set_extensions({
|
||||
@@ -1546,13 +1588,16 @@ void EmulatorWindow::CreateZarchive() {
|
||||
auto zarchive_entries =
|
||||
std::make_shared<std::vector<Emulator::ZarchiveEntry>>();
|
||||
|
||||
for (auto& content_path : content_dirs) {
|
||||
auto abs_content_dir = std::filesystem::absolute(content_path);
|
||||
for (size_t i = 0; i < content_dirs.size(); i++) {
|
||||
auto abs_content_dir = std::filesystem::absolute(content_dirs[i]);
|
||||
std::filesystem::path abs_zarchive_file;
|
||||
|
||||
if (content_dirs.size() > 1) {
|
||||
std::string stem = !source_infos[i].title_name.empty()
|
||||
? source_infos[i].title_name
|
||||
: abs_content_dir.stem().string();
|
||||
abs_zarchive_file = std::filesystem::absolute(
|
||||
(zarchive_dir / abs_content_dir.stem()).replace_extension("zar"));
|
||||
(zarchive_dir / stem).replace_extension("zar"));
|
||||
} else {
|
||||
abs_zarchive_file = std::filesystem::absolute(zarchive_dir);
|
||||
}
|
||||
@@ -1561,36 +1606,8 @@ void EmulatorWindow::CreateZarchive() {
|
||||
Emulator::ZarchiveOperation::Create);
|
||||
auto& entry = zarchive_entries->back();
|
||||
entry.name_ = xe::path_to_utf8(abs_zarchive_file.filename());
|
||||
|
||||
// Extract icon/title from STFS content, use title for output filename
|
||||
std::error_code ec;
|
||||
for (auto const& dirEntry :
|
||||
std::filesystem::recursive_directory_iterator(abs_content_dir, ec)) {
|
||||
if (!entry.icon_data_.empty()) break;
|
||||
if (dirEntry.is_regular_file()) {
|
||||
const auto header =
|
||||
vfs::XContentContainerDevice::ReadContainerHeader(dirEntry.path());
|
||||
if (header && header->content_header.is_magic_valid()) {
|
||||
auto title_name = xe::to_utf8(
|
||||
header->content_metadata.display_name(XLanguage::kEnglish));
|
||||
if (!title_name.empty()) {
|
||||
auto new_zarchive_file =
|
||||
abs_zarchive_file.parent_path() / (title_name + ".zar");
|
||||
entry.data_installation_path_ = new_zarchive_file;
|
||||
entry.name_ = title_name + ".zar";
|
||||
}
|
||||
|
||||
if (header->content_metadata.title_thumbnail_size > 0 &&
|
||||
header->content_metadata.title_thumbnail_size <=
|
||||
vfs::XContentMetadata::kThumbLengthV1) {
|
||||
entry.icon_data_.assign(
|
||||
header->content_metadata.title_thumbnail,
|
||||
header->content_metadata.title_thumbnail +
|
||||
header->content_metadata.title_thumbnail_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
entry.stfs_path_ = source_infos[i].stfs_path;
|
||||
entry.icon_data_ = std::move(source_infos[i].icon_data);
|
||||
}
|
||||
|
||||
// Show dialog first, then start creation
|
||||
|
||||
+178
-55
@@ -693,7 +693,9 @@ X_STATUS Emulator::LaunchDiscImage(const std::filesystem::path& path) {
|
||||
|
||||
X_STATUS Emulator::LaunchDiscArchive(const std::filesystem::path& path) {
|
||||
std::string module_path = FindLaunchModule();
|
||||
XELOGI("LaunchDiscArchive: FindLaunchModule returned '{}'", module_path);
|
||||
X_STATUS result = CompleteLaunch(path, module_path);
|
||||
XELOGI("LaunchDiscArchive: CompleteLaunch returned {:08X}", result);
|
||||
|
||||
if (result == X_STATUS_NOT_FOUND && !cvars::launch_module.empty()) {
|
||||
return LaunchDefaultModule(path);
|
||||
@@ -1164,12 +1166,46 @@ X_STATUS Emulator::CreateZarchivePackage(ZarchiveEntry& entry) {
|
||||
return X_ERROR_CANCELLED;
|
||||
}
|
||||
|
||||
// Mount STFS content via VFS to pack the real game files.
|
||||
std::unique_ptr<vfs::Device> stfs_device;
|
||||
if (!entry.stfs_path_.empty()) {
|
||||
stfs_device =
|
||||
vfs::XContentContainerDevice::CreateContentDevice("", entry.stfs_path_);
|
||||
if (!stfs_device || !stfs_device->Initialize()) {
|
||||
XELOGE("CreateZarchivePackage: Failed to mount STFS content at '{}'",
|
||||
xe::path_to_utf8(entry.stfs_path_));
|
||||
entry.installation_result_ = X_STATUS_UNSUCCESSFUL;
|
||||
entry.installation_error_message_ = "Failed to mount STFS content";
|
||||
entry.installation_state_ = InstallState::failed;
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
XELOGI("CreateZarchivePackage: Mounted STFS content from '{}'",
|
||||
xe::path_to_utf8(entry.stfs_path_));
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
entry.content_size_ = 0;
|
||||
for (auto const& dirEntry :
|
||||
std::filesystem::recursive_directory_iterator(inputDirectory, ec)) {
|
||||
if (dirEntry.is_regular_file() && dirEntry.path() != outputFile) {
|
||||
entry.content_size_ += std::filesystem::file_size(dirEntry.path(), ec);
|
||||
|
||||
if (stfs_device) {
|
||||
auto* root = stfs_device->ResolvePath("/");
|
||||
if (root) {
|
||||
std::function<void(vfs::Entry*)> calc_size = [&](vfs::Entry* e) {
|
||||
if (e->attributes() & vfs::kFileAttributeDirectory) {
|
||||
for (auto& child : e->children()) {
|
||||
calc_size(child.get());
|
||||
}
|
||||
} else {
|
||||
entry.content_size_ += e->size();
|
||||
}
|
||||
};
|
||||
calc_size(root);
|
||||
}
|
||||
} else {
|
||||
for (auto const& dirEntry :
|
||||
std::filesystem::recursive_directory_iterator(inputDirectory, ec)) {
|
||||
if (dirEntry.is_regular_file() && dirEntry.path() != outputFile) {
|
||||
entry.content_size_ += std::filesystem::file_size(dirEntry.path(), ec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1216,72 +1252,159 @@ X_STATUS Emulator::CreateZarchivePackage(ZarchiveEntry& entry) {
|
||||
return status;
|
||||
};
|
||||
|
||||
for (auto const& dirEntry :
|
||||
std::filesystem::recursive_directory_iterator(inputDirectory)) {
|
||||
if (entry.cancelled_.load()) {
|
||||
return cleanup_and_fail("Cancelled", X_ERROR_CANCELLED);
|
||||
}
|
||||
|
||||
std::filesystem::path pathEntry =
|
||||
std::filesystem::relative(dirEntry.path(), inputDirectory, ec);
|
||||
|
||||
if (ec) {
|
||||
XELOGI("Failed to get relative path {}\n", pathEntry.string());
|
||||
return cleanup_and_fail("Failed to get relative path",
|
||||
if (stfs_device) {
|
||||
// Pack from mounted STFS VFS device
|
||||
auto* root = stfs_device->ResolvePath("/");
|
||||
if (!root) {
|
||||
return cleanup_and_fail("Failed to resolve STFS root",
|
||||
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 cleanup_and_fail("Failed to create directory in archive",
|
||||
X_STATUS_UNSUCCESSFUL);
|
||||
}
|
||||
} else if (dirEntry.is_regular_file()) {
|
||||
// Don't pack itself to prevent infinite packing.
|
||||
if (dirEntry == outputFile) {
|
||||
continue;
|
||||
std::function<X_STATUS(vfs::Entry*)> pack_entry =
|
||||
[&](vfs::Entry* e) -> X_STATUS {
|
||||
if (entry.cancelled_.load()) {
|
||||
return X_ERROR_CANCELLED;
|
||||
}
|
||||
|
||||
XELOGI("Adding file: {}\n", pathEntry.string());
|
||||
|
||||
if (!zWriter.StartNewFile(pathEntry.generic_string().c_str())) {
|
||||
XELOGI("Failed to create archive file {}\n", pathEntry.string());
|
||||
return cleanup_and_fail("Failed to create file in archive",
|
||||
X_STATUS_UNSUCCESSFUL);
|
||||
// Use forward slashes for zarchive paths, skip leading separator
|
||||
std::string entry_path = utf8::fix_path_separators(e->path(), '/');
|
||||
if (!entry_path.empty() && entry_path[0] == '/') {
|
||||
entry_path = entry_path.substr(1);
|
||||
}
|
||||
|
||||
std::filesystem::path file_to_pack_path = inputDirectory / pathEntry;
|
||||
FILE* file = xe::filesystem::OpenFile(file_to_pack_path, "rb");
|
||||
if (e->attributes() & vfs::kFileAttributeDirectory) {
|
||||
if (!entry_path.empty()) {
|
||||
if (!zWriter.MakeDir(entry_path.c_str(), false)) {
|
||||
XELOGI("Failed to create directory {}", entry_path);
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
}
|
||||
for (auto& child : e->children()) {
|
||||
X_STATUS result = pack_entry(child.get());
|
||||
if (result != X_STATUS_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
XELOGI("Adding file: {}", entry_path);
|
||||
|
||||
if (!file) {
|
||||
XELOGI("Failed to open input file {}\n", pathEntry.string());
|
||||
return cleanup_and_fail("Failed to open input file",
|
||||
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) {
|
||||
if (entry.cancelled_.load()) {
|
||||
fclose(file);
|
||||
return cleanup_and_fail("Cancelled", X_ERROR_CANCELLED);
|
||||
if (!zWriter.StartNewFile(entry_path.c_str())) {
|
||||
XELOGI("Failed to create archive file {}", entry_path);
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
uint64_t bytes_read = fread(buffer.data(), 1, buffer.size(), file);
|
||||
vfs::File* vfs_file = nullptr;
|
||||
X_STATUS result = e->Open(vfs::FileAccess::kFileReadData, &vfs_file);
|
||||
if (result != X_STATUS_SUCCESS || !vfs_file) {
|
||||
XELOGI("Failed to open VFS file {}", entry_path);
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
total_bytes_read += bytes_read;
|
||||
entry.currently_installed_size_ += bytes_read;
|
||||
size_t remaining = e->size();
|
||||
size_t offset = 0;
|
||||
while (remaining > 0) {
|
||||
if (entry.cancelled_.load()) {
|
||||
vfs_file->Destroy();
|
||||
return X_ERROR_CANCELLED;
|
||||
}
|
||||
|
||||
zWriter.AppendData(buffer.data(), bytes_read);
|
||||
size_t bytes_read = 0;
|
||||
vfs_file->ReadSync(std::span<uint8_t>(buffer.data(), buffer.size()),
|
||||
offset, &bytes_read);
|
||||
if (bytes_read == 0) break;
|
||||
|
||||
zWriter.AppendData(buffer.data(), bytes_read);
|
||||
offset += bytes_read;
|
||||
remaining -= bytes_read;
|
||||
entry.currently_installed_size_ += bytes_read;
|
||||
}
|
||||
vfs_file->Destroy();
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
}
|
||||
if (packContext.hasError) {
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
return X_STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
if (packContext.hasError) {
|
||||
return cleanup_and_fail("Write error", X_STATUS_UNSUCCESSFUL);
|
||||
X_STATUS result = pack_entry(root);
|
||||
if (result == X_ERROR_CANCELLED) {
|
||||
return cleanup_and_fail("Cancelled", X_ERROR_CANCELLED);
|
||||
}
|
||||
if (result != X_STATUS_SUCCESS) {
|
||||
return cleanup_and_fail("Failed to pack STFS content",
|
||||
X_STATUS_UNSUCCESSFUL);
|
||||
}
|
||||
} else {
|
||||
// Pack from raw filesystem directory
|
||||
for (auto const& dirEntry :
|
||||
std::filesystem::recursive_directory_iterator(inputDirectory)) {
|
||||
if (entry.cancelled_.load()) {
|
||||
return cleanup_and_fail("Cancelled", X_ERROR_CANCELLED);
|
||||
}
|
||||
|
||||
std::filesystem::path pathEntry =
|
||||
std::filesystem::relative(dirEntry.path(), inputDirectory, ec);
|
||||
|
||||
if (ec) {
|
||||
XELOGI("Failed to get relative path {}\n", pathEntry.string());
|
||||
return cleanup_and_fail("Failed to get relative path",
|
||||
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 cleanup_and_fail("Failed to create directory in archive",
|
||||
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 cleanup_and_fail("Failed to create file in archive",
|
||||
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 cleanup_and_fail("Failed to open input file",
|
||||
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) {
|
||||
if (entry.cancelled_.load()) {
|
||||
fclose(file);
|
||||
return cleanup_and_fail("Cancelled", X_ERROR_CANCELLED);
|
||||
}
|
||||
|
||||
uint64_t bytes_read = fread(buffer.data(), 1, buffer.size(), file);
|
||||
|
||||
total_bytes_read += bytes_read;
|
||||
entry.currently_installed_size_ += bytes_read;
|
||||
|
||||
zWriter.AppendData(buffer.data(), bytes_read);
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
if (packContext.hasError) {
|
||||
return cleanup_and_fail("Write error", X_STATUS_UNSUCCESSFUL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -313,6 +313,7 @@ class Emulator {
|
||||
: name_(std::move(other.name_)),
|
||||
path_(std::move(other.path_)),
|
||||
data_installation_path_(std::move(other.data_installation_path_)),
|
||||
stfs_path_(std::move(other.stfs_path_)),
|
||||
operation_(other.operation_),
|
||||
content_size_(other.content_size_),
|
||||
currently_installed_size_(other.currently_installed_size_),
|
||||
@@ -328,6 +329,7 @@ class Emulator {
|
||||
name_ = std::move(other.name_);
|
||||
path_ = std::move(other.path_);
|
||||
data_installation_path_ = std::move(other.data_installation_path_);
|
||||
stfs_path_ = std::move(other.stfs_path_);
|
||||
operation_ = other.operation_;
|
||||
content_size_ = other.content_size_;
|
||||
currently_installed_size_ = other.currently_installed_size_;
|
||||
@@ -347,6 +349,7 @@ class Emulator {
|
||||
std::string name_{};
|
||||
std::filesystem::path path_;
|
||||
std::filesystem::path data_installation_path_;
|
||||
std::filesystem::path stfs_path_; // Set when source contains STFS content
|
||||
ZarchiveOperation operation_;
|
||||
|
||||
uint64_t content_size_ = 0;
|
||||
|
||||
@@ -36,6 +36,8 @@ bool DiscZarchiveDevice::Initialize() {
|
||||
|
||||
constexpr std::string_view root_path = "/";
|
||||
const ZArchiveNodeHandle handle = reader_->LookUp(root_path);
|
||||
XELOGI("DiscZarchiveDevice::Initialize: root LookUp('{}') -> handle={}",
|
||||
root_path, static_cast<uint32_t>(handle));
|
||||
auto root_entry = new DiscZarchiveEntry(this, nullptr, root_path);
|
||||
root_entry->attributes_ = kFileAttributeDirectory;
|
||||
root_entry->handle_ = static_cast<uint32_t>(handle);
|
||||
@@ -43,7 +45,12 @@ bool DiscZarchiveDevice::Initialize() {
|
||||
root_entry->absolute_path_ = root_path;
|
||||
root_entry_ = std::unique_ptr<Entry>(root_entry);
|
||||
|
||||
return ReadAllEntries("", root_entry, nullptr);
|
||||
bool result = ReadAllEntries("", root_entry, nullptr);
|
||||
XELOGI(
|
||||
"DiscZarchiveDevice::Initialize: ReadAllEntries returned {}, "
|
||||
"root has {} children",
|
||||
result, root_entry->children().size());
|
||||
return result;
|
||||
}
|
||||
|
||||
void DiscZarchiveDevice::Dump(StringBuffer* string_buffer) {
|
||||
@@ -56,17 +63,25 @@ Entry* DiscZarchiveDevice::ResolvePath(const std::string_view path) {
|
||||
// be in the form:
|
||||
// some\PATH.foo
|
||||
XELOGFS("DiscZarchiveDevice::ResolvePath({})", path);
|
||||
XELOGI("DiscZarchiveDevice::ResolvePath: path='{}'", path);
|
||||
|
||||
if (!reader_) {
|
||||
XELOGI("DiscZarchiveDevice::ResolvePath: reader_ is null");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ZArchiveNodeHandle handle = reader_->LookUp(path);
|
||||
XELOGI(
|
||||
"DiscZarchiveDevice::ResolvePath: LookUp('{}') -> handle={} (invalid={})",
|
||||
path, static_cast<uint32_t>(handle), handle == ZARCHIVE_INVALID_NODE);
|
||||
if (handle == ZARCHIVE_INVALID_NODE) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return root_entry_->ResolvePath(path);
|
||||
Entry* result = root_entry_->ResolvePath(path);
|
||||
XELOGI("DiscZarchiveDevice::ResolvePath: entry tree walk -> {}",
|
||||
result ? result->absolute_path() : "(null)");
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DiscZarchiveDevice::ReadAllEntries(const std::string& path,
|
||||
@@ -99,7 +114,15 @@ bool DiscZarchiveDevice::ReadAllEntries(const std::string& path,
|
||||
|
||||
const std::string full_path = path + std::string(dirEntry.name);
|
||||
const ZArchiveNodeHandle fileHandle = reader_->LookUp(full_path);
|
||||
XELOGI(
|
||||
"DiscZarchiveDevice::ReadAllEntries: entry='{}' type={} "
|
||||
"handle={} size={}",
|
||||
full_path,
|
||||
dirEntry.isDirectory ? "dir" : (dirEntry.isFile ? "file" : "unknown"),
|
||||
static_cast<uint32_t>(fileHandle), dirEntry.size);
|
||||
if (fileHandle == ZARCHIVE_INVALID_NODE) {
|
||||
XELOGE("DiscZarchiveDevice::ReadAllEntries: LookUp failed for '{}'",
|
||||
full_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user