diff --git a/src/xenia/kernel/xam/content_manager.cc b/src/xenia/kernel/xam/content_manager.cc index bdc09af8b..2b3aee4c9 100644 --- a/src/xenia/kernel/xam/content_manager.cc +++ b/src/xenia/kernel/xam/content_manager.cc @@ -31,7 +31,7 @@ static int content_device_id_ = 0; ContentPackage::ContentPackage(KernelState* kernel_state, const std::string_view root_name, - const ContentData& data, + const XCONTENT_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_); @@ -58,48 +58,26 @@ ContentManager::ContentManager(KernelState* kernel_state, ContentManager::~ContentManager() = default; std::filesystem::path ContentManager::ResolvePackageRoot( - uint32_t content_type) { - auto title_id = fmt::format("{:8X}", kernel_state_->title_id()); - - std::string type_name; - switch (content_type) { - case 1: - // Save games. - type_name = "00000001"; - break; - case 2: - // DLC from the marketplace. - type_name = "00000002"; - break; - case 3: - // Publisher content? - type_name = "00000003"; - break; - case 0x000D0000: - // ??? - type_name = "000D0000"; - break; - default: - assert_unhandled_case(data.content_type); - return std::filesystem::path(); - } + XContentType content_type) { + auto title_id_str = fmt::format("{:8X}", kernel_state_->title_id()); + auto content_type_str = fmt::format("{:08X}", uint32_t(content_type)); // Package root path: - // content_root/title_id/type_name/ - return root_path_ / title_id / type_name; + // content_root/title_id/content_type/ + return root_path_ / title_id_str / content_type_str; } std::filesystem::path ContentManager::ResolvePackagePath( - const ContentData& data) { + const XCONTENT_DATA& data) { // Content path: - // content_root/title_id/type_name/data_file_name/ + // content_root/title_id/content_type/data_file_name/ auto package_root = ResolvePackageRoot(data.content_type); - return package_root / xe::to_path(data.file_name); + return package_root / xe::to_path(data.file_name()); } -std::vector ContentManager::ListContent(uint32_t device_id, - uint32_t content_type) { - std::vector result; +std::vector ContentManager::ListContent( + uint32_t device_id, XContentType content_type) { + std::vector result; // Search path: // content_root/title_id/type_name/* @@ -110,11 +88,11 @@ std::vector ContentManager::ListContent(uint32_t device_id, // Directories only. continue; } - ContentData content_data; + XCONTENT_DATA content_data; content_data.device_id = device_id; content_data.content_type = content_type; - content_data.display_name = xe::path_to_utf16(file_info.name); - content_data.file_name = xe::path_to_utf8(file_info.name); + content_data.set_display_name(xe::path_to_utf16(file_info.name)); + content_data.set_file_name(xe::path_to_utf8(file_info.name)); result.emplace_back(std::move(content_data)); } @@ -122,7 +100,7 @@ std::vector ContentManager::ListContent(uint32_t device_id, } std::unique_ptr ContentManager::ResolvePackage( - const std::string_view root_name, const ContentData& data) { + const std::string_view root_name, const XCONTENT_DATA& data) { auto package_path = ResolvePackagePath(data); if (!std::filesystem::exists(package_path)) { return nullptr; @@ -135,13 +113,13 @@ std::unique_ptr ContentManager::ResolvePackage( return package; } -bool ContentManager::ContentExists(const ContentData& data) { +bool ContentManager::ContentExists(const XCONTENT_DATA& data) { auto path = ResolvePackagePath(data); return std::filesystem::exists(path); } X_RESULT ContentManager::CreateContent(const std::string_view root_name, - const ContentData& data) { + const XCONTENT_DATA& data) { auto global_lock = global_critical_region_.Acquire(); if (open_packages_.count(string_key(root_name))) { @@ -168,7 +146,7 @@ X_RESULT ContentManager::CreateContent(const std::string_view root_name, } X_RESULT ContentManager::OpenContent(const std::string_view root_name, - const ContentData& data) { + const XCONTENT_DATA& data) { auto global_lock = global_critical_region_.Acquire(); if (open_packages_.count(string_key(root_name))) { @@ -207,7 +185,7 @@ X_RESULT ContentManager::CloseContent(const std::string_view root_name) { return X_ERROR_SUCCESS; } -X_RESULT ContentManager::GetContentThumbnail(const ContentData& data, +X_RESULT ContentManager::GetContentThumbnail(const XCONTENT_DATA& data, std::vector* buffer) { auto global_lock = global_critical_region_.Acquire(); auto package_path = ResolvePackagePath(data); @@ -226,7 +204,7 @@ X_RESULT ContentManager::GetContentThumbnail(const ContentData& data, } } -X_RESULT ContentManager::SetContentThumbnail(const ContentData& data, +X_RESULT ContentManager::SetContentThumbnail(const XCONTENT_DATA& data, std::vector buffer) { auto global_lock = global_critical_region_.Acquire(); auto package_path = ResolvePackagePath(data); @@ -242,7 +220,7 @@ X_RESULT ContentManager::SetContentThumbnail(const ContentData& data, } } -X_RESULT ContentManager::DeleteContent(const ContentData& data) { +X_RESULT ContentManager::DeleteContent(const XCONTENT_DATA& data) { auto global_lock = global_critical_region_.Acquire(); if (IsContentOpen(data)) { @@ -267,7 +245,7 @@ std::filesystem::path ContentManager::ResolveGameUserContentPath() { return root_path_ / title_id / kGameUserContentDirName / user_name; } -bool ContentManager::IsContentOpen(const ContentData& data) const { +bool ContentManager::IsContentOpen(const XCONTENT_DATA& data) const { return std::any_of(open_packages_.cbegin(), open_packages_.cend(), [data](std::pair content) { return data == content.second->GetPackageContentData(); diff --git a/src/xenia/kernel/xam/content_manager.h b/src/xenia/kernel/xam/content_manager.h index 5eef60b4f..f0ff9c5b9 100644 --- a/src/xenia/kernel/xam/content_manager.h +++ b/src/xenia/kernel/xam/content_manager.h @@ -33,105 +33,87 @@ namespace xam { struct XCONTENT_DATA { be device_id; - be content_type; + be content_type; union { - be display_name[128]; - char16_t display_name_chars[128]; - }; - char file_name[42]; - uint8_t padding[2]; -}; -static_assert_size(XCONTENT_DATA, 308); + // this should be be, but that stops copy constructor being + // generated... + uint16_t uint[128]; + char16_t chars[128]; + } display_name_raw; -struct XCONTENT_AGGREGATE_DATA { - be device_id; - be content_type; - union { - be display_name[128]; - char16_t display_name_chars[128]; - }; - char file_name[42]; + 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 (eg Goldeneye XBLA) 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 title_id; -}; -static_assert_size(XCONTENT_AGGREGATE_DATA, 312); -struct ContentData { - uint32_t device_id; - uint32_t content_type; - std::u16string display_name; - std::string file_name; - - ContentData() = default; - - bool operator==(const ContentData& rhs) const { - return device_id == rhs.device_id && content_type == rhs.content_type && - file_name == rhs.file_name; - } - - explicit ContentData(const XCONTENT_DATA& data) { - device_id = data.device_id; - content_type = data.content_type; - display_name = xe::load_and_swap(data.display_name); - file_name = xe::load_and_swap(data.file_name); - } - - void Write(XCONTENT_DATA* data) const { - data->device_id = device_id; - data->content_type = content_type; - xe::string_util::copy_and_swap_truncating( - data->display_name_chars, display_name, - xe::countof(data->display_name_chars)); - xe::string_util::copy_maybe_truncating< - string_util::Safety::IKnowWhatIAmDoing>(data->file_name, file_name, - xe::countof(data->file_name)); - } -}; - -struct ContentAggregateData { - uint32_t device_id; - uint32_t content_type; - std::u16string display_name; - std::string file_name; - uint32_t title_id; - - ContentAggregateData() = default; - - explicit ContentAggregateData(const XCONTENT_AGGREGATE_DATA& data) { - device_id = data.device_id; - content_type = data.content_type; - display_name = xe::load_and_swap(data.display_name); - file_name = xe::load_and_swap(data.file_name); - title_id = data.title_id; - } - - void Write(XCONTENT_AGGREGATE_DATA* data) const { - data->device_id = device_id; - data->content_type = content_type; - xe::string_util::copy_and_swap_truncating( - data->display_name_chars, display_name, - xe::countof(data->display_name_chars)); - xe::string_util::copy_maybe_truncating< - string_util::Safety::IKnowWhatIAmDoing>(data->file_name, file_name, - xe::countof(data->file_name)); - data->title_id = title_id; + 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, 0x138); class ContentPackage { public: ContentPackage(KernelState* kernel_state, const std::string_view root_name, - const ContentData& data, + const XCONTENT_DATA& data, const std::filesystem::path& package_path); ~ContentPackage(); - const ContentData& GetPackageContentData() const { return content_data_; } + const XCONTENT_DATA& GetPackageContentData() const { return content_data_; } private: KernelState* kernel_state_; std::string root_name_; std::string device_path_; - ContentData content_data_; + XCONTENT_DATA content_data_; }; class ContentManager { @@ -140,30 +122,30 @@ class ContentManager { const std::filesystem::path& root_path); ~ContentManager(); - std::vector ListContent(uint32_t device_id, - uint32_t content_type); + std::vector ListContent(uint32_t device_id, + XContentType content_type); std::unique_ptr ResolvePackage( - const std::string_view root_name, const ContentData& data); + const std::string_view root_name, const XCONTENT_DATA& data); - bool ContentExists(const ContentData& data); + bool ContentExists(const XCONTENT_DATA& data); X_RESULT CreateContent(const std::string_view root_name, - const ContentData& data); + const XCONTENT_DATA& data); X_RESULT OpenContent(const std::string_view root_name, - const ContentData& data); + const XCONTENT_DATA& data); X_RESULT CloseContent(const std::string_view root_name); - X_RESULT GetContentThumbnail(const ContentData& data, + X_RESULT GetContentThumbnail(const XCONTENT_DATA& data, std::vector* buffer); - X_RESULT SetContentThumbnail(const ContentData& data, + X_RESULT SetContentThumbnail(const XCONTENT_DATA& data, std::vector buffer); - X_RESULT DeleteContent(const ContentData& data); + X_RESULT DeleteContent(const XCONTENT_DATA& data); std::filesystem::path ResolveGameUserContentPath(); - bool IsContentOpen(const ContentData& data) const; + bool IsContentOpen(const XCONTENT_DATA& data) const; void CloseOpenedFilesFromContent(const std::string_view root_name); private: - std::filesystem::path ResolvePackageRoot(uint32_t content_type); - std::filesystem::path ResolvePackagePath(const ContentData& data); + std::filesystem::path ResolvePackageRoot(XContentType content_type); + std::filesystem::path ResolvePackagePath(const XCONTENT_DATA& data); KernelState* kernel_state_; std::filesystem::path root_path_; diff --git a/src/xenia/kernel/xam/xam_content.cc b/src/xenia/kernel/xam/xam_content.cc index 0f8a365e8..5ba1d99aa 100644 --- a/src/xenia/kernel/xam/xam_content.cc +++ b/src/xenia/kernel/xam/xam_content.cc @@ -96,11 +96,12 @@ dword_result_t XamContentCreateEnumerator(dword_t user_index, dword_t device_id, if (!device_info || device_info->device_id == DummyDeviceId::HDD) { // Get all content data. auto content_datas = kernel_state()->content_manager()->ListContent( - static_cast(DummyDeviceId::HDD), content_type); + static_cast(DummyDeviceId::HDD), + XContentType(uint32_t(content_type))); for (const auto& content_data : content_datas) { auto item = reinterpret_cast(e->AppendItem()); assert_not_null(item); - content_data.Write(item); + *item = content_data; } } @@ -123,8 +124,7 @@ dword_result_t XamContentCreateEx(dword_t user_index, lpstring_t root_name, dword_t cache_size, qword_t content_size, lpvoid_t overlapped_ptr) { X_RESULT result = X_ERROR_INVALID_PARAMETER; - auto content_data = - static_cast(*content_data_ptr.as()); + auto content_data = *content_data_ptr.as(); auto content_manager = kernel_state()->content_manager(); bool create = false; @@ -277,14 +277,13 @@ dword_result_t XamContentGetCreator(dword_t user_index, lpunknown_t overlapped_ptr) { auto result = X_ERROR_SUCCESS; - auto content_data = - static_cast(*content_data_ptr.as()); + auto content_data = *content_data_ptr.as(); bool content_exists = kernel_state()->content_manager()->ContentExists(content_data); if (content_exists) { - if (content_data.content_type == 1) { + if (content_data.content_type == XContentType::kSavedGame) { // User always creates saves. *is_creator_ptr = 1; if (creator_xuid_ptr) { @@ -316,8 +315,7 @@ dword_result_t XamContentGetThumbnail(dword_t user_index, lpunknown_t overlapped_ptr) { assert_not_null(buffer_size_ptr); uint32_t buffer_size = *buffer_size_ptr; - auto content_data = - static_cast(*content_data_ptr.as()); + auto content_data = *content_data_ptr.as(); // Get thumbnail (if it exists). std::vector buffer; @@ -353,8 +351,7 @@ dword_result_t XamContentSetThumbnail(dword_t user_index, lpvoid_t content_data_ptr, lpvoid_t buffer_ptr, dword_t buffer_size, lpunknown_t overlapped_ptr) { - auto content_data = - static_cast(*content_data_ptr.as()); + auto content_data = *content_data_ptr.as(); // Buffer is PNG data. auto buffer = std::vector((uint8_t*)buffer_ptr, @@ -373,8 +370,7 @@ DECLARE_XAM_EXPORT1(XamContentSetThumbnail, kContent, kImplemented); dword_result_t XamContentDelete(dword_t user_index, lpvoid_t content_data_ptr, lpunknown_t overlapped_ptr) { - auto content_data = - static_cast(*content_data_ptr.as()); + auto content_data = *content_data_ptr.as(); auto result = kernel_state()->content_manager()->DeleteContent(content_data); diff --git a/src/xenia/kernel/xam/xam_content_aggregate.cc b/src/xenia/kernel/xam/xam_content_aggregate.cc index 39f4bb2b5..b7b9ba90b 100644 --- a/src/xenia/kernel/xam/xam_content_aggregate.cc +++ b/src/xenia/kernel/xam/xam_content_aggregate.cc @@ -22,14 +22,15 @@ namespace xe { namespace kernel { namespace xam { -void AddODDContentTest(object_ref e, uint32_t content_type) { +void AddODDContentTest(object_ref e, + XContentType content_type) { auto root_entry = kernel_state()->file_system()->ResolvePath( "game:\\Content\\0000000000000000"); if (!root_entry) { return; } - auto content_type_path = fmt::format("{:08X}", content_type); + auto content_type_path = fmt::format("{:08X}", uint32_t(content_type)); xe::filesystem::WildcardEngine title_find_engine; title_find_engine.SetRule("????????"); @@ -62,14 +63,11 @@ void AddODDContentTest(object_ref e, uint32_t content_type) { auto item = reinterpret_cast(e->AppendItem()); assert_not_null(item); - ContentAggregateData content_aggregate_data = {}; - content_aggregate_data.device_id = - static_cast(DummyDeviceId::ODD); - content_aggregate_data.content_type = content_type; - content_aggregate_data.display_name = to_utf16(content_entry->name()); - content_aggregate_data.file_name = content_entry->name(); - content_aggregate_data.title_id = title_id; - content_aggregate_data.Write(item); + item->device_id = static_cast(DummyDeviceId::ODD); + item->content_type = content_type; + item->set_display_name(to_utf16(content_entry->name())); + item->set_file_name(content_entry->name()); + item->title_id = title_id; } } } @@ -98,25 +96,22 @@ dword_result_t XamContentAggregateCreateEnumerator(qword_t xuid, extra->magic = kXObjSignature; extra->handle = e->handle(); + auto content_type_enum = XContentType(uint32_t(content_type)); + if (!device_info || device_info->device_type == DeviceType::HDD) { // Get all content data. auto content_datas = kernel_state()->content_manager()->ListContent( - static_cast(DummyDeviceId::HDD), content_type); + static_cast(DummyDeviceId::HDD), content_type_enum); for (const auto& content_data : content_datas) { auto item = reinterpret_cast(e->AppendItem()); assert_not_null(item); - ContentAggregateData content_aggregate_data = {}; - content_aggregate_data.device_id = content_data.device_id; - content_aggregate_data.content_type = content_data.content_type; - content_aggregate_data.display_name = content_data.display_name; - content_aggregate_data.file_name = content_data.file_name; - content_aggregate_data.title_id = kernel_state()->title_id(); - content_aggregate_data.Write(item); + *item = {content_data}; + item->title_id = kernel_state()->title_id(); } } if (!device_info || device_info->device_type == DeviceType::ODD) { - AddODDContentTest(e, content_type); + AddODDContentTest(e, content_type_enum); } XELOGD("XamContentAggregateCreateEnumerator: added {} items to enumerator", diff --git a/src/xenia/vfs/devices/stfs_xbox.h b/src/xenia/vfs/devices/stfs_xbox.h index 9378f1a86..ce755ea9c 100644 --- a/src/xenia/vfs/devices/stfs_xbox.h +++ b/src/xenia/vfs/devices/stfs_xbox.h @@ -36,43 +36,6 @@ enum class XContentPackageType : uint32_t { kLive = 0x4C495645, }; -enum XContentType : uint32_t { - kSavedGame = 0x00000001, - kMarketplaceContent = 0x00000002, - kPublisher = 0x00000003, - kXbox360Title = 0x00001000, - kIptvPauseBuffer = 0x00002000, - kXNACommunity = 0x00003000, - kInstalledGame = 0x00004000, - kXboxTitle = 0x00005000, - kSocialTitle = 0x00006000, - kGamesOnDemand = 0x00007000, - kSUStoragePack = 0x00008000, - kAvatarItem = 0x00009000, - kProfile = 0x00010000, - kGamerPicture = 0x00020000, - kTheme = 0x00030000, - kCacheFile = 0x00040000, - kStorageDownload = 0x00050000, - kXboxSavedGame = 0x00060000, - kXboxDownload = 0x00070000, - kGameDemo = 0x00080000, - kVideo = 0x00090000, - kGameTitle = 0x000A0000, - kInstaller = 0x000B0000, - kGameTrailer = 0x000C0000, - kArcadeTitle = 0x000D0000, - kXNA = 0x000E0000, - kLicenseStore = 0x000F0000, - kMovie = 0x00100000, - kTV = 0x00200000, - kMusicVideo = 0x00300000, - kGameVideo = 0x00400000, - kPodcastVideo = 0x00500000, - kViralVideo = 0x00600000, - kCommunityGame = 0x02000000, -}; - enum class XContentVolumeType : uint32_t { kStfs = 0, kSvod = 1, diff --git a/src/xenia/xbox.h b/src/xenia/xbox.h index bdf6e3f38..d27d37661 100644 --- a/src/xenia/xbox.h +++ b/src/xenia/xbox.h @@ -344,6 +344,43 @@ enum class XLanguage : uint32_t { kMaxLanguages = 13 }; +enum class XContentType : uint32_t { + kSavedGame = 0x00000001, + kMarketplaceContent = 0x00000002, + kPublisher = 0x00000003, + kXbox360Title = 0x00001000, + kIptvPauseBuffer = 0x00002000, + kXNACommunity = 0x00003000, + kInstalledGame = 0x00004000, + kXboxTitle = 0x00005000, + kSocialTitle = 0x00006000, + kGamesOnDemand = 0x00007000, + kSUStoragePack = 0x00008000, + kAvatarItem = 0x00009000, + kProfile = 0x00010000, + kGamerPicture = 0x00020000, + kTheme = 0x00030000, + kCacheFile = 0x00040000, + kStorageDownload = 0x00050000, + kXboxSavedGame = 0x00060000, + kXboxDownload = 0x00070000, + kGameDemo = 0x00080000, + kVideo = 0x00090000, + kGameTitle = 0x000A0000, + kInstaller = 0x000B0000, + kGameTrailer = 0x000C0000, + kArcadeTitle = 0x000D0000, + kXNA = 0x000E0000, + kLicenseStore = 0x000F0000, + kMovie = 0x00100000, + kTV = 0x00200000, + kMusicVideo = 0x00300000, + kGameVideo = 0x00400000, + kPodcastVideo = 0x00500000, + kViralVideo = 0x00600000, + kCommunityGame = 0x02000000, +}; + } // namespace xe // clang-format on