From 3dc8130ed6970b30ee5c563e9c752f5127cb83b9 Mon Sep 17 00:00:00 2001 From: The-Little-Wolf <116989599+The-Little-Wolf@users.noreply.github.com> Date: Fri, 18 Jul 2025 16:04:44 -0700 Subject: [PATCH] [XAM/CONTENT] - Add Missing returns and functions - Add missing return values to XamContentCreateEx & XamContentCreateInternal - Add Struct of XCONTENT_DATA_INTERNAL - Properly implement XamProfileOpen - Stub XamContentGetDeviceVolumePath --- src/xenia/kernel/xam/apps/xam_app.cc | 2 + src/xenia/kernel/xam/content_manager.h | 69 +++++++++++++- src/xenia/kernel/xam/xam_content.cc | 123 +++++++++++++++---------- src/xenia/kernel/xam/xam_profile.cc | 21 +++-- 4 files changed, 152 insertions(+), 63 deletions(-) diff --git a/src/xenia/kernel/xam/apps/xam_app.cc b/src/xenia/kernel/xam/apps/xam_app.cc index b2309a013..7cf8a1276 100644 --- a/src/xenia/kernel/xam/apps/xam_app.cc +++ b/src/xenia/kernel/xam/apps/xam_app.cc @@ -68,6 +68,8 @@ X_HRESULT XamApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr, uint8_t* content_data_ptr = memory_->TranslateVirtual(data_ptr->buffer_ptr); + assert_true(data_ptr->buffer_size == sizeof(XCONTENT_AGGREGATE_DATA)); + std::memset(content_data_ptr, 0, data_ptr->buffer_size); uint32_t item_count = 0; diff --git a/src/xenia/kernel/xam/content_manager.h b/src/xenia/kernel/xam/content_manager.h index f617d8e52..6efccc71a 100644 --- a/src/xenia/kernel/xam/content_manager.h +++ b/src/xenia/kernel/xam/content_manager.h @@ -39,22 +39,22 @@ namespace xam { constexpr uint32_t kCurrentlyRunningTitleId = 0xFFFFFFFF; struct XCONTENT_DATA { - be device_id; - be content_type; + be device_id; // 0x0 sz:0x4 + be content_type; // 0x4 sz:0x4 union { // this should be be, but that stops copy constructor being // generated... uint16_t uint[128]; char16_t chars[128]; - } display_name_raw; + } display_name_raw; // 0x8 sz:0x100 - char file_name_raw[42]; + char file_name_raw[42]; // 0x108 sz:0x2A // Some games use this padding field as a null-terminator, as eg. // DLC packages usually fill the entire file_name_raw array // Not every game sets it to 0 though, so make sure any file_name_raw reads // only go up to 42 chars! - uint8_t padding[2]; + uint8_t padding[2]; // 0x132 sz: 0x2 bool operator==(const XCONTENT_DATA& other) const { // Package is located via device_id/content_type/file_name, so only need to @@ -119,6 +119,65 @@ struct XCONTENT_AGGREGATE_DATA : XCONTENT_DATA { }; static_assert_size(XCONTENT_AGGREGATE_DATA, 0x148); +struct XCONTENT_DATA_MEDIA { + be series_id[0x10]; // 0x0 sz:0x10 + be seasonid[0x10]; // 0x10 sz:0x10 + be season_number; // 0x20 sz:0x2 + be episode_number; // 0x22 sz:0x2 +}; +static_assert_size(XCONTENT_DATA_MEDIA, 0x24); + +struct XCONTENT_DATA_AVATAR_ASSET { + be subcategory; // 0x0 sz:0x4 + be colorizable; // 0x4 sz:0x4 + be asset_id[0x10]; // 0x8 sz:0x10 + be skeleton_version_mask; // 0x18 sz:0x1 +}; +static_assert_size(XCONTENT_DATA_AVATAR_ASSET, 0x1C); + +struct XCONTENT_DATA_INTERNAL : XCONTENT_DATA { // 0x0 sz:0x134 + be category; // 0x134 sz:0x4 + be xuid; // 0x138 sz:0x8 + be title_id; // 0x140 sz:0x4 + be license_mask; // 0x144 sz:0x4 + + // added in V4532 0x148 + be content_size; // 0x148 sz:0x8 + + // added in NXE + be creation_time; // 0x150 sz:0x8 FILETIME + char16_t title_name[0x40]; // 0x158 sz:0x80 + + // compiler does not like + // union { + // XCONTENT_DATA_AVATAR_ASSET avatar_content_data; // 0x1D8 sz:0x1C + // XCONTENT_DATA_MEDIA media_content_data; // 0x1D8 sz:0x24 + //}; + be padding[0x24]; // 0x1D8 sz:0x1C + be content_flag; // 0x1FC sz:0x01 + be reserved[3]; // 0x1FD sz:0x3 + + XCONTENT_DATA_INTERNAL() = default; + XCONTENT_DATA_INTERNAL(const XCONTENT_DATA& other) { + device_id = other.device_id; + content_type = other.content_type; + set_display_name(other.display_name()); + set_file_name(other.file_name()); + padding[0] = padding[1] = 0; + xuid = 0; + title_id = kCurrentlyRunningTitleId; + } + + bool operator==(const XCONTENT_DATA_INTERNAL& other) const { + // Package is located via device_id/title_id/content_type/file_name, so only + // need to compare those + return device_id == other.device_id && title_id == other.title_id && + content_type == other.content_type && + file_name() == other.file_name(); + } +}; +static_assert_size(XCONTENT_DATA_INTERNAL, 0x200); + class ContentPackage { public: ContentPackage(KernelState* kernel_state, const std::string_view root_name, diff --git a/src/xenia/kernel/xam/xam_content.cc b/src/xenia/kernel/xam/xam_content.cc index 6b8e24f43..cca1e4cc2 100644 --- a/src/xenia/kernel/xam/xam_content.cc +++ b/src/xenia/kernel/xam/xam_content.cc @@ -74,17 +74,15 @@ dword_result_t XamContentGetLicenseMask_entry(lpdword_t mask_ptr, } DECLARE_XAM_EXPORT2(XamContentGetLicenseMask, kContent, kStub, kHighFrequency); -dword_result_t XamContentResolve_entry(dword_t user_index, - lpvoid_t content_data_ptr, - lpvoid_t buffer_ptr, dword_t buffer_size, - dword_t unk1, lpdword_t root_name_ptr, - lpvoid_t overlapped_ptr) { - auto content_data = content_data_ptr.as(); +dword_result_t XamContentResolve_entry( + dword_t user_index, pointer_t content_data_ptr, + lpvoid_t buffer_ptr, dword_t buffer_size, dword_t create_directory, + lpdword_t root_name_ptr, pointer_t overlapped_ptr) { uint64_t xuid = 0; const auto profile = kernel_state()->xam_state()->profile_manager()->GetProfile( static_cast(user_index)); - if (profile && content_data->content_type == XContentType::kSavedGame) { + if (profile && content_data_ptr->content_type == XContentType::kSavedGame) { xuid = profile->xuid(); } @@ -96,9 +94,10 @@ dword_result_t XamContentResolve_entry(dword_t user_index, // Unsupported for now. return X_ERROR_INVALID_PARAMETER; } else { - if (content_data->device_id == static_cast(DummyDeviceId::HDD)) { + if (content_data_ptr->device_id == + static_cast(DummyDeviceId::HDD)) { root_device_path = "\\Device\\Harddisk0\\Partition1\\Content\\"; - } else if (content_data->device_id == + } else if (content_data_ptr->device_id == static_cast(DummyDeviceId::ODD)) { // Or GAME, but D: usually means DVD drive meanwhile GAME always pinpoints // to game, even if it is running from HDD @@ -110,8 +109,8 @@ dword_result_t XamContentResolve_entry(dword_t user_index, const std::string relative_path = fmt::format( "{:016X}\\{:08X}\\{:08X}\\{}", xuid, kernel_state()->title_id(), - static_cast(content_data->content_type.get()), - content_data->file_name()); + static_cast(content_data_ptr->content_type.get()), + content_data_ptr->file_name()); char* buffer = kernel_memory()->TranslateVirtual(buffer_ptr.guest_address()); @@ -229,7 +228,7 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name, lpdword_t disposition_ptr, lpdword_t license_mask_ptr, dword_t cache_size, qword_t content_size, - lpvoid_t overlapped_ptr) { + pointer_t overlapped_ptr) { uint64_t xuid = 0; if (user_index != XUserIndexNone) { const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index); @@ -241,6 +240,10 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name, xuid = user->xuid(); } + if (!root_name || *root_name == '\0') { + return X_ERROR_INVALID_NAME; + } + XCONTENT_AGGREGATE_DATA content_data; if (content_data_size == sizeof(XCONTENT_DATA)) { content_data = *content_data_ptr.as(); @@ -352,7 +355,12 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name, dword_result_t XamContentCreateEx_entry( dword_t user_index, lpstring_t root_name, lpvoid_t content_data_ptr, dword_t flags, lpdword_t disposition_ptr, lpdword_t license_mask_ptr, - dword_t cache_size, qword_t content_size, lpvoid_t overlapped_ptr) { + dword_t cache_size, qword_t content_size, + pointer_t overlapped_ptr) { + auto content_data = *content_data_ptr.as(); + if (content_data.file_name_raw[0] == '\0') { + return X_ERROR_INVALID_NAME; + } return xeXamContentCreate(user_index, root_name, content_data_ptr, sizeof(XCONTENT_DATA), flags, disposition_ptr, license_mask_ptr, cache_size, content_size, @@ -360,21 +368,24 @@ dword_result_t XamContentCreateEx_entry( } DECLARE_XAM_EXPORT1(XamContentCreateEx, kContent, kImplemented); -dword_result_t XamContentCreate_entry(dword_t user_index, lpstring_t root_name, - lpvoid_t content_data_ptr, dword_t flags, - lpdword_t disposition_ptr, - lpdword_t license_mask_ptr, - lpvoid_t overlapped_ptr) { - return xeXamContentCreate(user_index, root_name, content_data_ptr, - sizeof(XCONTENT_DATA), flags, disposition_ptr, - license_mask_ptr, 0, 0, overlapped_ptr); +dword_result_t XamContentCreate_entry( + dword_t user_index, lpstring_t root_name, lpvoid_t content_data_ptr, + dword_t flags, lpdword_t disposition_ptr, lpdword_t license_mask_ptr, + pointer_t overlapped_ptr) { + return XamContentCreateEx_entry(user_index, root_name, content_data_ptr, + flags, disposition_ptr, license_mask_ptr, 0, + 0, overlapped_ptr); } DECLARE_XAM_EXPORT1(XamContentCreate, kContent, kImplemented); dword_result_t XamContentCreateInternal_entry( lpstring_t root_name, lpvoid_t content_data_ptr, dword_t flags, lpdword_t disposition_ptr, lpdword_t license_mask_ptr, dword_t cache_size, - qword_t content_size, lpvoid_t overlapped_ptr) { + qword_t content_size, pointer_t overlapped_ptr) { + auto content_data_internal = *content_data_ptr.as(); + if (content_data_internal.file_name_raw[0] == '\0') { + return X_ERROR_INVALID_NAME; + } return xeXamContentCreate(XUserIndexNone, root_name, content_data_ptr, sizeof(XCONTENT_AGGREGATE_DATA), flags, disposition_ptr, license_mask_ptr, cache_size, @@ -382,19 +393,17 @@ dword_result_t XamContentCreateInternal_entry( } DECLARE_XAM_EXPORT1(XamContentCreateInternal, kContent, kImplemented); -dword_result_t XamContentOpenFile_entry(dword_t user_index, - lpstring_t root_name, lpstring_t path, - dword_t flags, - lpdword_t disposition_ptr, - lpdword_t license_mask_ptr, - lpvoid_t overlapped_ptr) { +dword_result_t XamContentOpenFile_entry( + dword_t user_index, lpstring_t root_name, lpstring_t path, dword_t flags, + lpdword_t disposition_ptr, lpdword_t license_mask_ptr, + pointer_t overlapped_ptr) { // TODO(gibbed): arguments assumed based on XamContentCreate. return X_ERROR_FILE_NOT_FOUND; } DECLARE_XAM_EXPORT1(XamContentOpenFile, kContent, kStub); dword_result_t XamContentFlush_entry(lpstring_t root_name, - lpvoid_t overlapped_ptr) { + pointer_t overlapped_ptr) { X_RESULT result = X_ERROR_SUCCESS; if (overlapped_ptr) { kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result); @@ -406,7 +415,7 @@ dword_result_t XamContentFlush_entry(lpstring_t root_name, DECLARE_XAM_EXPORT1(XamContentFlush, kContent, kStub); dword_result_t XamContentClose_entry(lpstring_t root_name, - lpvoid_t overlapped_ptr) { + pointer_t overlapped_ptr) { // Closes a previously opened root from XamContentCreate*. auto result = kernel_state()->content_manager()->CloseContent(root_name.value()); @@ -420,11 +429,9 @@ dword_result_t XamContentClose_entry(lpstring_t root_name, } DECLARE_XAM_EXPORT1(XamContentClose, kContent, kImplemented); -dword_result_t XamContentGetCreator_entry(dword_t user_index, - lpvoid_t content_data_ptr, - lpdword_t is_creator_ptr, - lpqword_t creator_xuid_ptr, - lpvoid_t overlapped_ptr) { +dword_result_t XamContentGetCreator_entry( + dword_t user_index, lpvoid_t content_data_ptr, lpdword_t is_creator_ptr, + lpqword_t creator_xuid_ptr, pointer_t overlapped_ptr) { if (!is_creator_ptr) { return X_ERROR_INVALID_PARAMETER; } @@ -488,11 +495,9 @@ dword_result_t XamContentGetCreator_entry(dword_t user_index, } DECLARE_XAM_EXPORT1(XamContentGetCreator, kContent, kImplemented); -dword_result_t XamContentGetThumbnail_entry(dword_t user_index, - lpvoid_t content_data_ptr, - lpvoid_t buffer_ptr, - lpdword_t buffer_size_ptr, - lpvoid_t overlapped_ptr) { +dword_result_t XamContentGetThumbnail_entry( + dword_t user_index, lpvoid_t content_data_ptr, lpvoid_t buffer_ptr, + lpdword_t buffer_size_ptr, pointer_t overlapped_ptr) { const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index); if (!user) { @@ -533,11 +538,9 @@ dword_result_t XamContentGetThumbnail_entry(dword_t user_index, } DECLARE_XAM_EXPORT1(XamContentGetThumbnail, kContent, kImplemented); -dword_result_t XamContentSetThumbnail_entry(dword_t user_index, - lpvoid_t content_data_ptr, - lpvoid_t buffer_ptr, - dword_t buffer_size, - lpvoid_t overlapped_ptr) { +dword_result_t XamContentSetThumbnail_entry( + dword_t user_index, lpvoid_t content_data_ptr, lpvoid_t buffer_ptr, + dword_t buffer_size, pointer_t overlapped_ptr) { const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index); if (!user) { @@ -563,7 +566,7 @@ DECLARE_XAM_EXPORT1(XamContentSetThumbnail, kContent, kImplemented); dword_result_t xeXamContentDelete(dword_t user_index, lpvoid_t content_data_ptr, dword_t content_data_size, - lpvoid_t overlapped_ptr) { + pointer_t overlapped_ptr) { uint64_t xuid = 0; XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as(); if (content_data_size == sizeof(XCONTENT_AGGREGATE_DATA)) { @@ -591,16 +594,16 @@ dword_result_t xeXamContentDelete(dword_t user_index, lpvoid_t content_data_ptr, } } -dword_result_t XamContentDelete_entry(dword_t user_index, - lpvoid_t content_data_ptr, - lpvoid_t overlapped_ptr) { +dword_result_t XamContentDelete_entry( + dword_t user_index, lpvoid_t content_data_ptr, + pointer_t overlapped_ptr) { return xeXamContentDelete(user_index, content_data_ptr, sizeof(XCONTENT_DATA), overlapped_ptr); } DECLARE_XAM_EXPORT1(XamContentDelete, kContent, kImplemented); -dword_result_t XamContentDeleteInternal_entry(lpvoid_t content_data_ptr, - lpvoid_t overlapped_ptr) { +dword_result_t XamContentDeleteInternal_entry( + lpvoid_t content_data_ptr, pointer_t overlapped_ptr) { // INFO: Analysis of xam.xex shows that "internal" functions are wrappers with // 0xFE as user_index. // In XAM content size is set to 0x200. @@ -790,6 +793,24 @@ dword_result_t XamContentLaunchImageInternal_entry(lpvoid_t content_data_ptr, } DECLARE_XAM_EXPORT1(XamContentLaunchImageInternal, kContent, kStub); + +dword_result_t XamContentGetDeviceVolumePath_entry(dword_t device_id, + lpvoid_t path_ptr, + dword_t path_size, + dword_t append_backslash) { + std::string volume_path = "hdd0\\"; + if (device_id != static_cast(DummyDeviceId::HDD)) { + return X_ERROR_FUNCTION_FAILED; + } + + char* path = + kernel_memory()->TranslateVirtual(path_ptr.guest_address()); + + string_util::copy_truncating(path, volume_path, path_size); + + return X_ERROR_SUCCESS; +} +DECLARE_XAM_EXPORT1(XamContentGetDeviceVolumePath, kContent, kStub); } // namespace xam } // namespace kernel } // namespace xe diff --git a/src/xenia/kernel/xam/xam_profile.cc b/src/xenia/kernel/xam/xam_profile.cc index 7c7dae026..8cc002f3b 100644 --- a/src/xenia/kernel/xam/xam_profile.cc +++ b/src/xenia/kernel/xam/xam_profile.cc @@ -44,16 +44,23 @@ dword_result_t XamProfileFindAccount_entry( } DECLARE_XAM_EXPORT1(XamProfileFindAccount, kUserProfiles, kImplemented); -dword_result_t XamProfileOpen_entry(qword_t xuid, lpstring_t mount_path, - dword_t flags, lpvoid_t content_data) { +dword_result_t XamProfileOpen_entry( + qword_t xuid, lpstring_t mount_path, dword_t flags, + pointer_t content_data_ptr) { /* Notes: - If xuid is not local then returns X_ERROR_INVALID_PARAMETER */ - const bool result = - kernel_state()->xam_state()->profile_manager()->MountProfile( - xuid, mount_path.value()); - - return result ? X_ERROR_SUCCESS : X_ERROR_INVALID_PARAMETER; + if (!kernel_state()->xam_state()->profile_manager()->MountProfile( + xuid, mount_path.value())) { + return X_ERROR_INVALID_PARAMETER; + } + if (content_data_ptr) { + content_data_ptr->content_type = XContentType::kProfile; + content_data_ptr->device_id = 1; + content_data_ptr->set_file_name(xe::string_util::to_hex_string(xuid)); + content_data_ptr->title_id = kDashboardID; + } + return X_ERROR_SUCCESS; } DECLARE_XAM_EXPORT1(XamProfileOpen, kNone, kImplemented);