From 17b30be56aa4611db40477e5ee02ec90aa021aad Mon Sep 17 00:00:00 2001 From: Gliniak Date: Tue, 4 Jan 2022 17:44:58 +0100 Subject: [PATCH] Added support for local multiplayer --- src/xenia/hid/input_system.cc | 20 +++++ src/xenia/hid/input_system.h | 5 ++ src/xenia/kernel/kernel_state.cc | 25 +++++- src/xenia/kernel/kernel_state.h | 14 +++- src/xenia/kernel/xam/content_manager.cc | 2 +- src/xenia/kernel/xam/user_profile.cc | 7 +- src/xenia/kernel/xam/user_profile.h | 2 +- src/xenia/kernel/xam/xam_content.cc | 7 +- src/xenia/kernel/xam/xam_user.cc | 106 +++++++++++++----------- 9 files changed, 132 insertions(+), 56 deletions(-) diff --git a/src/xenia/hid/input_system.cc b/src/xenia/hid/input_system.cc index da58e00e1..06fc137b0 100644 --- a/src/xenia/hid/input_system.cc +++ b/src/xenia/hid/input_system.cc @@ -26,6 +26,18 @@ void InputSystem::AddDriver(std::unique_ptr driver) { drivers_.push_back(std::move(driver)); } +void InputSystem::UpdateUsedSlot(uint8_t slot, bool connected) { + if (slot == 0xFF) { + slot = 0; + } + + if (connected) { + connected_slot |= (1 << slot); + } else { + connected_slot &= ~(1 << slot); + } +} + X_RESULT InputSystem::GetCapabilities(uint32_t user_index, uint32_t flags, X_INPUT_CAPABILITIES* out_caps) { SCOPE_profile_cpu_f("hid"); @@ -37,9 +49,11 @@ X_RESULT InputSystem::GetCapabilities(uint32_t user_index, uint32_t flags, any_connected = true; } if (result == X_ERROR_SUCCESS) { + UpdateUsedSlot(user_index, any_connected); return result; } } + UpdateUsedSlot(user_index, any_connected); return any_connected ? X_ERROR_EMPTY : X_ERROR_DEVICE_NOT_CONNECTED; } @@ -53,9 +67,11 @@ X_RESULT InputSystem::GetState(uint32_t user_index, X_INPUT_STATE* out_state) { any_connected = true; } if (result == X_ERROR_SUCCESS) { + UpdateUsedSlot(user_index, any_connected); return result; } } + UpdateUsedSlot(user_index, any_connected); return any_connected ? X_ERROR_EMPTY : X_ERROR_DEVICE_NOT_CONNECTED; } @@ -70,9 +86,11 @@ X_RESULT InputSystem::SetState(uint32_t user_index, any_connected = true; } if (result == X_ERROR_SUCCESS) { + UpdateUsedSlot(user_index, any_connected); return result; } } + UpdateUsedSlot(user_index, any_connected); return any_connected ? X_ERROR_EMPTY : X_ERROR_DEVICE_NOT_CONNECTED; } @@ -87,9 +105,11 @@ X_RESULT InputSystem::GetKeystroke(uint32_t user_index, uint32_t flags, any_connected = true; } if (result == X_ERROR_SUCCESS || result == X_ERROR_EMPTY) { + UpdateUsedSlot(user_index, any_connected); return result; } } + UpdateUsedSlot(user_index, any_connected); return any_connected ? X_ERROR_EMPTY : X_ERROR_DEVICE_NOT_CONNECTED; } diff --git a/src/xenia/hid/input_system.h b/src/xenia/hid/input_system.h index 7f26e0663..94fbc69ec 100644 --- a/src/xenia/hid/input_system.h +++ b/src/xenia/hid/input_system.h @@ -44,10 +44,15 @@ class InputSystem { X_RESULT GetKeystroke(uint32_t user_index, uint32_t flags, X_INPUT_KEYSTROKE* out_keystroke); + void UpdateUsedSlot(uint8_t slot, bool connected); + uint8_t GetConnectedSlots() const { return connected_slot; } + private: xe::ui::Window* window_ = nullptr; std::vector> drivers_; + + uint8_t connected_slot = 0b0001; }; } // namespace hid diff --git a/src/xenia/kernel/kernel_state.cc b/src/xenia/kernel/kernel_state.cc index 7d60aac64..79b416669 100644 --- a/src/xenia/kernel/kernel_state.cc +++ b/src/xenia/kernel/kernel_state.cc @@ -18,6 +18,7 @@ #include "xenia/base/string.h" #include "xenia/cpu/processor.h" #include "xenia/emulator.h" +#include "xenia/hid/input_system.h" #include "xenia/kernel/user_module.h" #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xam/xam_module.h" @@ -49,7 +50,7 @@ KernelState::KernelState(Emulator* emulator) file_system_ = emulator->file_system(); app_manager_ = std::make_unique(); - user_profile_ = std::make_unique(); + user_profiles_.emplace(0, std::make_unique(0)); auto content_root = emulator_->content_root(); if (!content_root.empty()) { @@ -883,5 +884,27 @@ bool KernelState::Restore(ByteStream* stream) { return true; } +uint8_t KernelState::GetConnectedUsers() const { + return emulator_->input_system()->GetConnectedSlots(); +} + +void KernelState::UpdateUsedUserProfiles() { + const uint8_t used_slots_bitmask = GetConnectedUsers(); + + for (uint8_t i = 1; i < 4; i++) { + bool is_used = used_slots_bitmask & (1 << i); + + if (IsUserSignedIn(i) && !is_used) { + user_profiles_.erase(i); + BroadcastNotification(0x12, 0); + } + + if (!IsUserSignedIn(i) && is_used) { + user_profiles_.emplace(i, std::make_unique(i)); + BroadcastNotification(0x12, 0); + } + } +} + } // namespace kernel } // namespace xe diff --git a/src/xenia/kernel/kernel_state.h b/src/xenia/kernel/kernel_state.h index 796d2dada..0c87413ac 100644 --- a/src/xenia/kernel/kernel_state.h +++ b/src/xenia/kernel/kernel_state.h @@ -104,7 +104,17 @@ class KernelState { xam::ContentManager* content_manager() const { return content_manager_.get(); } - xam::UserProfile* user_profile() const { return user_profile_.get(); } + + uint8_t GetConnectedUsers() const; + void UpdateUsedUserProfiles(); + + bool IsUserSignedIn(uint8_t index) const { + return user_profiles_.find(index) != user_profiles_.cend(); + } + + xam::UserProfile* user_profile(uint8_t index) const { + return user_profiles_.at(index).get(); + } // Access must be guarded by the global critical region. util::ObjectTable* object_table() { return &object_table_; } @@ -206,7 +216,7 @@ class KernelState { std::unique_ptr app_manager_; std::unique_ptr content_manager_; - std::unique_ptr user_profile_; + std::map> user_profiles_; xe::global_critical_region global_critical_region_; diff --git a/src/xenia/kernel/xam/content_manager.cc b/src/xenia/kernel/xam/content_manager.cc index 883020ce9..bce57d673 100644 --- a/src/xenia/kernel/xam/content_manager.cc +++ b/src/xenia/kernel/xam/content_manager.cc @@ -246,7 +246,7 @@ X_RESULT ContentManager::DeleteContent(const XCONTENT_AGGREGATE_DATA& data) { std::filesystem::path ContentManager::ResolveGameUserContentPath() { auto title_id = fmt::format("{:08X}", kernel_state_->title_id()); - auto user_name = xe::to_path(kernel_state_->user_profile()->name()); + auto user_name = xe::to_path(kernel_state_->user_profile(0)->name()); // Per-game per-profile data location: // content_root/title_id/profile/user_name diff --git a/src/xenia/kernel/xam/user_profile.cc b/src/xenia/kernel/xam/user_profile.cc index 3bfa40f48..4911051d0 100644 --- a/src/xenia/kernel/xam/user_profile.cc +++ b/src/xenia/kernel/xam/user_profile.cc @@ -19,12 +19,15 @@ namespace xe { namespace kernel { namespace xam { -UserProfile::UserProfile() { +UserProfile::UserProfile(uint8_t index) { // 58410A1F checks the user XUID against a mask of 0x00C0000000000000 (3<<54), // if non-zero, it prevents the user from playing the game. // "You do not have permissions to perform this operation." - xuid_ = 0xB13EBABEBABEBABE; + xuid_ = 0xB13EBABEBABEBABE + index; name_ = "User"; + if (index) { + name_ = "User_" + std::to_string(index); + } // https://cs.rin.ru/forum/viewtopic.php?f=38&t=60668&hilit=gfwl+live&start=195 // https://github.com/arkem/py360/blob/master/py360/constants.py diff --git a/src/xenia/kernel/xam/user_profile.h b/src/xenia/kernel/xam/user_profile.h index ad24b003d..0d0d001ca 100644 --- a/src/xenia/kernel/xam/user_profile.h +++ b/src/xenia/kernel/xam/user_profile.h @@ -222,7 +222,7 @@ class UserProfile { } }; - UserProfile(); + UserProfile(uint8_t index); uint64_t xuid() const { return xuid_; } std::string name() const { return name_; } diff --git a/src/xenia/kernel/xam/xam_content.cc b/src/xenia/kernel/xam/xam_content.cc index 11a9cf52b..639dbe05d 100644 --- a/src/xenia/kernel/xam/xam_content.cc +++ b/src/xenia/kernel/xam/xam_content.cc @@ -314,7 +314,12 @@ dword_result_t XamContentGetCreator_entry(dword_t user_index, // User always creates saves. *is_creator_ptr = 1; if (creator_xuid_ptr) { - *creator_xuid_ptr = kernel_state()->user_profile()->xuid(); + if (kernel_state()->IsUserSignedIn(user_index)) { + const auto& user_profile = kernel_state()->user_profile(user_index); + *creator_xuid_ptr = user_profile->xuid(); + } else { + result = X_ERROR_NO_SUCH_USER; + } } } else { *is_creator_ptr = 0; diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc index 2155504e8..620548970 100644 --- a/src/xenia/kernel/xam/xam_user.cc +++ b/src/xenia/kernel/xam/xam_user.cc @@ -34,8 +34,8 @@ X_HRESULT_result_t XamUserGetXUID_entry(dword_t user_index, dword_t type_mask, uint32_t result = X_E_NO_SUCH_USER; uint64_t xuid = 0; if (user_index < 4) { - if (user_index == 0) { - const auto& user_profile = kernel_state()->user_profile(); + if (kernel_state()->IsUserSignedIn(user_index)) { + const auto& user_profile = kernel_state()->user_profile(user_index); auto type = user_profile->type() & type_mask; if (type & (2 | 4)) { // maybe online profile? @@ -60,8 +60,8 @@ dword_result_t XamUserGetSigninState_entry(dword_t user_index) { xe::threading::MaybeYield(); uint32_t signin_state = 0; if (user_index < 4) { - if (user_index == 0) { - const auto& user_profile = kernel_state()->user_profile(); + if (kernel_state()->IsUserSignedIn(user_index)) { + const auto& user_profile = kernel_state()->user_profile(user_index); signin_state = user_profile->signin_state(); } } @@ -87,15 +87,21 @@ X_HRESULT_result_t XamUserGetSigninInfo_entry( } std::memset(info, 0, sizeof(X_USER_SIGNIN_INFO)); - if (user_index) { + if (user_index > 3) { return X_E_NO_SUCH_USER; } - const auto& user_profile = kernel_state()->user_profile(); - info->xuid = user_profile->xuid(); - info->signin_state = user_profile->signin_state(); - xe::string_util::copy_truncating(info->name, user_profile->name(), - xe::countof(info->name)); + kernel_state()->UpdateUsedUserProfiles(); + + if (kernel_state()->IsUserSignedIn(user_index)) { + const auto& user_profile = kernel_state()->user_profile(user_index); + info->xuid = user_profile->xuid(); + info->signin_state = user_profile->signin_state(); + xe::string_util::copy_truncating(info->name, user_profile->name(), + xe::countof(info->name)); + } else { + return X_E_NO_SUCH_USER; + } return X_E_SUCCESS; } DECLARE_XAM_EXPORT1(XamUserGetSigninInfo, kUserProfiles, kImplemented); @@ -106,14 +112,14 @@ dword_result_t XamUserGetName_entry(dword_t user_index, lpstring_t buffer, return X_E_INVALIDARG; } - if (user_index) { + if (kernel_state()->IsUserSignedIn(user_index)) { + const auto& user_profile = kernel_state()->user_profile(user_index); + const auto& user_name = user_profile->name(); + xe::string_util::copy_truncating( + buffer, user_name, std::min(buffer_len.value(), uint32_t(16))); + } else { return X_E_NO_SUCH_USER; } - - const auto& user_profile = kernel_state()->user_profile(); - const auto& user_name = user_profile->name(); - xe::string_util::copy_truncating(buffer, user_name, - std::min(buffer_len.value(), uint32_t(16))); return X_E_SUCCESS; } DECLARE_XAM_EXPORT1(XamUserGetName, kUserProfiles, kImplemented); @@ -125,15 +131,15 @@ dword_result_t XamUserGetGamerTag_entry(dword_t user_index, return X_E_INVALIDARG; } - if (user_index) { - return X_E_NO_SUCH_USER; - } - if (!buffer || buffer_len < 16) { return X_E_INVALIDARG; } - const auto& user_profile = kernel_state()->user_profile(); + if (!kernel_state()->IsUserSignedIn(user_index)) { + return X_E_INVALIDARG; + } + + const auto& user_profile = kernel_state()->user_profile(user_index); auto user_name = xe::to_utf16(user_profile->name()); xe::string_util::copy_and_swap_truncating( buffer, user_name, std::min(buffer_len.value(), uint32_t(16))); @@ -161,11 +167,13 @@ uint32_t XamUserReadProfileSettingsEx(uint32_t title_id, uint32_t user_index, assert_true(xuid_count == 1); assert_not_null(xuids); // 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; + if (kernel_state()->IsUserSignedIn(user_index)) { + const auto& user_profile = kernel_state()->user_profile(user_index); + assert_true(static_cast(xuids[0]) == user_profile->xuid()); + } } assert_zero(unk); // probably flags @@ -216,19 +224,17 @@ uint32_t XamUserReadProfileSettingsEx(uint32_t title_id, uint32_t user_index, // Title ID = 0 means us. // 0xfffe07d1 = profile? - - if (!xuids && user_index) { - // Only support user 0. + if (!kernel_state()->IsUserSignedIn(user_index)) { if (overlapped) { - kernel_state()->CompleteOverlappedImmediate( - kernel_state()->memory()->HostToGuestVirtual(overlapped), - X_ERROR_NO_SUCH_USER); + kernel_state()->CompleteOverlappedImmediate( + kernel_state()->memory()->HostToGuestVirtual(overlapped), + X_ERROR_NO_SUCH_USER); return X_ERROR_IO_PENDING; } return X_ERROR_NO_SUCH_USER; } - const auto& user_profile = kernel_state()->user_profile(); + const auto& user_profile = kernel_state()->user_profile(user_index); // First call asks for size (fill buffer_size_ptr). // Second call asks for buffer contents with that size. @@ -325,18 +331,18 @@ dword_result_t XamUserWriteProfileSettings_entry( return X_ERROR_INVALID_PARAMETER; } - if (user_index) { - // Only support user 0. + // Skip writing data about users with id != 0 they're not supported + if (user_index > 0) { if (overlapped) { - kernel_state()->CompleteOverlappedImmediate(overlapped, - X_ERROR_NO_SUCH_USER); + kernel_state()->CompleteOverlappedImmediate( + kernel_state()->memory()->HostToGuestVirtual(overlapped), + X_ERROR_NO_SUCH_USER); return X_ERROR_IO_PENDING; } - return X_ERROR_NO_SUCH_USER; + return X_ERROR_SUCCESS; } - // Update and save settings. - const auto& user_profile = kernel_state()->user_profile(); + const auto& user_profile = kernel_state()->user_profile(user_index); for (uint32_t n = 0; n < setting_count; ++n) { const X_USER_PROFILE_SETTING& setting = settings[n]; @@ -401,7 +407,7 @@ dword_result_t XamUserCheckPrivilege_entry(dword_t user_index, dword_t mask, return X_ERROR_INVALID_PARAMETER; } - if (user_index) { + if (!kernel_state()->IsUserSignedIn(user_index)) { return X_ERROR_NO_SUCH_USER; } } @@ -414,7 +420,7 @@ DECLARE_XAM_EXPORT1(XamUserCheckPrivilege, kUserProfiles, kStub); dword_result_t XamUserContentRestrictionGetFlags_entry(dword_t user_index, lpdword_t out_flags) { - if (user_index) { + if (!kernel_state()->IsUserSignedIn(user_index)) { return X_ERROR_NO_SUCH_USER; } @@ -428,7 +434,7 @@ dword_result_t XamUserContentRestrictionGetRating_entry(dword_t user_index, dword_t unk1, lpdword_t out_unk2, lpdword_t out_unk3) { - if (user_index) { + if (!kernel_state()->IsUserSignedIn(user_index)) { return X_ERROR_NO_SUCH_USER; } @@ -462,7 +468,8 @@ dword_result_t XamUserGetMembershipTier_entry(dword_t user_index) { if (user_index >= 4) { return X_ERROR_INVALID_PARAMETER; } - if (user_index) { + + if (kernel_state()->IsUserSignedIn(user_index)) { return X_ERROR_NO_SUCH_USER; } return 6 /* 6 appears to be Gold */; @@ -478,8 +485,8 @@ dword_result_t XamUserAreUsersFriends_entry(dword_t user_index, dword_t unk1, if (user_index >= 4) { result = X_ERROR_INVALID_PARAMETER; } else { - if (user_index == 0) { - const auto& user_profile = kernel_state()->user_profile(); + if (kernel_state()->IsUserSignedIn(user_index)) { + const auto& user_profile = kernel_state()->user_profile(user_index); if (user_profile->signin_state() == 0) { result = X_ERROR_NOT_LOGGED_ON; } else { @@ -514,13 +521,16 @@ dword_result_t XamUserAreUsersFriends_entry(dword_t user_index, dword_t unk1, DECLARE_XAM_EXPORT1(XamUserAreUsersFriends, kUserProfiles, kStub); dword_result_t XamShowSigninUI_entry(dword_t unk, dword_t unk_mask) { + kernel_state()->UpdateUsedUserProfiles(); // Mask values vary. Probably matching user types? Local/remote? - - // To fix game modes that display a 4 profile signin UI (even if playing - // alone): - // XN_SYS_SIGNINCHANGED - kernel_state()->BroadcastNotification(0x0000000A, 1); // Games seem to sit and loop until we trigger this notification: + + for (uint8_t i = 1; i < 4; i++) { + if (kernel_state()->IsUserSignedIn(i)) { + // XN_SYS_SIGNINCHANGED + kernel_state()->BroadcastNotification(0xA, i); + } + } // XN_SYS_UI (off) kernel_state()->BroadcastNotification(0x00000009, 0); return X_ERROR_SUCCESS;