rexglue-skate3/include/rex/system/xam/content_manager.h

222 lines
9 KiB
C++

/**
******************************************************************************
* 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. *
******************************************************************************
*
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
*/
#pragma once
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include <rex/memory.h>
#include <rex/string/key.h>
#include <rex/string/util.h>
#include <rex/system/xcontent.h>
#include <rex/system/xtypes.h>
#include <rex/thread/mutex.h>
namespace rex {
namespace system {
class KernelState;
} // namespace system
} // namespace rex
namespace rex {
namespace system {
namespace xam {
// If set in XCONTENT_AGGREGATE_DATA, will be substituted with the running
// titles ID
// TODO: check if actual x360 kernel/xam has a value similar to this
constexpr uint32_t kCurrentlyRunningTitleId = 0xFFFFFFFF;
struct XCONTENT_DATA {
be<uint32_t> device_id;
be<XContentType> content_type;
union {
// this should be be<uint16_t>, but that stops copy constructor being
// generated...
uint16_t uint[128];
char16_t chars[128];
} display_name_raw;
char file_name_raw[42];
// Some games use this padding field as a null-terminator, as eg.
// DLC packages usually fill the entire file_name_raw array
// Not every game sets it to 0 though, so make sure any file_name_raw reads
// only go up to 42 chars!
uint8_t padding[2];
bool operator==(const XCONTENT_DATA& other) const {
// Package is located via device_id/content_type/file_name, so only need to
// compare those
return device_id == other.device_id && content_type == other.content_type &&
file_name() == other.file_name();
}
std::u16string display_name() const {
return memory::load_and_swap<std::u16string>(display_name_raw.uint);
}
std::string file_name() const {
std::string value;
value.assign(file_name_raw, std::min(strlen(file_name_raw), countof(file_name_raw)));
return value;
}
void set_display_name(const std::u16string_view value) {
// Some games (e.g. 584108A9) require multiple null-terminators for it to
// read the string properly, blanking the array should take care of that
std::fill_n(display_name_raw.chars, countof(display_name_raw.chars), 0);
rex::string::util_copy_and_swap_truncating(display_name_raw.chars, value,
countof(display_name_raw.chars));
}
void set_file_name(const std::string_view value) {
std::fill_n(file_name_raw, countof(file_name_raw), 0);
rex::string::util_copy_maybe_truncating<rex::string::CopySafety::IKnowWhatIAmDoing>(
file_name_raw, value, rex::countof(file_name_raw));
// Some games rely on padding field acting as a null-terminator...
padding[0] = padding[1] = 0;
}
};
static_assert_size(XCONTENT_DATA, 0x134);
struct XCONTENT_AGGREGATE_DATA : XCONTENT_DATA {
be<uint64_t> xuid; // some titles store XUID here?
be<uint32_t> title_id;
XCONTENT_AGGREGATE_DATA() = default;
XCONTENT_AGGREGATE_DATA(const XCONTENT_DATA& other) {
device_id = other.device_id;
content_type = other.content_type;
set_display_name(other.display_name());
set_file_name(other.file_name());
padding[0] = padding[1] = 0;
xuid = 0;
title_id = kCurrentlyRunningTitleId;
}
bool operator==(const XCONTENT_AGGREGATE_DATA& other) const {
// Package is located via device_id/title_id/content_type/file_name, so only
// need to compare those
return device_id == other.device_id && title_id == other.title_id &&
content_type == other.content_type && file_name() == other.file_name();
}
};
static_assert_size(XCONTENT_AGGREGATE_DATA, 0x148);
class ContentPackage {
public:
ContentPackage(KernelState* kernel_state, const std::string_view root_name,
const XCONTENT_AGGREGATE_DATA& data, const std::filesystem::path& package_path);
~ContentPackage();
void LoadPackageLicenseMask(const std::filesystem::path header_path);
const XCONTENT_AGGREGATE_DATA& GetPackageContentData() const { return content_data_; }
const std::filesystem::path& package_path() const { return package_path_; }
uint32_t GetPackageLicense() const { return license_; }
private:
KernelState* kernel_state_;
std::string root_name_;
std::string device_path_;
std::filesystem::path package_path_;
XCONTENT_AGGREGATE_DATA content_data_;
uint32_t license_ = 0;
};
class ContentManager {
public:
ContentManager(KernelState* kernel_state, const std::filesystem::path& root_path);
~ContentManager();
std::vector<XCONTENT_AGGREGATE_DATA> ListContent(uint32_t device_id, uint64_t xuid,
XContentType content_type,
uint32_t title_id = -1);
std::unique_ptr<ContentPackage> ResolvePackage(const std::string_view root_name, uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data);
bool ContentExists(uint64_t xuid, const XCONTENT_AGGREGATE_DATA& data);
X_RESULT CreateContent(const std::string_view root_name, uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data);
X_RESULT OpenContent(const std::string_view root_name, uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data, uint32_t& content_license);
X_RESULT CloseContent(const std::string_view root_name);
X_RESULT GetContentThumbnail(uint64_t xuid, const XCONTENT_AGGREGATE_DATA& data,
std::vector<uint8_t>* buffer);
X_RESULT SetContentThumbnail(uint64_t xuid, const XCONTENT_AGGREGATE_DATA& data,
std::vector<uint8_t> buffer);
X_RESULT DeleteContent(uint64_t xuid, const XCONTENT_AGGREGATE_DATA& data);
X_RESULT UnmountContent(uint64_t xuid, const XCONTENT_AGGREGATE_DATA& data);
X_RESULT UnmountAndDeleteContent(uint64_t xuid, const XCONTENT_AGGREGATE_DATA& data);
X_RESULT WriteContentHeaderFile(uint64_t xuid, XCONTENT_AGGREGATE_DATA data,
uint32_t license_mask = 0);
X_RESULT ReadContentHeaderFile(const std::string_view file_name, uint64_t xuid, uint32_t title_id,
XContentType content_type, XCONTENT_AGGREGATE_DATA& data) const;
std::filesystem::path ResolveGameUserContentPath();
bool IsContentOpen(const XCONTENT_AGGREGATE_DATA& data) const;
void CloseOpenedFilesFromContent(const std::string_view root_name);
// Returns the host filesystem path for an open content package, or empty.
std::filesystem::path GetOpenPackagePath(const std::string_view root_name) const;
// Installs an STFS content package from an arbitrary host path.
// Extracts the package into root_path_/0000000000000000/{title_id}/00000002/{filename}/
// and writes a .header file for XAM enumeration.
X_RESULT InstallContent(const std::filesystem::path& package_path);
// Redirects all content of `content_type` to `root` with a flattened
// single-title layout instead of the shared content tree:
// root/{xuid}/{package}/ data
// root/{xuid}/Headers/{package}.header enumeration header
// The xuid level is kept so per-profile content stays isolated. Set before
// the title enumerates or opens content of this type; no data is migrated.
void SetContentTypeRoot(XContentType content_type, std::filesystem::path root);
private:
std::filesystem::path ResolvePackageRoot(uint64_t xuid, XContentType content_type,
uint32_t title_id = -1);
std::filesystem::path ResolvePackagePath(uint64_t xuid, const XCONTENT_AGGREGATE_DATA& data);
std::filesystem::path ResolvePackageHeaderPath(const std::string_view file_name, uint64_t xuid,
uint32_t title_id,
XContentType content_type) const;
std::unordered_map<string::string_key_case, ContentPackage*,
string::string_key_case::Hash>::iterator
FindOpenPackageByData(const XCONTENT_AGGREGATE_DATA& data);
ContentPackage* DetachPackage(std::unordered_map<string::string_key_case, ContentPackage*,
string::string_key_case::Hash>::iterator it);
KernelState* kernel_state_;
std::filesystem::path root_path_;
// Per-content-type root overrides (SetContentTypeRoot), keyed by the raw
// XContentType value.
std::unordered_map<uint32_t, std::filesystem::path> content_type_roots_;
// TODO(benvanik): remove use of global lock, it's bad here!
rex::thread::global_critical_region global_critical_region_;
std::unordered_map<string::string_key_case, ContentPackage*, string::string_key_case::Hash>
open_packages_;
};
} // namespace xam
} // namespace system
} // namespace rex