diff --git a/src/xenia/kernel/xam/user_profile.h b/src/xenia/kernel/xam/user_profile.h index 82bb7b7a4..ad24b003d 100644 --- a/src/xenia/kernel/xam/user_profile.h +++ b/src/xenia/kernel/xam/user_profile.h @@ -2,7 +2,7 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2020 Ben Vanik. All rights reserved. * + * Copyright 2022 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ @@ -15,14 +15,69 @@ #include #include +#include "xenia/base/byte_stream.h" #include "xenia/xbox.h" namespace xe { namespace kernel { namespace xam { +struct X_USER_PROFILE_SETTING_DATA { + // UserProfile::Setting::Type. Appears to be 8-in-32 field, and the upper 24 + // are not always zeroed by the game. + uint8_t type; + uint8_t unk_1[3]; + xe::be unk_4; + // TODO(sabretooth): not sure if this is a union, but it seems likely. + // Haven't run into cases other than "binary data" yet. + union { + xe::be s32; + xe::be s64; + xe::be u32; + xe::be f64; + struct { + xe::be size; + xe::be ptr; + } unicode; + xe::be f32; + struct { + xe::be size; + xe::be ptr; + } binary; + xe::be filetime; + }; +}; +static_assert_size(X_USER_PROFILE_SETTING_DATA, 16); + +struct X_USER_PROFILE_SETTING { + xe::be from; + xe::be unk04; + union { + xe::be user_index; + xe::be xuid; + }; + xe::be setting_id; + xe::be unk14; + union { + uint8_t data_bytes[sizeof(X_USER_PROFILE_SETTING_DATA)]; + X_USER_PROFILE_SETTING_DATA data; + }; +}; +static_assert_size(X_USER_PROFILE_SETTING, 40); + class UserProfile { public: + class SettingByteStream : public ByteStream { + public: + SettingByteStream(uint32_t ptr, uint8_t* data, size_t data_length, + size_t offset = 0) + : ByteStream(data, data_length, offset), ptr_(ptr) {} + + uint32_t ptr() const { return static_cast(ptr_ + offset()); } + + private: + uint32_t ptr_; + }; struct Setting { enum class Type { CONTENT = 0, @@ -33,7 +88,7 @@ class UserProfile { FLOAT = 5, BINARY = 6, DATETIME = 7, - INVALID = 0xFF, + UNSET = 0xFF, }; union Key { uint32_t value; @@ -55,97 +110,77 @@ class UserProfile { size(size), is_set(is_set), loaded_title_id(0) {} - virtual size_t extra_size() const { return 0; } - virtual size_t Append(uint8_t* user_data, uint8_t* buffer, - uint32_t buffer_ptr, size_t buffer_offset) { - xe::store_and_swap(user_data + kTypeOffset, - static_cast(type)); - return buffer_offset; + virtual void Append(X_USER_PROFILE_SETTING_DATA* data, + SettingByteStream* stream) { + data->type = static_cast(type); } virtual std::vector Serialize() const { return std::vector(); } virtual void Deserialize(std::vector) {} bool is_title_specific() const { return (setting_id & 0x3F00) == 0x3F00; } - - protected: - const size_t kTypeOffset = 0; - const size_t kValueOffset = 8; - const size_t kPointerOffset = 12; }; struct Int32Setting : public Setting { Int32Setting(uint32_t setting_id, int32_t value) : Setting(setting_id, Type::INT32, 4, true), value(value) {} int32_t value; - size_t Append(uint8_t* user_data, uint8_t* buffer, uint32_t buffer_ptr, - size_t buffer_offset) override { - buffer_offset = - Setting::Append(user_data, buffer, buffer_ptr, buffer_offset); - xe::store_and_swap(user_data + kValueOffset, value); - return buffer_offset; + void Append(X_USER_PROFILE_SETTING_DATA* data, + SettingByteStream* stream) override { + Setting::Append(data, stream); + data->s32 = value; } }; struct Int64Setting : public Setting { Int64Setting(uint32_t setting_id, int64_t value) : Setting(setting_id, Type::INT64, 8, true), value(value) {} int64_t value; - size_t Append(uint8_t* user_data, uint8_t* buffer, uint32_t buffer_ptr, - size_t buffer_offset) override { - buffer_offset = - Setting::Append(user_data, buffer, buffer_ptr, buffer_offset); - xe::store_and_swap(user_data + kValueOffset, value); - return buffer_offset; + void Append(X_USER_PROFILE_SETTING_DATA* data, + SettingByteStream* stream) override { + Setting::Append(data, stream); + data->s64 = value; } }; struct DoubleSetting : public Setting { DoubleSetting(uint32_t setting_id, double value) : Setting(setting_id, Type::DOUBLE, 8, true), value(value) {} double value; - size_t Append(uint8_t* user_data, uint8_t* buffer, uint32_t buffer_ptr, - size_t buffer_offset) override { - buffer_offset = - Setting::Append(user_data, buffer, buffer_ptr, buffer_offset); - xe::store_and_swap(user_data + kValueOffset, value); - return buffer_offset; + void Append(X_USER_PROFILE_SETTING_DATA* data, + SettingByteStream* stream) override { + Setting::Append(data, stream); + data->f64 = value; } }; struct UnicodeSetting : public Setting { UnicodeSetting(uint32_t setting_id, const std::u16string& value) : Setting(setting_id, Type::WSTRING, 8, true), value(value) {} std::u16string value; - size_t extra_size() const override { - return value.empty() ? 0 : 2 * (static_cast(value.size()) + 1); - } - size_t Append(uint8_t* user_data, uint8_t* buffer, uint32_t buffer_ptr, - size_t buffer_offset) override { - buffer_offset = - Setting::Append(user_data, buffer, buffer_ptr, buffer_offset); - int32_t length; + void Append(X_USER_PROFILE_SETTING_DATA* data, + SettingByteStream* stream) override { + Setting::Append(data, stream); if (value.empty()) { - length = 0; - xe::store_and_swap(user_data + kValueOffset, 0); - xe::store_and_swap(user_data + kPointerOffset, 0); + data->unicode.size = 0; + data->unicode.ptr = 0; } else { - length = 2 * (static_cast(value.size()) + 1); - xe::store_and_swap(user_data + kValueOffset, length); - xe::store_and_swap( - user_data + kPointerOffset, - buffer_ptr + static_cast(buffer_offset)); - memcpy(buffer + buffer_offset, value.data(), length); + size_t count = value.size() + 1; + size_t size = 2 * count; + assert_true(size <= std::numeric_limits::max()); + data->unicode.size = static_cast(size); + data->unicode.ptr = stream->ptr(); + auto buffer = + reinterpret_cast(&stream->data()[stream->offset()]); + stream->Advance(size); + xe::copy_and_swap(buffer, (uint16_t*)value.data(), count); } - return buffer_offset + length; } }; struct FloatSetting : public Setting { FloatSetting(uint32_t setting_id, float value) : Setting(setting_id, Type::FLOAT, 4, true), value(value) {} float value; - size_t Append(uint8_t* user_data, uint8_t* buffer, uint32_t buffer_ptr, - size_t buffer_offset) override { - buffer_offset = - Setting::Append(user_data, buffer, buffer_ptr, buffer_offset); - xe::store_and_swap(user_data + kValueOffset, value); - return buffer_offset; + void Append(X_USER_PROFILE_SETTING_DATA* data, + SettingByteStream* stream) override { + Setting::Append(data, stream); + data->f32 = value; } }; struct BinarySetting : public Setting { @@ -154,27 +189,19 @@ class UserProfile { BinarySetting(uint32_t setting_id, const std::vector& value) : Setting(setting_id, Type::BINARY, 8, true), value(value) {} std::vector value; - size_t extra_size() const override { - return static_cast(value.size()); - } - size_t Append(uint8_t* user_data, uint8_t* buffer, uint32_t buffer_ptr, - size_t buffer_offset) override { - buffer_offset = - Setting::Append(user_data, buffer, buffer_ptr, buffer_offset); - int32_t length; + void Append(X_USER_PROFILE_SETTING_DATA* data, + SettingByteStream* stream) override { + Setting::Append(data, stream); if (value.empty()) { - length = 0; - xe::store_and_swap(user_data + kValueOffset, 0); - xe::store_and_swap(user_data + kPointerOffset, 0); + data->binary.size = 0; + data->binary.ptr = 0; } else { - length = static_cast(value.size()); - xe::store_and_swap(user_data + kValueOffset, length); - xe::store_and_swap( - user_data + kPointerOffset, - buffer_ptr + static_cast(buffer_offset)); - memcpy(buffer + buffer_offset, value.data(), length); + size_t size = value.size(); + assert_true(size <= std::numeric_limits::max()); + data->binary.size = static_cast(size); + data->binary.ptr = stream->ptr(); + stream->Write(value.data(), size); } - return buffer_offset + length; } std::vector Serialize() const override { return std::vector(value.data(), value.data() + value.size()); @@ -188,12 +215,10 @@ class UserProfile { DateTimeSetting(uint32_t setting_id, int64_t value) : Setting(setting_id, Type::DATETIME, 8, true), value(value) {} int64_t value; - size_t Append(uint8_t* user_data, uint8_t* buffer, uint32_t buffer_ptr, - size_t buffer_offset) override { - buffer_offset = - Setting::Append(user_data, buffer, buffer_ptr, buffer_offset); - xe::store_and_swap(user_data + kValueOffset, value); - return buffer_offset; + void Append(X_USER_PROFILE_SETTING_DATA* data, + SettingByteStream* stream) override { + Setting::Append(data, stream); + data->filetime = value; } }; diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc index 625b977cd..2155504e8 100644 --- a/src/xenia/kernel/xam/xam_user.cc +++ b/src/xenia/kernel/xam/xam_user.cc @@ -14,6 +14,7 @@ #include "xenia/base/string_util.h" #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/util/shim_utils.h" +#include "xenia/kernel/xam/user_profile.h" #include "xenia/kernel/xam/xam_private.h" #include "xenia/kernel/xenumerator.h" #include "xenia/kernel/xthread.h" @@ -146,25 +147,14 @@ typedef struct { } X_USER_READ_PROFILE_SETTINGS; static_assert_size(X_USER_READ_PROFILE_SETTINGS, 8); -typedef struct { - xe::be from; - xe::be unk04; - xe::be user_index; - xe::be unk0C; - xe::be setting_id; - xe::be unk14; - uint8_t setting_data[16]; -} X_USER_READ_PROFILE_SETTING; -static_assert_size(X_USER_READ_PROFILE_SETTING, 40); - // https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/Generic/xboxtools.cpp -uint32_t xeXamUserReadProfileSettingsEx(uint32_t title_id, uint32_t user_index, - uint32_t xuid_count, lpqword_t xuids, - uint32_t setting_count, - lpdword_t setting_ids, uint32_t unk, - lpdword_t buffer_size_ptr, - lpvoid_t buffer_ptr, - uint32_t overlapped_ptr) { +uint32_t XamUserReadProfileSettingsEx(uint32_t title_id, uint32_t user_index, + uint32_t xuid_count, be* xuids, + uint32_t setting_count, + be* setting_ids, uint32_t unk, + be* buffer_size_ptr, + uint8_t* buffer, + XAM_OVERLAPPED* overlapped) { if (!xuid_count) { assert_null(xuids); } else { @@ -173,8 +163,11 @@ uint32_t xeXamUserReadProfileSettingsEx(uint32_t title_id, uint32_t user_index, // TODO(gibbed): allow proper lookup of arbitrary XUIDs const auto& user_profile = kernel_state()->user_profile(); assert_true(static_cast(xuids[0]) == user_profile->xuid()); + // TODO(gibbed): we assert here, but in case a title passes xuid_count > 1 + // until it's implemented for release builds... + xuid_count = 1; } - assert_zero(unk); + assert_zero(unk); // probably flags // must have at least 1 to 32 settings if (setting_count < 1 || setting_count > 32) { @@ -188,35 +181,33 @@ uint32_t xeXamUserReadProfileSettingsEx(uint32_t title_id, uint32_t user_index, // if buffer size is non-zero, buffer pointer must be valid auto buffer_size = static_cast(*buffer_size_ptr); - if (buffer_size && !buffer_ptr) { + if (buffer_size && !buffer) { return X_ERROR_INVALID_PARAMETER; } uint32_t needed_header_size = 0; - uint32_t needed_extra_size = 0; + uint32_t needed_data_size = 0; for (uint32_t i = 0; i < setting_count; ++i) { - needed_header_size += sizeof(X_USER_READ_PROFILE_SETTING); + needed_header_size += sizeof(X_USER_PROFILE_SETTING); UserProfile::Setting::Key setting_key; setting_key.value = static_cast(setting_ids[i]); switch (static_cast(setting_key.type)) { case UserProfile::Setting::Type::WSTRING: - case UserProfile::Setting::Type::BINARY: { - needed_extra_size += setting_key.size; + case UserProfile::Setting::Type::BINARY: + needed_data_size += setting_key.size; break; - } - default: { + default: break; - } } } if (xuids) { - // needed_header_size *= xuid_count; - // needed_extra_size *= !xuid_count; + needed_header_size *= xuid_count; + needed_data_size *= xuid_count; } needed_header_size += sizeof(X_USER_READ_PROFILE_SETTINGS); - uint32_t needed_size = needed_header_size + needed_extra_size; - if (!buffer_ptr || buffer_size < needed_size) { + uint32_t needed_size = needed_header_size + needed_data_size; + if (!buffer || buffer_size < needed_size) { if (!buffer_size) { *buffer_size_ptr = needed_size; } @@ -226,11 +217,12 @@ uint32_t xeXamUserReadProfileSettingsEx(uint32_t title_id, uint32_t user_index, // Title ID = 0 means us. // 0xfffe07d1 = profile? - if (user_index) { + if (!xuids && user_index) { // Only support user 0. - if (overlapped_ptr) { - kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, - X_ERROR_NO_SUCH_USER); + if (overlapped) { + kernel_state()->CompleteOverlappedImmediate( + kernel_state()->memory()->HostToGuestVirtual(overlapped), + X_ERROR_NO_SUCH_USER); return X_ERROR_IO_PENDING; } return X_ERROR_NO_SUCH_USER; @@ -257,49 +249,49 @@ uint32_t xeXamUserReadProfileSettingsEx(uint32_t title_id, uint32_t user_index, } if (any_missing) { // TODO(benvanik): don't fail? most games don't even check! - if (overlapped_ptr) { - kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, - X_ERROR_INVALID_PARAMETER); + if (overlapped) { + kernel_state()->CompleteOverlappedImmediate( + kernel_state()->memory()->HostToGuestVirtual(overlapped), + X_ERROR_INVALID_PARAMETER); return X_ERROR_IO_PENDING; } return X_ERROR_INVALID_PARAMETER; } - auto out_header = buffer_ptr.as(); + auto out_header = reinterpret_cast(buffer); + auto out_setting = reinterpret_cast(&out_header[1]); out_header->setting_count = static_cast(setting_count); - out_header->settings_ptr = buffer_ptr.guest_address() + 8; + out_header->settings_ptr = + kernel_state()->memory()->HostToGuestVirtual(out_setting); - auto out_setting = - reinterpret_cast(&out_header[1]); - - size_t buffer_offset = needed_header_size; + UserProfile::SettingByteStream out_stream( + kernel_state()->memory()->HostToGuestVirtual(buffer), buffer, buffer_size, + needed_header_size); for (uint32_t n = 0; n < setting_count; ++n) { uint32_t setting_id = setting_ids[n]; auto setting = user_profile->GetSetting(setting_id); - std::memset(out_setting, 0, sizeof(X_USER_READ_PROFILE_SETTING)); + std::memset(out_setting, 0, sizeof(X_USER_PROFILE_SETTING)); out_setting->from = !setting || !setting->is_set ? 0 : setting->is_title_specific() ? 2 : 1; - out_setting->user_index = static_cast(user_index); + if (xuids) { + out_setting->xuid = user_profile->xuid(); + } else { + out_setting->user_index = static_cast(user_index); + } out_setting->setting_id = setting_id; if (setting && setting->is_set) { - buffer_offset = - setting->Append(&out_setting->setting_data[0], buffer_ptr, - buffer_ptr.guest_address(), buffer_offset); + setting->Append(&out_setting->data, &out_stream); } - // TODO(benvanik): why did I do this? - /*else { - std::memset(&out_setting->setting_data[0], 0, - sizeof(out_setting->setting_data)); - }*/ ++out_setting; } - if (overlapped_ptr) { - kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, - X_ERROR_SUCCESS); + if (overlapped) { + kernel_state()->CompleteOverlappedImmediate( + kernel_state()->memory()->HostToGuestVirtual(overlapped), + X_ERROR_SUCCESS); return X_ERROR_IO_PENDING; } return X_ERROR_SUCCESS; @@ -308,57 +300,35 @@ uint32_t xeXamUserReadProfileSettingsEx(uint32_t title_id, uint32_t user_index, dword_result_t XamUserReadProfileSettings_entry( dword_t title_id, dword_t user_index, dword_t xuid_count, lpqword_t xuids, dword_t setting_count, lpdword_t setting_ids, lpdword_t buffer_size_ptr, - lpvoid_t buffer_ptr, dword_t overlapped_ptr) { - return xeXamUserReadProfileSettingsEx( - title_id, user_index, xuid_count, xuids, setting_count, setting_ids, 0, - buffer_size_ptr, buffer_ptr, overlapped_ptr); + lpvoid_t buffer_ptr, pointer_t overlapped) { + return XamUserReadProfileSettingsEx(title_id, user_index, xuid_count, xuids, + setting_count, setting_ids, 0, + buffer_size_ptr, buffer_ptr, overlapped); } DECLARE_XAM_EXPORT1(XamUserReadProfileSettings, kUserProfiles, kImplemented); dword_result_t XamUserReadProfileSettingsEx_entry( dword_t title_id, dword_t user_index, dword_t xuid_count, lpqword_t xuids, dword_t setting_count, lpdword_t setting_ids, lpdword_t buffer_size_ptr, - dword_t unk_2, lpvoid_t buffer_ptr, dword_t overlapped_ptr) { - return xeXamUserReadProfileSettingsEx( - title_id, user_index, xuid_count, xuids, setting_count, setting_ids, - unk_2, buffer_size_ptr, buffer_ptr, overlapped_ptr); + dword_t unk_2, lpvoid_t buffer_ptr, pointer_t overlapped) { + return XamUserReadProfileSettingsEx(title_id, user_index, xuid_count, xuids, + setting_count, setting_ids, unk_2, + buffer_size_ptr, buffer_ptr, overlapped); } DECLARE_XAM_EXPORT1(XamUserReadProfileSettingsEx, kUserProfiles, kImplemented); -typedef struct { - xe::be from; - xe::be unk_04; - xe::be unk_08; - xe::be unk_0c; - xe::be setting_id; - xe::be unk_14; - - // UserProfile::Setting::Type. Appears to be 8-in-32 field, and the upper 24 - // are not always zeroed by the game. - uint8_t type; - - xe::be unk_1c; - // TODO(sabretooth): not sure if this is a union, but it seems likely. - // Haven't run into cases other than "binary data" yet. - union { - struct { - xe::be length; - xe::be ptr; - } binary; - }; -} X_USER_WRITE_PROFILE_SETTING; - dword_result_t XamUserWriteProfileSettings_entry( dword_t title_id, dword_t user_index, dword_t setting_count, - pointer_t settings, dword_t overlapped_ptr) { + pointer_t settings, + pointer_t overlapped) { if (!setting_count || !settings) { return X_ERROR_INVALID_PARAMETER; } if (user_index) { // Only support user 0. - if (overlapped_ptr) { - kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, + if (overlapped) { + kernel_state()->CompleteOverlappedImmediate(overlapped, X_ERROR_NO_SUCH_USER); return X_ERROR_IO_PENDING; } @@ -369,39 +339,39 @@ dword_result_t XamUserWriteProfileSettings_entry( const auto& user_profile = kernel_state()->user_profile(); for (uint32_t n = 0; n < setting_count; ++n) { - const X_USER_WRITE_PROFILE_SETTING& settings_data = settings[n]; + const X_USER_PROFILE_SETTING& setting = settings[n]; + + auto setting_type = + static_cast(setting.data.type); + if (setting_type == UserProfile::Setting::Type::UNSET) { + continue; + } + XELOGD( "XamUserWriteProfileSettings: setting index [{}]:" " from={} setting_id={:08X} data.type={}", - n, (uint32_t)settings_data.from, (uint32_t)settings_data.setting_id, - settings_data.type); + n, (uint32_t)setting.from, (uint32_t)setting.setting_id, + setting.data.type); - xam::UserProfile::Setting::Type settingType = - static_cast(settings_data.type); - - switch (settingType) { + switch (setting_type) { case UserProfile::Setting::Type::CONTENT: case UserProfile::Setting::Type::BINARY: { - uint8_t* settings_data_ptr = kernel_state()->memory()->TranslateVirtual( - settings_data.binary.ptr); - size_t settings_data_len = settings_data.binary.length; - std::vector data_vec; - - if (settings_data.binary.ptr) { + uint8_t* binary_ptr = + kernel_state()->memory()->TranslateVirtual(setting.data.binary.ptr); + size_t binary_size = setting.data.binary.size; + std::vector bytes; + if (setting.data.binary.ptr) { // Copy provided data - data_vec.resize(settings_data_len); - std::memcpy(data_vec.data(), settings_data_ptr, settings_data_len); + bytes.resize(binary_size); + std::memcpy(bytes.data(), binary_ptr, binary_size); } else { // Data pointer was NULL, so just fill with zeroes - data_vec.resize(settings_data_len, 0); + bytes.resize(binary_size, 0); } - user_profile->AddSetting( std::make_unique( - settings_data.setting_id, data_vec)); - + setting.setting_id, bytes)); } break; - case UserProfile::Setting::Type::WSTRING: case UserProfile::Setting::Type::DOUBLE: case UserProfile::Setting::Type::FLOAT: @@ -410,14 +380,13 @@ dword_result_t XamUserWriteProfileSettings_entry( case UserProfile::Setting::Type::DATETIME: default: { XELOGE("XamUserWriteProfileSettings: Unimplemented data type {}", - settingType); + setting_type); } break; }; } - if (overlapped_ptr) { - kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, - X_ERROR_SUCCESS); + if (overlapped) { + kernel_state()->CompleteOverlappedImmediate(overlapped, X_ERROR_SUCCESS); return X_ERROR_IO_PENDING; } return X_ERROR_SUCCESS;