Files
Xenia-Canary/src/xenia/kernel/xam/content_manager.h
Adrian 07501cfcb9 [XAM] Fixed XEnumerateCrossTitle corrupt items_returned_ptr
Fixed Halo: Reach from crashing when accessing The Armory
2025-11-22 22:29:05 +01:00

297 lines
11 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. *
******************************************************************************
*/
#ifndef XENIA_KERNEL_XAM_CONTENT_MANAGER_H_
#define XENIA_KERNEL_XAM_CONTENT_MANAGER_H_
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "xenia/base/memory.h"
#include "xenia/base/mutex.h"
#include "xenia/base/string_key.h"
#include "xenia/base/string_util.h"
#include "xenia/vfs/entry.h"
#include "xenia/xbox.h"
namespace xe {
namespace kernel {
class KernelState;
} // namespace kernel
} // namespace xe
namespace xe {
namespace kernel {
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; // 0x0 sz:0x4
be<XContentType> content_type; // 0x4 sz:0x4
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; // 0x8 sz:0x100
char file_name_raw[42]; // 0x108 sz:0x2A
// 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]; // 0x132 sz: 0x2
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 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);
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);
string_util::copy_maybe_truncating<string_util::Safety::IKnowWhatIAmDoing>(
file_name_raw, value, xe::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);
struct XCONTENT_CROSS_TITLE_DATA {
XCONTENT_DATA content_data;
xe::be<uint32_t> title_id;
};
static_assert_size(XCONTENT_CROSS_TITLE_DATA, 0x138);
struct XCONTENT_DATA_MEDIA {
be<uint8_t> series_id[0x10]; // 0x0 sz:0x10
be<uint8_t> seasonid[0x10]; // 0x10 sz:0x10
be<uint16_t> season_number; // 0x20 sz:0x2
be<uint16_t> episode_number; // 0x22 sz:0x2
};
static_assert_size(XCONTENT_DATA_MEDIA, 0x24);
struct XCONTENT_DATA_AVATAR_ASSET {
be<uint32_t> subcategory; // 0x0 sz:0x4
be<int32_t> colorizable; // 0x4 sz:0x4
be<uint8_t> asset_id[0x10]; // 0x8 sz:0x10
be<uint8_t> skeleton_version_mask; // 0x18 sz:0x1
};
static_assert_size(XCONTENT_DATA_AVATAR_ASSET, 0x1C);
enum XCONTENT_INTERNAL_FLAGS : uint32_t {
Partial = 0x00000001,
Corrupt = 0x00000002,
KinectEnabled = 0x00000004,
DeepLinkSupported = 0x00000008,
DisableNetworkStorage = 0x00000010,
MoveOnlyTransfer = 0x00000020,
DeviceTransfer = 0x00000040,
ProfileTransfer = 0x00000080,
};
struct XCONTENT_DATA_INTERNAL : XCONTENT_DATA { // 0x0 sz:0x134
be<uint32_t> category; // 0x134 sz:0x4
be<uint64_t> xuid; // 0x138 sz:0x8
be<uint32_t> title_id; // 0x140 sz:0x4
be<uint32_t> license_mask; // 0x144 sz:0x4
// added in V4532 0x148
be<uint64_t> content_size; // 0x148 sz:0x8
// added in NXE
be<uint64_t> creation_time; // 0x150 sz:0x8 FILETIME
char16_t title_name[0x40]; // 0x158 sz:0x80
// compiler does not like
// union {
// XCONTENT_DATA_AVATAR_ASSET avatar_content_data; // 0x1D8 sz:0x1C
// XCONTENT_DATA_MEDIA media_content_data; // 0x1D8 sz:0x24
//};
be<uint8_t> padding[0x24]; // 0x1D8 sz:0x1C
be<XCONTENT_INTERNAL_FLAGS> xcontent_flag; // 0x1FC sz:0x04
XCONTENT_DATA_INTERNAL() = default;
XCONTENT_DATA_INTERNAL(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_DATA_INTERNAL& 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_DATA_INTERNAL, 0x200);
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 uint32_t GetPackageLicense() const { return license_; }
private:
KernelState* kernel_state_;
std::string root_name_;
std::string device_path_;
XCONTENT_AGGREGATE_DATA content_data_;
uint32_t license_;
};
class ContentManager {
public:
ContentManager(KernelState* kernel_state,
const std::filesystem::path& root_path);
~ContentManager();
std::vector<XCONTENT_AGGREGATE_DATA> ListContent(
const uint32_t device_id, const uint64_t xuid, const uint32_t title_id,
const XContentType content_type) const;
std::vector<XCONTENT_AGGREGATE_DATA> ListContentODD(
const uint32_t device_id, const uint64_t xuid, const uint32_t title_id,
const XContentType content_type) const;
std::unique_ptr<ContentPackage> ResolvePackage(
const std::string_view root_name, const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data, const uint32_t disc_number = -1);
bool ContentExists(const uint64_t xuid, const XCONTENT_AGGREGATE_DATA& data);
X_RESULT WriteContentHeaderFile(const uint64_t xuid,
XCONTENT_AGGREGATE_DATA data);
X_RESULT ReadContentHeaderFile(const std::string_view file_name,
const uint64_t xuid, const uint32_t title_id,
XContentType content_type,
XCONTENT_AGGREGATE_DATA& data) const;
X_RESULT CreateContent(const std::string_view root_name, const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data);
X_RESULT OpenContent(const std::string_view root_name, const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data,
uint32_t& content_license,
const uint32_t disc_number = -1);
X_RESULT CloseContent(const std::string_view root_name);
X_RESULT GetContentThumbnail(const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data,
std::vector<uint8_t>* buffer);
X_RESULT SetContentThumbnail(const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data,
std::vector<uint8_t> buffer);
X_RESULT DeleteContent(const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data);
std::filesystem::path ResolveGameUserContentPath(const uint64_t xuid);
bool IsContentOpen(const XCONTENT_AGGREGATE_DATA& data) const;
void CloseOpenedFilesFromContent(const std::string_view root_name);
uint64_t GetContentTotalSpace() const;
uint64_t GetContentFreeSpace() const;
private:
std::filesystem::path ResolvePackageRoot(
const uint64_t xuid, const uint32_t title_id,
const XContentType content_type) const;
std::filesystem::path ResolvePackagePath(const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data,
const uint32_t disc_number = -1);
std::filesystem::path ResolvePackageHeaderPath(
const std::string_view file_name, uint64_t xuid, uint32_t title_id,
const XContentType content_type) const;
std::unordered_set<uint32_t> FindPublisherTitleIds(
const uint64_t xuid,
uint32_t base_title_id = kCurrentlyRunningTitleId) const;
bool UpdateSpaData(vfs::Entry* spa_file_update);
KernelState* kernel_state_;
std::filesystem::path root_path_;
// TODO(benvanik): remove use of global lock, it's bad here!
xe::global_critical_region global_critical_region_;
std::unordered_map<string_key_insensitive, ContentPackage*> open_packages_;
};
} // namespace xam
} // namespace kernel
} // namespace xe
#endif // XENIA_KERNEL_XAM_CONTENT_MANAGER_H_