222 lines
9 KiB
C++
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
|