297 lines
11 KiB
C++
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_
|