diff --git a/src/xenia/kernel/kernel_state.h b/src/xenia/kernel/kernel_state.h index b4097061b7e..121b8022dad 100644 --- a/src/xenia/kernel/kernel_state.h +++ b/src/xenia/kernel/kernel_state.h @@ -29,7 +29,7 @@ #include "xenia/kernel/util/object_table.h" #include "xenia/kernel/util/xdbf_utils.h" #include "xenia/kernel/xam/app_manager.h" -#include "xenia/kernel/xam/content_manager.h" +#include "xenia/kernel/xam/content/content_manager.h" #include "xenia/kernel/xam/user_profile.h" #include "xenia/kernel/xevent.h" #include "xenia/memory.h" diff --git a/src/xenia/kernel/xam/content_manager.cc b/src/xenia/kernel/xam/content/content_manager.cc similarity index 60% rename from src/xenia/kernel/xam/content_manager.cc rename to src/xenia/kernel/xam/content/content_manager.cc index aec68653d2b..292013bf469 100644 --- a/src/xenia/kernel/xam/content_manager.cc +++ b/src/xenia/kernel/xam/content/content_manager.cc @@ -7,11 +7,12 @@ ****************************************************************************** */ -#include "xenia/kernel/xam/content_manager.h" - +#include "xenia/kernel/xam/content/content_manager.h" #include #include #include +#include "xenia/kernel/xam/content/host_content_package.h" +#include "xenia/kernel/xam/content/xcontent_package.h" #include "third_party/fmt/include/fmt/format.h" #include "xenia/base/filesystem.h" @@ -22,38 +23,15 @@ #include "xenia/kernel/xobject.h" #include "xenia/vfs/devices/host_path_device.h" +extern "C" { +#include "third_party/FFmpeg/libavutil/md5.h" +} + namespace xe { namespace kernel { namespace xam { -static const char* kThumbnailFileName = "__thumbnail.png"; - static const char* kGameUserContentDirName = "profile"; -static const char* kGameContentHeaderDirName = "Headers"; - -static int content_device_id_ = 0; - -ContentPackage::ContentPackage(KernelState* kernel_state, - const std::string_view root_name, - const XCONTENT_AGGREGATE_DATA& data, - const std::filesystem::path& package_path) - : kernel_state_(kernel_state), root_name_(root_name) { - device_path_ = fmt::format("\\Device\\Content\\{0}\\", ++content_device_id_); - content_data_ = data; - - auto fs = kernel_state_->file_system(); - auto device = - std::make_unique(device_path_, package_path, false); - device->Initialize(); - fs->RegisterDevice(std::move(device)); - fs->RegisterSymbolicLink(root_name_ + ":", device_path_); -} - -ContentPackage::~ContentPackage() { - auto fs = kernel_state_->file_system(); - fs->UnregisterSymbolicLink(root_name_ + ":"); - fs->UnregisterDevice(device_path_); -} ContentManager::ContentManager(KernelState* kernel_state, const std::filesystem::path& root_path) @@ -106,23 +84,6 @@ std::filesystem::path ContentManager::ResolvePackagePath( return ""; } -std::filesystem::path ContentManager::ResolvePackageHeaderPath( - const std::string_view file_name, XContentType content_type, - uint32_t title_id) { - if (title_id == kCurrentlyRunningTitleId) { - title_id = kernel_state_->title_id(); - } - auto title_id_str = fmt::format("{:08X}", title_id); - auto content_type_str = fmt::format("{:08X}", uint32_t(content_type)); - std::string final_name = - xe::string_util::trim(std::string(file_name)) + ".header"; - - // Header root path: - // content_root/title_id/Headers/content_type/ - return root_path_ / title_id_str / kGameContentHeaderDirName / - content_type_str / final_name; -} - std::set ContentManager::FindPublisherTitleIds( uint32_t base_title_id) const { if (base_title_id == kCurrentlyRunningTitleId) { @@ -153,7 +114,8 @@ std::set ContentManager::FindPublisherTitleIds( } std::vector ContentManager::ListContent( - uint32_t device_id, XContentType content_type, uint32_t title_id) { + const uint32_t device_id, const XContentType content_type, + uint32_t title_id) { std::vector result; if (title_id == kCurrentlyRunningTitleId) { @@ -173,32 +135,54 @@ std::vector ContentManager::ListContent( auto file_infos = xe::filesystem::ListFiles(package_root); for (const auto& file_info : file_infos) { - if (file_info.type != xe::filesystem::FileInfo::Type::kDirectory) { - // Directories only. - continue; - } - - XCONTENT_AGGREGATE_DATA content_data; - if (XSUCCEEDED(ReadContentHeaderFile(xe::path_to_utf8(file_info.name), - content_type, content_data, - title_id))) { - result.emplace_back(std::move(content_data)); - } else { - content_data.device_id = device_id; - content_data.content_type = content_type; - content_data.set_display_name(xe::path_to_utf16(file_info.name)); - content_data.set_file_name(xe::path_to_utf8(file_info.name)); - content_data.title_id = title_id; - result.emplace_back(std::move(content_data)); + const auto type = ResolvePackageType(file_info.path / file_info.name); + + switch (type) { + case ContentPackage::PackageType::host: { + HostContentPackage package = + HostContentPackage(kernel_state_, file_info.path / file_info.name, + device_id, title_id, content_type); + + result.emplace_back(*package.GetPackageContentData()); + }; break; + case ContentPackage::PackageType::xcontent: { + XContentPackage package = + XContentPackage(kernel_state_, file_info.path / file_info.name); + + result.emplace_back(*package.GetPackageContentData()); + }; break; + default: + break; } } } return result; } +ContentPackage::PackageType ContentManager::ResolvePackageType( + const std::filesystem::path path) const { + xe::filesystem::FileInfo info; + + const bool success = xe::filesystem::GetInfo(path, &info); + if (!success) { + return ContentPackage::PackageType::unknown; + } + + switch (info.type) { + case xe::filesystem::FileInfo::Type::kFile: + return ContentPackage::PackageType::xcontent; + break; + case xe::filesystem::FileInfo::Type::kDirectory: + return ContentPackage::PackageType::host; + break; + default: + break; + } + return ContentPackage::PackageType::unknown; +} + std::unique_ptr ContentManager::ResolvePackage( - const std::string_view root_name, const XCONTENT_AGGREGATE_DATA& data, - const uint32_t disc_number) { + const XCONTENT_AGGREGATE_DATA& data, const uint32_t disc_number) { auto package_path = ResolvePackagePath(data, disc_number); if (!std::filesystem::exists(package_path)) { return nullptr; @@ -206,9 +190,19 @@ std::unique_ptr ContentManager::ResolvePackage( auto global_lock = global_critical_region_.Acquire(); - auto package = std::make_unique(kernel_state_, root_name, - data, package_path); - return package; + switch (ResolvePackageType(package_path)) { + case ContentPackage::PackageType::host: + return std::make_unique(kernel_state_, package_path, + &data); + break; + case ContentPackage::PackageType::xcontent: + return std::make_unique(kernel_state_, package_path, + &data); + break; + default: + break; + } + return nullptr; } bool ContentManager::ContentExists(const XCONTENT_AGGREGATE_DATA& data) { @@ -216,67 +210,27 @@ bool ContentManager::ContentExists(const XCONTENT_AGGREGATE_DATA& data) { return std::filesystem::exists(path); } -X_RESULT ContentManager::WriteContentHeaderFile( - const XCONTENT_AGGREGATE_DATA* data) { - auto header_path = ResolvePackageHeaderPath( - data->file_name(), data->content_type, data->title_id); - auto parent_path = header_path.parent_path(); +std::string ContentManager::GenerateUniquePackageId( + const std::filesystem::path path) { + AVMD5* md5 = av_md5_alloc(); + av_md5_init(md5); - if (!std::filesystem::exists(parent_path)) { - if (!std::filesystem::create_directories(parent_path)) { - return X_STATUS_ACCESS_DENIED; - } - } + std::string path_as_string = xe::path_to_utf8(path); + av_md5_update(md5, (const uint8_t*)(path_as_string.c_str()), + (int)path_as_string.length()); + uint8_t digest[16]; + av_md5_final(md5, digest); - xe::filesystem::CreateEmptyFile(header_path); - - if (std::filesystem::exists(header_path)) { - auto file = xe::filesystem::OpenFile(header_path, "wb"); - fwrite(data, 1, sizeof(XCONTENT_AGGREGATE_DATA), file); - fclose(file); - return X_STATUS_SUCCESS; + std::string unique_id = ""; + for (uint8_t i = 0; i < 16; i++) { + unique_id += fmt::format("{:02X}", digest[i]); } - return X_STATUS_NO_SUCH_FILE; -} - -X_RESULT ContentManager::ReadContentHeaderFile(const std::string_view file_name, - XContentType content_type, - XCONTENT_AGGREGATE_DATA& data, - const uint32_t title_id) { - auto header_file_path = - ResolvePackageHeaderPath(file_name, content_type, title_id); - constexpr uint32_t header_size = sizeof(XCONTENT_AGGREGATE_DATA); - - if (std::filesystem::exists(header_file_path)) { - auto file = xe::filesystem::OpenFile(header_file_path, "rb"); - - std::array buffer; - - auto file_size = std::filesystem::file_size(header_file_path); - if (file_size != header_size && file_size != sizeof(XCONTENT_DATA)) { - fclose(file); - return X_STATUS_END_OF_FILE; - } - - size_t result = fread(buffer.data(), 1, file_size, file); - if (result != file_size) { - fclose(file); - return X_STATUS_END_OF_FILE; - } - fclose(file); - std::memcpy(&data, buffer.data(), buffer.size()); - // It only reads basic info, however importing savefiles - // usually requires title_id to be provided - // Kinda simple workaround for that, but still assumption - data.title_id = title_id; - data.unk134 = kernel_state_->user_profile(uint32_t(0))->xuid(); - return X_STATUS_SUCCESS; - } - return X_STATUS_NO_SUCH_FILE; + return unique_id; } X_RESULT ContentManager::CreateContent(const std::string_view root_name, - const XCONTENT_AGGREGATE_DATA& data) { + const XCONTENT_AGGREGATE_DATA& data, + const uint32_t flags) { auto global_lock = global_critical_region_.Acquire(); if (open_packages_.count(string_key(root_name))) { @@ -290,12 +244,21 @@ X_RESULT ContentManager::CreateContent(const std::string_view root_name, return X_ERROR_ALREADY_EXISTS; } - if (!std::filesystem::create_directories(package_path)) { - return X_ERROR_ACCESS_DENIED; + if (!std::filesystem::exists(package_path.parent_path())) { + if (!std::filesystem::create_directories(package_path.parent_path())) { + return X_ERROR_ACCESS_DENIED; + } } - auto package = ResolvePackage(root_name, data); + const ContentPackage::PackageType saving_package_type = + ContentPackage::PackageType::host; + + auto package = ContentPackage::CreatePackage( + kernel_state_, saving_package_type, package_path, &data); + assert_not_null(package); + package->CreatePackageFile(flags); + package->MountPackage(root_name); open_packages_.insert({string_key::create(root_name), package.release()}); @@ -319,14 +282,27 @@ X_RESULT ContentManager::OpenContent(const std::string_view root_name, } // Open package. - auto package = ResolvePackage(root_name, data, disc_number); + auto package = ResolvePackage(data, disc_number); assert_not_null(package); + package->MountPackage(root_name); open_packages_.insert({string_key::create(root_name), package.release()}); return X_ERROR_SUCCESS; } +uint32_t ContentManager::GetContentLicense( + const std::string_view root_name) const { + auto global_lock = global_critical_region_.Acquire(); + + if (!open_packages_.count(string_key(root_name))) { + // Already content open with this root name. + return 0; + } + + return open_packages_.at(string_key(root_name))->GetLicense(); +} + X_RESULT ContentManager::CloseContent(const std::string_view root_name) { auto global_lock = global_critical_region_.Acquire(); @@ -346,34 +322,27 @@ X_RESULT ContentManager::CloseContent(const std::string_view root_name) { X_RESULT ContentManager::GetContentThumbnail( const XCONTENT_AGGREGATE_DATA& data, std::vector* buffer) { auto global_lock = global_critical_region_.Acquire(); - auto package_path = ResolvePackagePath(data); - auto thumb_path = package_path / kThumbnailFileName; - if (std::filesystem::exists(thumb_path)) { - auto file = xe::filesystem::OpenFile(thumb_path, "rb"); - size_t file_len = std::filesystem::file_size(thumb_path); - buffer->resize(file_len); - fread(const_cast(buffer->data()), 1, buffer->size(), file); - fclose(file); - return X_ERROR_SUCCESS; - } else { + auto package = ResolvePackage(data); + if (!package) { return X_ERROR_FILE_NOT_FOUND; } + + const auto thumbnail = package->GetPackageThumbnail(); + memcpy(buffer->data(), thumbnail.data(), thumbnail.size()); + return X_ERROR_SUCCESS; } X_RESULT ContentManager::SetContentThumbnail( const XCONTENT_AGGREGATE_DATA& data, std::vector buffer) { auto global_lock = global_critical_region_.Acquire(); - auto package_path = ResolvePackagePath(data); - std::filesystem::create_directories(package_path); - if (std::filesystem::exists(package_path)) { - auto thumb_path = package_path / kThumbnailFileName; - auto file = xe::filesystem::OpenFile(thumb_path, "wb"); - fwrite(buffer.data(), 1, buffer.size(), file); - fclose(file); - return X_ERROR_SUCCESS; - } else { + auto package = ResolvePackage(data); + if (!package) { return X_ERROR_FILE_NOT_FOUND; } + + package->SetPackageThumbnail(buffer); + + return X_ERROR_SUCCESS; } X_RESULT ContentManager::DeleteContent(const XCONTENT_AGGREGATE_DATA& data) { @@ -405,7 +374,7 @@ std::filesystem::path ContentManager::ResolveGameUserContentPath() { bool ContentManager::IsContentOpen(const XCONTENT_AGGREGATE_DATA& data) const { return std::any_of(open_packages_.cbegin(), open_packages_.cend(), [data](std::pair content) { - return data == content.second->GetPackageContentData(); + return &data == content.second->GetPackageContentData(); }); } diff --git a/src/xenia/kernel/xam/content/content_manager.h b/src/xenia/kernel/xam/content/content_manager.h new file mode 100644 index 00000000000..56b568ded5a --- /dev/null +++ b/src/xenia/kernel/xam/content/content_manager.h @@ -0,0 +1,98 @@ +/** + ****************************************************************************** + * 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 +#include +#include +#include +#include + +#include "xenia/base/memory.h" +#include "xenia/base/mutex.h" +#include "xenia/base/string_key.h" +#include "xenia/base/string_util.h" +#include "xenia/kernel/xam/content/content_package.h" +#include "xenia/kernel/xam/content/xcontent.h" +#include "xenia/xbox.h" + +namespace xe { +namespace kernel { +class KernelState; +} // namespace kernel +} // namespace xe + +namespace xe { +namespace kernel { +namespace xam { + +class ContentManager { + public: + ContentManager(KernelState* kernel_state, + const std::filesystem::path& root_path); + ~ContentManager(); + + std::filesystem::path GetRootPath() const { return root_path_; } + + std::vector ListContent( + const uint32_t device_id, const XContentType content_type, + uint32_t title_id = -1); + + std::unique_ptr ResolvePackage( + const XCONTENT_AGGREGATE_DATA& data, const uint32_t disc_number = -1); + + bool ContentExists(const XCONTENT_AGGREGATE_DATA& data); + X_RESULT CreateContent(const std::string_view root_name, + const XCONTENT_AGGREGATE_DATA& data, + const uint32_t flags); + X_RESULT OpenContent(const std::string_view root_name, + const XCONTENT_AGGREGATE_DATA& data, + const uint32_t disc_number = -1); + X_RESULT CloseContent(const std::string_view root_name); + + uint32_t GetContentLicense(const std::string_view root_name) const; + + X_RESULT GetContentThumbnail(const XCONTENT_AGGREGATE_DATA& data, + std::vector* buffer); + X_RESULT SetContentThumbnail(const XCONTENT_AGGREGATE_DATA& data, + std::vector buffer); + X_RESULT DeleteContent(const XCONTENT_AGGREGATE_DATA& data); + std::filesystem::path ResolveGameUserContentPath(); + bool IsContentOpen(const XCONTENT_AGGREGATE_DATA& data) const; + void CloseOpenedFilesFromContent(const std::string_view root_name); + + static std::string GenerateUniquePackageId(const std::filesystem::path path); + + private: + std::filesystem::path ResolvePackageRoot(XContentType content_type, + uint32_t title_id = -1); + std::filesystem::path ResolvePackagePath(const XCONTENT_AGGREGATE_DATA& data, + const uint32_t disc_number = -1); + + ContentPackage::PackageType ResolvePackageType( + const std::filesystem::path path) const; + + std::set FindPublisherTitleIds( + uint32_t base_title_id = kCurrentlyRunningTitleId) const; + + 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 open_packages_; +}; + +} // namespace xam +} // namespace kernel +} // namespace xe + +#endif // XENIA_KERNEL_XAM_CONTENT_MANAGER_H_ diff --git a/src/xenia/kernel/xam/content/content_package.cc b/src/xenia/kernel/xam/content/content_package.cc new file mode 100644 index 00000000000..f5825d62dcb --- /dev/null +++ b/src/xenia/kernel/xam/content/content_package.cc @@ -0,0 +1,66 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2024 Xenia Canary. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/kernel/xam/content/content_package.h" +#include "third_party/fmt/include/fmt/format.h" +#include "xenia/kernel/kernel_state.h" +#include "xenia/kernel/xam/content/content_manager.h" +#include "xenia/kernel/xam/content/host_content_package.h" +#include "xenia/kernel/xam/content/xcontent_package.h" +#include "xenia/vfs/devices/host_path_device.h" +#include "xenia/vfs/devices/xcontent_container_device.h" + +namespace xe { +namespace kernel { +namespace xam { + +std::unique_ptr ContentPackage::CreatePackage( + KernelState* kernel_state, const PackageType packageType, + const std::filesystem::path& package_path, + const XCONTENT_AGGREGATE_DATA* data) { + switch (packageType) { + case PackageType::host: + return std::make_unique(kernel_state, package_path, + data); + + case PackageType::xcontent: + return std::make_unique(kernel_state, package_path, + data); + + default: + break; + } + + return nullptr; +} + +ContentPackage::ContentPackage(KernelState* kernel_state, + const std::filesystem::path& package_path, + const XCONTENT_AGGREGATE_DATA* data) + : kernel_state_(kernel_state), + package_path_(package_path), + content_data_(nullptr) { + device_path_ = + fmt::format("\\Device\\Package_{}", + ContentManager::GenerateUniquePackageId(package_path)); + + if (data) { + content_data_ = std::make_unique(*data); + } +} + +ContentPackage::~ContentPackage() { + auto fs = kernel_state_->file_system(); + fs->UnregisterSymbolicLink(root_name_ + ":"); + fs->UnregisterDevice(device_path_); +} + +} // namespace xam +} // namespace kernel +} // namespace xe \ No newline at end of file diff --git a/src/xenia/kernel/xam/content/content_package.h b/src/xenia/kernel/xam/content/content_package.h new file mode 100644 index 00000000000..aea73b54ce2 --- /dev/null +++ b/src/xenia/kernel/xam/content/content_package.h @@ -0,0 +1,78 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2024 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_KERNEL_XAM_CONTENT_CONTENT_PACKAGE_H_ +#define XENIA_KERNEL_XAM_CONTENT_CONTENT_PACKAGE_H_ + +#include +#include "xenia/kernel/xam/content/xcontent.h" + +namespace xe { +namespace kernel { +class KernelState; +} // namespace kernel +} // namespace xe + +namespace xe { +namespace kernel { +namespace xam { + +class ContentPackage { + public: + enum class PackageType { host = 0, xcontent = 1, unknown = 0xFF }; + + ContentPackage(KernelState* kernel_state, + const std::filesystem::path& package_path, + const XCONTENT_AGGREGATE_DATA* data = nullptr); + ~ContentPackage(); + + static std::unique_ptr CreatePackage( + KernelState* kernel_state, const PackageType packageType, + const std::filesystem::path& package_path, + const XCONTENT_AGGREGATE_DATA* data = nullptr); + + const XCONTENT_AGGREGATE_DATA* GetPackageContentData() const { + return content_data_.get(); + } + + virtual bool CreatePackageFile(const uint32_t flags) = 0; + virtual bool MountPackage(const std::string_view root_name) = 0; + + virtual void SetPackageThumbnail(const std::vector buffer) = 0; + virtual std::vector GetPackageThumbnail() const = 0; + virtual uint32_t GetLicense() const = 0; + + protected: + std::string_view GetDevicePath() const { return device_path_; } + std::string_view GetRootName() const { return root_name_; } + std::filesystem::path GetPackagePath() const { return package_path_; } + + void SetRootName(const std::string_view root_name) { + if (!root_name_.empty()) { + return; + } + root_name_ = root_name; + } + + KernelState* kernel_state_; + std::unique_ptr content_data_; + + private: + virtual bool Initialize() = 0; + + std::string device_path_; + std::string root_name_ = ""; + std::filesystem::path package_path_; +}; + +} // namespace xam +} // namespace kernel +} // namespace xe + +#endif // XENIA_KERNEL_XAM_CONTENT_CONTENT_PACKAGE_H_ \ No newline at end of file diff --git a/src/xenia/kernel/xam/content/host_content_package.cc b/src/xenia/kernel/xam/content/host_content_package.cc new file mode 100644 index 00000000000..186108eb503 --- /dev/null +++ b/src/xenia/kernel/xam/content/host_content_package.cc @@ -0,0 +1,187 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2024 Xenia Canary. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/kernel/xam/content/host_content_package.h" +#include "third_party/fmt/include/fmt/format.h" +#include "xenia/kernel/kernel_state.h" +#include "xenia/kernel/xam/content/content_manager.h" +#include "xenia/kernel/xam/content/content_package.h" +#include "xenia/vfs/devices/host_path_device.h" + +DECLARE_int32(license_mask); + +namespace xe { +namespace kernel { +namespace xam { + +HostContentPackage::HostContentPackage( + KernelState* kernel_state, const std::filesystem::path& package_path, + const XCONTENT_AGGREGATE_DATA* data) + : ContentPackage(kernel_state, package_path, data) { + Initialize(); +} + +HostContentPackage::HostContentPackage( + KernelState* kernel_state, const std::filesystem::path& package_path, + const uint32_t device_id, const uint32_t title_id, + const XContentType content_type) + : ContentPackage(kernel_state, package_path, nullptr) { + ReadHeader(device_id, title_id, content_type); +} + +bool HostContentPackage::Initialize() { + // content_data_. + + return true; +} + +bool HostContentPackage::CreatePackageFile(const uint32_t flags) { + if (!std::filesystem::create_directories(GetPackagePath())) { + return false; + } + + WriteHeader(); + + return true; +} + +bool HostContentPackage::MountPackage(const std::string_view root_name) { + SetRootName(root_name); + const std::string_view device_path_ = GetDevicePath(); + auto fs = kernel_state_->file_system(); + auto device = std::make_unique(device_path_, + GetPackagePath(), false); + device->Initialize(); + fs->RegisterDevice(std::move(device)); + fs->RegisterSymbolicLink(std::string(GetRootName()) + ":", device_path_); + + return true; +} + +X_RESULT HostContentPackage::ReadHeader(const uint32_t device_id, + const uint32_t title_id, + const XContentType content_type) { + const auto header_file_path = ResolveHeaderPath(title_id, content_type); + + if (!std::filesystem::exists(header_file_path)) { + // Header file doesn't exist. Let's create default one. + + XCONTENT_AGGREGATE_DATA content_data; + + content_data.device_id = device_id; + content_data.content_type = static_cast(content_type); + content_data.set_display_name( + xe::path_to_utf16(GetPackagePath().filename())); + content_data.set_file_name(xe::path_to_utf8(GetPackagePath().filename())); + content_data.title_id = title_id; + content_data.xuid = + kernel_state_->user_profile(static_cast(0))->xuid(); + + content_data_ = std::make_unique(content_data); + return X_STATUS_SUCCESS; + } + + auto file_size = std::filesystem::file_size(header_file_path); + if (file_size != sizeof(XCONTENT_DATA) && + file_size != sizeof(XCONTENT_AGGREGATE_DATA)) { + return X_STATUS_UNSUCCESSFUL; + } + + auto file = xe::filesystem::OpenFile(header_file_path, "rb"); + if (!file) { + return X_STATUS_UNSUCCESSFUL; + } + + content_data_ = std::make_unique(); + size_t result = fread(content_data_.get(), 1, file_size, file); + if (result != sizeof(XCONTENT_DATA) && + result != sizeof(XCONTENT_AGGREGATE_DATA)) { + content_data_.reset(); + return X_STATUS_UNSUCCESSFUL; + } + fclose(file); + + content_data_->title_id = title_id; + content_data_->xuid = + kernel_state_->user_profile(static_cast(0))->xuid(); + return X_STATUS_SUCCESS; +} + +bool HostContentPackage::WriteHeader() { + const auto header_file_path = + ResolveHeaderPath(kernel_state_->title_id(), content_data_->content_type); + auto parent_path = header_file_path.parent_path(); + + if (!std::filesystem::exists(parent_path)) { + if (!std::filesystem::create_directories(parent_path)) { + return false; + } + } + + xe::filesystem::CreateEmptyFile(header_file_path); + + if (!std::filesystem::exists(header_file_path)) { + return false; + } + + auto file = xe::filesystem::OpenFile(header_file_path, "wb"); + fwrite(content_data_.get(), 1, sizeof(XCONTENT_AGGREGATE_DATA), file); + fclose(file); + return true; +} + +std::filesystem::path HostContentPackage::ResolveHeaderPath( + const uint32_t title_id, const XContentType content_type) { + auto title_id_str = fmt::format("{:08X}", title_id); + auto content_type_str = + fmt::format("{:08X}", static_cast(content_type)); + std::string final_name = + xe::path_to_utf8(GetPackagePath().filename()) + ".header"; + + // Header root path: + // content_root/title_id/Headers/content_type/ + return kernel_state_->content_manager()->GetRootPath() / title_id_str / + kGameContentHeaderDirName / content_type_str / final_name; +} + +std::vector HostContentPackage::GetPackageThumbnail() const { + const auto thumbnail_path = + GetPackagePath().parent_path() / kThumbnailFileName; + + if (!std::filesystem::exists(thumbnail_path)) { + return {}; + } + + std::vector buffer; + auto file = xe::filesystem::OpenFile(thumbnail_path, "rb"); + size_t file_len = std::filesystem::file_size(thumbnail_path); + buffer.resize(file_len); + fread(buffer.data(), 1, buffer.size(), file); + fclose(file); + return buffer; +} + +void HostContentPackage::SetPackageThumbnail( + const std::vector buffer) { + std::filesystem::create_directories(GetPackagePath()); + + if (std::filesystem::exists(GetPackagePath())) { + auto thumb_path = GetPackagePath() / kThumbnailFileName; + auto file = xe::filesystem::OpenFile(thumb_path, "wb"); + fwrite(buffer.data(), 1, buffer.size(), file); + fclose(file); + } + return; +} + +uint32_t HostContentPackage::GetLicense() const { return cvars::license_mask; } + +} // namespace xam +} // namespace kernel +} // namespace xe \ No newline at end of file diff --git a/src/xenia/kernel/xam/content/host_content_package.h b/src/xenia/kernel/xam/content/host_content_package.h new file mode 100644 index 00000000000..034edfc911a --- /dev/null +++ b/src/xenia/kernel/xam/content/host_content_package.h @@ -0,0 +1,62 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2024 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_KERNEL_XAM_CONTENT_HOST_CONTENT_PACKAGE_H_ +#define XENIA_KERNEL_XAM_CONTENT_HOST_CONTENT_PACKAGE_H_ + +#include +#include "xenia/kernel/xam/content/content_package.h" +#include "xenia/kernel/xam/content/xcontent.h" + +namespace xe { +namespace kernel { +class KernelState; +} // namespace kernel +} // namespace xe + +namespace xe { +namespace kernel { +namespace xam { + +static const char* kThumbnailFileName = "__thumbnail.png"; +static const char* kGameContentHeaderDirName = "Headers"; + +class HostContentPackage : public ContentPackage { + public: + HostContentPackage(KernelState* kernel_state, + const std::filesystem::path& package_path, + const XCONTENT_AGGREGATE_DATA* data = nullptr); + + HostContentPackage(KernelState* kernel_state, + const std::filesystem::path& package_path, + const uint32_t device_id, const uint32_t title_id, + const XContentType content_type); + ~HostContentPackage(){}; + + bool CreatePackageFile(const uint32_t flags) override; + bool MountPackage(const std::string_view root_name) override; + + std::vector GetPackageThumbnail() const override; + void SetPackageThumbnail(const std::vector buffer) override; + uint32_t GetLicense() const override; + + private: + bool Initialize() override; + X_RESULT ReadHeader(const uint32_t device_id, const uint32_t title_id, + const XContentType content_type); + bool WriteHeader(); + std::filesystem::path ResolveHeaderPath(const uint32_t title_id, + const XContentType content_type); +}; + +} // namespace xam +} // namespace kernel +} // namespace xe + +#endif \ No newline at end of file diff --git a/src/xenia/kernel/xam/content/xcontent.h b/src/xenia/kernel/xam/content/xcontent.h new file mode 100644 index 00000000000..84dcc6359ae --- /dev/null +++ b/src/xenia/kernel/xam/content/xcontent.h @@ -0,0 +1,386 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2024 Xenia Canary. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_KERNEL_XAM_XCONTENT_H_ +#define XENIA_KERNEL_XAM_XCONTENT_H_ + +#include "xenia/base/string_util.h" +#include "xenia/vfs/devices/stfs_xbox.h" +#include "xenia/xbox.h" + +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; + +enum class XContentPackageType : uint32_t { + kCon = 0x434F4E20, + kPirs = 0x50495253, + kLive = 0x4C495645, +}; + +enum class XContentVolumeType : uint32_t { + kStfs = 0, + kSvod = 1, +}; + +struct XContentLicense { + be licensee_id; + be license_bits; + be license_flags; +}; +static_assert_size(XContentLicense, 0x10); + +struct XContentMediaData { + uint8_t series_id[0x10]; + uint8_t season_id[0x10]; + be season_number; + be episode_number; +}; +static_assert_size(XContentMediaData, 0x24); + +struct XContentAvatarAssetData { + be sub_category; + be colorizable; + uint8_t asset_id[0x10]; + uint8_t skeleton_version_mask; + uint8_t reserved[0xB]; +}; +static_assert_size(XContentAvatarAssetData, 0x24); + +struct XContentAttributes { + uint8_t profile_transfer : 1; + uint8_t device_transfer : 1; + uint8_t move_only_transfer : 1; + uint8_t kinect_enabled : 1; + uint8_t disable_network_storage : 1; + uint8_t deep_link_supported : 1; + uint8_t reserved : 2; +}; +static_assert_size(XContentAttributes, 1); + +#pragma pack(push, 1) +struct XContentMetadata { + static const uint32_t kThumbLengthV1 = 0x4000; + static const uint32_t kThumbLengthV2 = 0x3D00; + + static const uint32_t kNumLanguagesV1 = 9; + // metadata_version 2 adds 3 languages inside thumbnail/title_thumbnail space + static const uint32_t kNumLanguagesV2 = 12; + + be content_type; + be metadata_version; + be content_size; + xex2_opt_execution_info execution_info; + uint8_t console_id[5]; + be profile_id; + union { + vfs::StfsVolumeDescriptor stfs; + vfs::SvodDeviceDescriptor svod; + } volume_descriptor; + be data_file_count; + be data_file_size; + be volume_type; + be online_creator; + be category; + uint8_t reserved2[0x20]; + union { + XContentMediaData media_data; + XContentAvatarAssetData avatar_asset_data; + } metadata_v2; + uint8_t device_id[0x14]; + union { + be uint[kNumLanguagesV1][128]; + char16_t chars[kNumLanguagesV1][128]; + } display_name_raw; + union { + be uint[kNumLanguagesV1][128]; + char16_t chars[kNumLanguagesV1][128]; + } description_raw; + union { + be uint[64]; + char16_t chars[64]; + } publisher_raw; + union { + be uint[64]; + char16_t chars[64]; + } title_name_raw; + union { + uint8_t as_byte; + XContentAttributes bits; + } flags; + be thumbnail_size; + be title_thumbnail_size; + uint8_t thumbnail[kThumbLengthV2]; + union { + be uint[kNumLanguagesV2 - kNumLanguagesV1][128]; + char16_t chars[kNumLanguagesV2 - kNumLanguagesV1][128]; + } display_name_ex_raw; + uint8_t title_thumbnail[kThumbLengthV2]; + union { + be uint[kNumLanguagesV2 - kNumLanguagesV1][128]; + char16_t chars[kNumLanguagesV2 - kNumLanguagesV1][128]; + } description_ex_raw; + + std::u16string display_name(XLanguage language) const { + uint32_t lang_id = uint32_t(language) - 1; + + if (lang_id >= kNumLanguagesV2) { + assert_always(); + // no room for this lang, read from english slot.. + lang_id = uint32_t(XLanguage::kEnglish) - 1; + } + + const be* str = 0; + if (lang_id >= 0 && lang_id < kNumLanguagesV1) { + str = display_name_raw.uint[lang_id]; + } else if (lang_id >= kNumLanguagesV1 && lang_id < kNumLanguagesV2 && + metadata_version >= 2) { + str = display_name_ex_raw.uint[lang_id - kNumLanguagesV1]; + } + + if (!str) { + // Invalid language ID? + assert_always(); + return u""; + } + + return load_and_swap(str); + } + + std::u16string description(XLanguage language) const { + uint32_t lang_id = uint32_t(language) - 1; + + if (lang_id >= kNumLanguagesV2) { + assert_always(); + // no room for this lang, read from english slot.. + lang_id = uint32_t(XLanguage::kEnglish) - 1; + } + + const be* str = 0; + if (lang_id >= 0 && lang_id < kNumLanguagesV1) { + str = description_raw.uint[lang_id]; + } else if (lang_id >= kNumLanguagesV1 && lang_id < kNumLanguagesV2 && + metadata_version >= 2) { + str = description_ex_raw.uint[lang_id - kNumLanguagesV1]; + } + + if (!str) { + // Invalid language ID? + assert_always(); + return u""; + } + + return load_and_swap(str); + } + + std::u16string publisher() const { + return load_and_swap(publisher_raw.uint); + } + + std::u16string title_name() const { + return load_and_swap(title_name_raw.uint); + } + + bool set_display_name(XLanguage language, const std::u16string_view value) { + uint32_t lang_id = uint32_t(language) - 1; + + if (lang_id >= kNumLanguagesV2) { + assert_always(); + // no room for this lang, store in english slot.. + lang_id = uint32_t(XLanguage::kEnglish) - 1; + } + + char16_t* str = 0; + if (lang_id >= 0 && lang_id < kNumLanguagesV1) { + str = display_name_raw.chars[lang_id]; + } else if (lang_id >= kNumLanguagesV1 && lang_id < kNumLanguagesV2 && + metadata_version >= 2) { + str = display_name_ex_raw.chars[lang_id - kNumLanguagesV1]; + } + + if (!str) { + // Invalid language ID? + assert_always(); + return false; + } + + string_util::copy_and_swap_truncating(str, value, + countof(display_name_raw.chars[0])); + return true; + } + + bool set_description(XLanguage language, const std::u16string_view value) { + uint32_t lang_id = uint32_t(language) - 1; + + if (lang_id >= kNumLanguagesV2) { + assert_always(); + // no room for this lang, store in english slot.. + lang_id = uint32_t(XLanguage::kEnglish) - 1; + } + + char16_t* str = 0; + if (lang_id >= 0 && lang_id < kNumLanguagesV1) { + str = description_raw.chars[lang_id]; + } else if (lang_id >= kNumLanguagesV1 && lang_id < kNumLanguagesV2 && + metadata_version >= 2) { + str = description_ex_raw.chars[lang_id - kNumLanguagesV1]; + } + + if (!str) { + // Invalid language ID? + assert_always(); + return false; + } + + string_util::copy_and_swap_truncating(str, value, + countof(description_raw.chars[0])); + return true; + } + + void set_publisher(const std::u16string_view value) { + string_util::copy_and_swap_truncating(publisher_raw.chars, value, + countof(publisher_raw.chars)); + } + + void set_title_name(const std::u16string_view value) { + string_util::copy_and_swap_truncating(title_name_raw.chars, value, + countof(title_name_raw.chars)); + } +}; +static_assert_size(XContentMetadata, 0x93D6); + +struct XContentHeader { + be magic; + union { + // signature used by LIVE/PIRS content packages + uint8_t online[0x228]; + // signature used by CON (console-signed) savegame/profile packages + X_XE_CONSOLE_SIGNATURE console; + } signature; + + XContentLicense licenses[0x10]; + uint8_t content_id[0x14]; + be header_size; + + bool is_magic_valid() const { + return magic == XContentPackageType::kCon || + magic == XContentPackageType::kLive || + magic == XContentPackageType::kPirs; + } +}; +static_assert_size(XContentHeader, 0x344); +#pragma pack(pop) + +struct XContentContainerHeader { + XContentHeader content_header; + XContentMetadata content_metadata; + // TODO: title/system updates contain more data after XContentMetadata, seems + // to affect header.header_size + + bool is_package_readonly() const { + if (content_metadata.volume_type == XContentVolumeType::kSvod) { + return true; + } + + return content_metadata.volume_descriptor.stfs.flags.bits.read_only_format; + } +}; +static_assert_size(XContentContainerHeader, 0x971A); + +struct XCONTENT_DATA { + be device_id; + be content_type; + union { + // this should be be, 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 load_and_swap(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( + 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 xuid; + be 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); + +} // namespace xam +} // namespace kernel +} // namespace xe + +#endif // XENIA_KERNEL_XAM_XCONTENT_H_ \ No newline at end of file diff --git a/src/xenia/kernel/xam/content/xcontent_package.cc b/src/xenia/kernel/xam/content/xcontent_package.cc new file mode 100644 index 00000000000..c915ea76295 --- /dev/null +++ b/src/xenia/kernel/xam/content/xcontent_package.cc @@ -0,0 +1,208 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2024 Xenia Canary. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/kernel/xam/content/xcontent_package.h" +#include "third_party/fmt/include/fmt/format.h" +#include "xenia/kernel/kernel_state.h" +#include "xenia/kernel/user_module.h" +#include "xenia/kernel/xam/content/content_manager.h" +#include "xenia/kernel/xam/content/content_package.h" +#include "xenia/kernel/xam/content/xcontent.h" +#include "xenia/kernel/xam/user_profile.h" +#include "xenia/vfs/devices/xcontent_container_device.h" + +namespace xe { +namespace kernel { +namespace xam { + +XContentPackage::XContentPackage(KernelState* kernel_state, + const std::filesystem::path& package_path, + const XCONTENT_AGGREGATE_DATA* data) + : ContentPackage(kernel_state, package_path, data){}; + +bool XContentPackage::MountPackage(const std::string_view root_name) { + SetRootName(root_name); + const std::string_view device_path_ = GetDevicePath(); + auto fs = kernel_state_->file_system(); + auto device = vfs::XContentContainerDevice::CreateContentDevice( + device_path_, GetPackagePath()); + device->Initialize(); + fs->RegisterDevice(std::move(device)); + fs->RegisterSymbolicLink(std::string(GetRootName()) + ":", device_path_); + + return true; +} + +bool XContentPackage::CreatePackageFile(const uint32_t flags) { + if (std::filesystem::exists(GetPackagePath())) { + return false; + } + + if (!xe::filesystem::CreateEmptyFile(GetPackagePath())) { + return false; + } + + std::error_code resize_error; + std::filesystem::resize_file( + GetPackagePath(), xe::round_up(sizeof(XContentContainerHeader), 0x1000), + resize_error); + + if (resize_error) { + return false; + } + + if (!LoadAndReadHeader(true)) { + return false; + } + + WriteBasicHeaderInfo(flags); + + memcpy(mapped_header_->data(), header_.get(), + sizeof(XContentContainerHeader)); + mapped_header_->Flush(); + + return true; +} +bool XContentPackage::WriteBasicHeaderInfo(const uint32_t flags) { + header_->content_header.magic = XContentPackageType::kCon; + header_->content_header.licenses[0].licensee_id = static_cast(-1); + header_->content_header.header_size = sizeof(XContentContainerHeader); + + if (content_data_) { + header_->content_metadata.content_type = content_data_->content_type; + header_->content_metadata.metadata_version = 2; + + xex2_opt_execution_info* opt_exec_info = nullptr; + + kernel_state_->GetExecutableModule()->GetOptHeader( + XEX_HEADER_EXECUTION_INFO, &opt_exec_info); + + memcpy(&header_->content_metadata.execution_info, opt_exec_info, + sizeof(xex2_opt_execution_info)); + + std::string console_id = "XENIA"; + memcpy(header_->content_metadata.console_id, console_id.c_str(), 5); + + header_->content_metadata.profile_id = + kernel_state_->user_profile(static_cast(0))->xuid(); + + // TODO! + header_->content_metadata.device_id[0] = 0; + + memcpy(header_->content_metadata.display_name_raw.chars, + content_data_->display_name_raw.chars, 128); + + std::u16string title_name = + xe::to_utf16(kernel_state_->title_xdbf().title()); + + xe::string_util::copy_and_swap_truncating( + header_->content_metadata.title_name_raw.chars, title_name, 64); + + header_->content_metadata.flags.as_byte = flags; + + const auto icon = kernel_state_->title_xdbf().icon(); + if (icon) { + header_->content_metadata.thumbnail_size = + static_cast(icon.size); + header_->content_metadata.title_thumbnail_size = + static_cast(icon.size); + + memcpy(header_->content_metadata.thumbnail, icon.buffer, icon.size); + memcpy(header_->content_metadata.title_thumbnail, icon.buffer, icon.size); + } + } + + header_->content_header.signature.console.console_certificate.console_type = + XConsoleType::Retail; + return true; +} + +bool XContentPackage::LoadAndReadHeader(bool writable) { + mapped_header_ = + MappedMemory::Open(GetPackagePath(), + !writable ? xe::MappedMemory::Mode::kRead + : xe::MappedMemory::Mode::kReadWrite, + 0, sizeof(XContentContainerHeader)); + + if (!mapped_header_) { + return false; + } + + header_ = std::make_unique(); + memcpy(header_.get(), mapped_header_->data(), + sizeof(XContentContainerHeader)); + + mapped_header_->Close(); + return true; +} + +bool XContentPackage::Initialize() { + if (!LoadAndReadHeader()) { + return false; + } + + if (!IsPackageValid()) { + return false; + } + + content_data_->content_type = header_->content_metadata.content_type; + content_data_->device_id = 0; + + memcpy(content_data_->display_name_raw.uint, + header_->content_metadata.display_name_raw.uint, 128); + + content_data_->title_id = header_->content_metadata.execution_info.title_id; + content_data_->xuid = header_->content_metadata.profile_id; + memset(content_data_->file_name_raw, 0, 42); + + const auto filename = xe::path_to_utf8(GetPackagePath().filename()); + + memcpy(content_data_->file_name_raw, filename.c_str(), filename.length()); + + return true; +} + +bool XContentPackage::IsPackageValid() { + if (!header_) { + return false; + } + + if (!header_->content_header.is_magic_valid()) { + return false; + } + + return true; +} + +std::vector XContentPackage::GetPackageThumbnail() const { + std::vector title_thumbnail; + title_thumbnail.resize(header_->content_metadata.thumbnail_size); + + memcpy(title_thumbnail.data(), header_->content_metadata.thumbnail, + header_->content_metadata.thumbnail_size); + + return title_thumbnail; +} + +void XContentPackage::SetPackageThumbnail(const std::vector buffer) { + const size_t size = + std::min(buffer.size(), sizeof(header_->content_metadata.thumbnail)); + + memcpy(header_->content_metadata.thumbnail, buffer.data(), size); + + header_->content_metadata.thumbnail_size = static_cast(size); +} + +uint32_t XContentPackage::GetLicense() const { + return header_->content_header.licenses[0].license_bits; +} + +} // namespace xam +} // namespace kernel +} // namespace xe \ No newline at end of file diff --git a/src/xenia/kernel/xam/content/xcontent_package.h b/src/xenia/kernel/xam/content/xcontent_package.h new file mode 100644 index 00000000000..9e163aaaeed --- /dev/null +++ b/src/xenia/kernel/xam/content/xcontent_package.h @@ -0,0 +1,49 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2024 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_KERNEL_XAM_CONTENT_XCONTENT_PACKAGE_H_ +#define XENIA_KERNEL_XAM_CONTENT_XCONTENT_PACKAGE_H_ + +#include +#include "xenia/base/mapped_memory.h" +#include "xenia/kernel/xam/content/content_package.h" +#include "xenia/kernel/xam/content/xcontent.h" + +namespace xe { +namespace kernel { +namespace xam { + +class XContentPackage : public ContentPackage { + public: + XContentPackage(KernelState* kernel_state, + const std::filesystem::path& package_path, + const XCONTENT_AGGREGATE_DATA* data = nullptr); + ~XContentPackage(){}; + + bool CreatePackageFile(const uint32_t flags); + bool MountPackage(const std::string_view root_name) override; + std::vector GetPackageThumbnail() const override; + void SetPackageThumbnail(const std::vector buffer) override; + uint32_t GetLicense() const override; + + private: + bool LoadAndReadHeader(bool writable = false); + bool WriteBasicHeaderInfo(const uint32_t flags); + bool IsPackageValid(); + bool Initialize() override; + + std::unique_ptr header_; + std::unique_ptr mapped_header_; +}; + +} // namespace xam +} // namespace kernel +} // namespace xe + +#endif \ No newline at end of file diff --git a/src/xenia/kernel/xam/content_manager.h b/src/xenia/kernel/xam/content_manager.h deleted file mode 100644 index 0343c12158b..00000000000 --- a/src/xenia/kernel/xam/content_manager.h +++ /dev/null @@ -1,198 +0,0 @@ -/** - ****************************************************************************** - * 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 -#include -#include -#include -#include - -#include "xenia/base/memory.h" -#include "xenia/base/mutex.h" -#include "xenia/base/string_key.h" -#include "xenia/base/string_util.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 device_id; - be content_type; - union { - // this should be be, 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 load_and_swap(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( - 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 unk134; // some titles store XUID here? - be 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; - unk134 = 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(); - - const XCONTENT_AGGREGATE_DATA& GetPackageContentData() const { - return content_data_; - } - - private: - KernelState* kernel_state_; - std::string root_name_; - std::string device_path_; - XCONTENT_AGGREGATE_DATA content_data_; -}; - -class ContentManager { - public: - ContentManager(KernelState* kernel_state, - const std::filesystem::path& root_path); - ~ContentManager(); - - std::vector ListContent(uint32_t device_id, - XContentType content_type, - uint32_t title_id = -1); - - std::unique_ptr ResolvePackage( - const std::string_view root_name, const XCONTENT_AGGREGATE_DATA& data, - const uint32_t disc_number = -1); - - bool ContentExists(const XCONTENT_AGGREGATE_DATA& data); - X_RESULT WriteContentHeaderFile(const XCONTENT_AGGREGATE_DATA* data_raw); - X_RESULT ReadContentHeaderFile(const std::string_view file_name, - XContentType content_type, - XCONTENT_AGGREGATE_DATA& data, - const uint32_t title_id = -1); - X_RESULT CreateContent(const std::string_view root_name, - const XCONTENT_AGGREGATE_DATA& data); - X_RESULT OpenContent(const std::string_view root_name, - const XCONTENT_AGGREGATE_DATA& data, - const uint32_t disc_number = -1); - X_RESULT CloseContent(const std::string_view root_name); - X_RESULT GetContentThumbnail(const XCONTENT_AGGREGATE_DATA& data, - std::vector* buffer); - X_RESULT SetContentThumbnail(const XCONTENT_AGGREGATE_DATA& data, - std::vector buffer); - X_RESULT DeleteContent(const XCONTENT_AGGREGATE_DATA& data); - std::filesystem::path ResolveGameUserContentPath(); - bool IsContentOpen(const XCONTENT_AGGREGATE_DATA& data) const; - void CloseOpenedFilesFromContent(const std::string_view root_name); - - private: - std::filesystem::path ResolvePackageRoot(XContentType content_type, - uint32_t title_id = -1); - std::filesystem::path ResolvePackagePath(const XCONTENT_AGGREGATE_DATA& data, - const uint32_t disc_number = -1); - std::filesystem::path ResolvePackageHeaderPath( - const std::string_view file_name, XContentType content_type, - uint32_t title_id = -1); - - std::set FindPublisherTitleIds( - uint32_t base_title_id = kCurrentlyRunningTitleId) const; - - 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 open_packages_; -}; - -} // namespace xam -} // namespace kernel -} // namespace xe - -#endif // XENIA_KERNEL_XAM_CONTENT_MANAGER_H_ diff --git a/src/xenia/kernel/xam/xam_content.cc b/src/xenia/kernel/xam/xam_content.cc index fef073ad268..d7650d6bb82 100644 --- a/src/xenia/kernel/xam/xam_content.cc +++ b/src/xenia/kernel/xam/xam_content.cc @@ -199,22 +199,14 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name, } if (disposition == kDispositionState::Create) { - result = content_manager->CreateContent(root_name, content_data); - if (XSUCCEEDED(result)) { - content_manager->WriteContentHeaderFile(&content_data); - } + result = content_manager->CreateContent(root_name, content_data, flags); } else if (disposition == kDispositionState::Open) { result = content_manager->OpenContent(root_name, content_data); } if (license_mask_ptr && XSUCCEEDED(result)) { - *license_mask_ptr = 0; // Stub! - - // Set license only for DLCs and XBLA titles - if (content_data.content_type == xe::XContentType::kMarketplaceContent || - content_data.content_type == xe::XContentType::kArcadeTitle) { - *license_mask_ptr = static_cast(cvars::license_mask); - } + *license_mask_ptr = + content_manager->GetContentLicense(root_name); } extended_error = X_HRESULT_FROM_WIN32(result); diff --git a/src/xenia/vfs/devices/stfs_xbox.h b/src/xenia/vfs/devices/stfs_xbox.h index 4312ddd77c8..302332462ca 100644 --- a/src/xenia/vfs/devices/stfs_xbox.h +++ b/src/xenia/vfs/devices/stfs_xbox.h @@ -12,7 +12,6 @@ #include -#include "xenia/base/string_util.h" #include "xenia/kernel/util/xex2_info.h" #include "xenia/xbox.h" @@ -58,17 +57,6 @@ inline void store_uint24_le(uint8_t* p, uint32_t value) { p[0] = uint8_t(value & 0xFF); } -enum class XContentPackageType : uint32_t { - kCon = 0x434F4E20, - kPirs = 0x50495253, - kLive = 0x4C495645, -}; - -enum class XContentVolumeType : uint32_t { - kStfs = 0, - kSvod = 1, -}; - /* STFS structures */ #pragma pack(push, 1) struct StfsVolumeDescriptor { @@ -240,265 +228,6 @@ struct SvodDeviceDescriptor { }; static_assert_size(SvodDeviceDescriptor, 0x24); -/* XContent structures */ -struct XContentLicense { - be licensee_id; - be license_bits; - be license_flags; -}; -static_assert_size(XContentLicense, 0x10); - -struct XContentMediaData { - uint8_t series_id[0x10]; - uint8_t season_id[0x10]; - be season_number; - be episode_number; -}; -static_assert_size(XContentMediaData, 0x24); - -struct XContentAvatarAssetData { - be sub_category; - be colorizable; - uint8_t asset_id[0x10]; - uint8_t skeleton_version_mask; - uint8_t reserved[0xB]; -}; -static_assert_size(XContentAvatarAssetData, 0x24); - -struct XContentAttributes { - uint8_t profile_transfer : 1; - uint8_t device_transfer : 1; - uint8_t move_only_transfer : 1; - uint8_t kinect_enabled : 1; - uint8_t disable_network_storage : 1; - uint8_t deep_link_supported : 1; - uint8_t reserved : 2; -}; -static_assert_size(XContentAttributes, 1); - -#pragma pack(push, 1) -struct XContentMetadata { - static const uint32_t kThumbLengthV1 = 0x4000; - static const uint32_t kThumbLengthV2 = 0x3D00; - - static const uint32_t kNumLanguagesV1 = 9; - // metadata_version 2 adds 3 languages inside thumbnail/title_thumbnail space - static const uint32_t kNumLanguagesV2 = 12; - - be content_type; - be metadata_version; - be content_size; - xex2_opt_execution_info execution_info; - uint8_t console_id[5]; - be profile_id; - union { - StfsVolumeDescriptor stfs; - SvodDeviceDescriptor svod; - } volume_descriptor; - be data_file_count; - be data_file_size; - be volume_type; - be online_creator; - be category; - uint8_t reserved2[0x20]; - union { - XContentMediaData media_data; - XContentAvatarAssetData avatar_asset_data; - } metadata_v2; - uint8_t device_id[0x14]; - union { - be uint[kNumLanguagesV1][128]; - char16_t chars[kNumLanguagesV1][128]; - } display_name_raw; - union { - be uint[kNumLanguagesV1][128]; - char16_t chars[kNumLanguagesV1][128]; - } description_raw; - union { - be uint[64]; - char16_t chars[64]; - } publisher_raw; - union { - be uint[64]; - char16_t chars[64]; - } title_name_raw; - union { - uint8_t as_byte; - XContentAttributes bits; - } flags; - be thumbnail_size; - be title_thumbnail_size; - uint8_t thumbnail[kThumbLengthV2]; - union { - be uint[kNumLanguagesV2 - kNumLanguagesV1][128]; - char16_t chars[kNumLanguagesV2 - kNumLanguagesV1][128]; - } display_name_ex_raw; - uint8_t title_thumbnail[kThumbLengthV2]; - union { - be uint[kNumLanguagesV2 - kNumLanguagesV1][128]; - char16_t chars[kNumLanguagesV2 - kNumLanguagesV1][128]; - } description_ex_raw; - - std::u16string display_name(XLanguage language) const { - uint32_t lang_id = uint32_t(language) - 1; - - if (lang_id >= kNumLanguagesV2) { - assert_always(); - // no room for this lang, read from english slot.. - lang_id = uint32_t(XLanguage::kEnglish) - 1; - } - - const be* str = 0; - if (lang_id >= 0 && lang_id < kNumLanguagesV1) { - str = display_name_raw.uint[lang_id]; - } else if (lang_id >= kNumLanguagesV1 && lang_id < kNumLanguagesV2 && - metadata_version >= 2) { - str = display_name_ex_raw.uint[lang_id - kNumLanguagesV1]; - } - - if (!str) { - // Invalid language ID? - assert_always(); - return u""; - } - - return load_and_swap(str); - } - - std::u16string description(XLanguage language) const { - uint32_t lang_id = uint32_t(language) - 1; - - if (lang_id >= kNumLanguagesV2) { - assert_always(); - // no room for this lang, read from english slot.. - lang_id = uint32_t(XLanguage::kEnglish) - 1; - } - - const be* str = 0; - if (lang_id >= 0 && lang_id < kNumLanguagesV1) { - str = description_raw.uint[lang_id]; - } else if (lang_id >= kNumLanguagesV1 && lang_id < kNumLanguagesV2 && - metadata_version >= 2) { - str = description_ex_raw.uint[lang_id - kNumLanguagesV1]; - } - - if (!str) { - // Invalid language ID? - assert_always(); - return u""; - } - - return load_and_swap(str); - } - - std::u16string publisher() const { - return load_and_swap(publisher_raw.uint); - } - - std::u16string title_name() const { - return load_and_swap(title_name_raw.uint); - } - - bool set_display_name(XLanguage language, const std::u16string_view value) { - uint32_t lang_id = uint32_t(language) - 1; - - if (lang_id >= kNumLanguagesV2) { - assert_always(); - // no room for this lang, store in english slot.. - lang_id = uint32_t(XLanguage::kEnglish) - 1; - } - - char16_t* str = 0; - if (lang_id >= 0 && lang_id < kNumLanguagesV1) { - str = display_name_raw.chars[lang_id]; - } else if (lang_id >= kNumLanguagesV1 && lang_id < kNumLanguagesV2 && - metadata_version >= 2) { - str = display_name_ex_raw.chars[lang_id - kNumLanguagesV1]; - } - - if (!str) { - // Invalid language ID? - assert_always(); - return false; - } - - string_util::copy_and_swap_truncating(str, value, - countof(display_name_raw.chars[0])); - return true; - } - - bool set_description(XLanguage language, const std::u16string_view value) { - uint32_t lang_id = uint32_t(language) - 1; - - if (lang_id >= kNumLanguagesV2) { - assert_always(); - // no room for this lang, store in english slot.. - lang_id = uint32_t(XLanguage::kEnglish) - 1; - } - - char16_t* str = 0; - if (lang_id >= 0 && lang_id < kNumLanguagesV1) { - str = description_raw.chars[lang_id]; - } else if (lang_id >= kNumLanguagesV1 && lang_id < kNumLanguagesV2 && - metadata_version >= 2) { - str = description_ex_raw.chars[lang_id - kNumLanguagesV1]; - } - - if (!str) { - // Invalid language ID? - assert_always(); - return false; - } - - string_util::copy_and_swap_truncating(str, value, - countof(description_raw.chars[0])); - return true; - } - - void set_publisher(const std::u16string_view value) { - string_util::copy_and_swap_truncating(publisher_raw.chars, value, - countof(publisher_raw.chars)); - } - - void set_title_name(const std::u16string_view value) { - string_util::copy_and_swap_truncating(title_name_raw.chars, value, - countof(title_name_raw.chars)); - } -}; -static_assert_size(XContentMetadata, 0x93D6); - -struct XContentHeader { - be magic; - uint8_t signature[0x228]; - XContentLicense licenses[0x10]; - uint8_t content_id[0x14]; - be header_size; - - bool is_magic_valid() const { - return magic == XContentPackageType::kCon || - magic == XContentPackageType::kLive || - magic == XContentPackageType::kPirs; - } -}; -static_assert_size(XContentHeader, 0x344); -#pragma pack(pop) - -struct XContentContainerHeader { - XContentHeader content_header; - XContentMetadata content_metadata; - // TODO: title/system updates contain more data after XContentMetadata, seems - // to affect header.header_size - - bool is_package_readonly() const { - if (content_metadata.volume_type == vfs::XContentVolumeType::kSvod) { - return true; - } - - return content_metadata.volume_descriptor.stfs.flags.bits.read_only_format; - } -}; -static_assert_size(XContentContainerHeader, 0x971A); - } // namespace vfs } // namespace xe diff --git a/src/xenia/vfs/devices/xcontent_container_device.cc b/src/xenia/vfs/devices/xcontent_container_device.cc index 44cf1189102..50bfa3ed0ea 100644 --- a/src/xenia/vfs/devices/xcontent_container_device.cc +++ b/src/xenia/vfs/devices/xcontent_container_device.cc @@ -34,7 +34,7 @@ std::unique_ptr XContentContainerDevice::CreateContentDevice( } const uint64_t package_size = std::filesystem::file_size(host_path); - if (package_size < sizeof(XContentContainerHeader)) { + if (package_size < sizeof(kernel::xam::XContentContainerHeader)) { return nullptr; } @@ -50,10 +50,10 @@ std::unique_ptr XContentContainerDevice::CreateContentDevice( } switch (header->content_metadata.volume_type) { - case XContentVolumeType::kStfs: + case kernel::xam::XContentVolumeType::kStfs: return std::make_unique(mount_path, host_path); break; - case XContentVolumeType::kSvod: + case kernel::xam::XContentVolumeType::kSvod: return std::make_unique(mount_path, host_path); break; default: @@ -69,7 +69,7 @@ XContentContainerDevice::XContentContainerDevice( name_("XContent"), host_path_(host_path), files_total_size_(0), - header_(std::make_unique()) {} + header_(std::make_unique()) {} XContentContainerDevice::~XContentContainerDevice() {} @@ -108,11 +108,13 @@ bool XContentContainerDevice::Initialize() { return Read() == Result::kSuccess; } -XContentContainerHeader* XContentContainerDevice::ReadContainerHeader( - FILE* host_file) { - XContentContainerHeader* header = new XContentContainerHeader(); +kernel::xam::XContentContainerHeader* +XContentContainerDevice::ReadContainerHeader(FILE* host_file) { + kernel::xam::XContentContainerHeader* header = + new kernel::xam::XContentContainerHeader(); // Read header & check signature - if (fread(header, sizeof(XContentContainerHeader), 1, host_file) != 1) { + if (fread(header, sizeof(kernel::xam::XContentContainerHeader), 1, + host_file) != 1) { return nullptr; } return header; @@ -169,16 +171,18 @@ kernel::xam::XCONTENT_AGGREGATE_DATA XContentContainerDevice::content_header() XContentContainerDevice::Result XContentContainerDevice::ReadHeaderAndVerify( FILE* header_file) { files_total_size_ = std::filesystem::file_size(host_path_); - if (files_total_size_ < sizeof(XContentContainerHeader)) { + if (files_total_size_ < sizeof(kernel::xam::XContentContainerHeader)) { return Result::kTooSmall; } - const XContentContainerHeader* header = ReadContainerHeader(header_file); + const kernel::xam::XContentContainerHeader* header = + ReadContainerHeader(header_file); if (header == nullptr) { return Result::kReadError; } - std::memcpy(header_.get(), header, sizeof(XContentContainerHeader)); + std::memcpy(header_.get(), header, + sizeof(kernel::xam::XContentContainerHeader)); if (!header_->content_header.is_magic_valid()) { // Unexpected format. diff --git a/src/xenia/vfs/devices/xcontent_container_device.h b/src/xenia/vfs/devices/xcontent_container_device.h index 2623ac3ed49..be8946ce0ca 100644 --- a/src/xenia/vfs/devices/xcontent_container_device.h +++ b/src/xenia/vfs/devices/xcontent_container_device.h @@ -16,7 +16,8 @@ #include "xenia/base/math.h" #include "xenia/kernel/util/xex2_info.h" -#include "xenia/kernel/xam/content_manager.h" +#include "xenia/kernel/xam/content/content_manager.h" +#include "xenia/kernel/xam/content/xcontent.h" #include "xenia/vfs/device.h" #include "xenia/vfs/devices/stfs_xbox.h" @@ -50,7 +51,7 @@ class XContentContainerDevice : public Device { return files_total_size_ - xe::round_up(header_->content_header.header_size, kBlockSize); } - return files_total_size_ - sizeof(XContentContainerHeader); + return files_total_size_ - sizeof(kernel::xam::XContentContainerHeader); } uint32_t title_id() const { @@ -80,7 +81,7 @@ class XContentContainerDevice : public Device { // multiple file. virtual Result LoadHostFiles(FILE* header_file) = 0; // Initialize any container specific fields. - virtual void SetupContainer() {}; + virtual void SetupContainer(){}; Entry* ResolvePath(const std::string_view path); void CloseFiles(); @@ -95,7 +96,7 @@ class XContentContainerDevice : public Device { const std::filesystem::path& GetHostPath() const { return host_path_; } - const XContentContainerHeader* GetContainerHeader() const { + const kernel::xam::XContentContainerHeader* GetContainerHeader() const { return header_.get(); } @@ -105,10 +106,11 @@ class XContentContainerDevice : public Device { std::map files_; size_t files_total_size_; std::unique_ptr root_entry_; - std::unique_ptr header_; + std::unique_ptr header_; private: - static XContentContainerHeader* ReadContainerHeader(FILE* host_file); + static kernel::xam::XContentContainerHeader* ReadContainerHeader( + FILE* host_file); }; } // namespace vfs diff --git a/src/xenia/vfs/devices/xcontent_devices/stfs_container_device.cc b/src/xenia/vfs/devices/xcontent_devices/stfs_container_device.cc index 417f054147f..562db99f51b 100644 --- a/src/xenia/vfs/devices/xcontent_devices/stfs_container_device.cc +++ b/src/xenia/vfs/devices/xcontent_devices/stfs_container_device.cc @@ -11,7 +11,7 @@ #include #include "xenia/base/logging.h" -#include "xenia/kernel/xam/content_manager.h" +#include "xenia/kernel/xam/content/content_manager.h" #include "xenia/vfs/devices/xcontent_container_entry.h" #include "xenia/vfs/devices/xcontent_devices/stfs_container_device.h" @@ -30,7 +30,7 @@ StfsContainerDevice::~StfsContainerDevice() { CloseFiles(); } void StfsContainerDevice::SetupContainer() { // Additional part specific to STFS container. - const XContentContainerHeader* header = GetContainerHeader(); + const kernel::xam::XContentContainerHeader* header = GetContainerHeader(); blocks_per_hash_table_ = header->is_package_readonly() ? 1 : 2; block_step_[0] = kBlocksPerHashLevel[0] + blocks_per_hash_table_; @@ -40,7 +40,7 @@ void StfsContainerDevice::SetupContainer() { XContentContainerDevice::Result StfsContainerDevice::LoadHostFiles( FILE* header_file) { - const XContentContainerHeader* header = GetContainerHeader(); + const kernel::xam::XContentContainerHeader* header = GetContainerHeader(); if (header->content_metadata.data_file_count > 0) { XELOGW("STFS container is not a single file. Loading might fail!"); @@ -304,7 +304,7 @@ const StfsHashEntry* StfsContainerDevice::GetBlockHash(uint32_t block_index) { const uint8_t StfsContainerDevice::GetBlocksPerHashTableFromContainerHeader() const { - const XContentContainerHeader* header = GetContainerHeader(); + const kernel::xam::XContentContainerHeader* header = GetContainerHeader(); if (!header) { XELOGE( "VFS: SetBlocksPerHashTableBasedOnContainerHeader - Missing " diff --git a/src/xenia/vfs/virtual_file_system.cc b/src/xenia/vfs/virtual_file_system.cc index 077dc1e61c2..df988b9638a 100644 --- a/src/xenia/vfs/virtual_file_system.cc +++ b/src/xenia/vfs/virtual_file_system.cc @@ -8,7 +8,7 @@ */ #include "xenia/vfs/virtual_file_system.h" -#include "xenia/kernel/xam/content_manager.h" +#include "xenia/kernel/xam/content/content_manager.h" #include "xenia/vfs/devices/xcontent_container_device.h" #include "devices/host_path_entry.h" diff --git a/src/xenia/xbox.h b/src/xenia/xbox.h index b8f96dd2214..8be23823fcb 100644 --- a/src/xenia/xbox.h +++ b/src/xenia/xbox.h @@ -437,6 +437,39 @@ struct X_KSPINLOCK { static_assert_size(X_KSPINLOCK, 4); #pragma pack(pop) +typedef struct { + uint8_t public_exponent[4]; + uint8_t modulus[0x80]; +} X_CONSOLE_PUBLIC_KEY; +static_assert_size(X_CONSOLE_PUBLIC_KEY, 0x84); + +enum class XConsoleType : uint32_t { + Invalid = 0, + Devkit = 1, + Retail = 2, + Testkit = 0x40000001, + RecoveredDevkit = 0x80000001 +}; + +typedef struct { + be cert_size; + uint8_t console_id[5]; + uint8_t console_partnumber[11]; + uint8_t reserved[4]; + be privileges; + be console_type; + uint8_t manufacture_date[8]; + X_CONSOLE_PUBLIC_KEY console_publickey; + uint8_t signature[0x100]; +} X_XE_CONSOLE_CERTIFICATE; +static_assert_size(X_XE_CONSOLE_CERTIFICATE, 0x1A8); + +typedef struct { + X_XE_CONSOLE_CERTIFICATE console_certificate; + uint8_t signature[0x80]; +} X_XE_CONSOLE_SIGNATURE; +static_assert_size(X_XE_CONSOLE_SIGNATURE, 0x228); + // Found by dumping the kSectionStringTable sections of various games: // and the language list at // https://free60project.github.io/wiki/Profile_Account/