[XAM] Implementation of: Profile Manager & Profiles Support

Thanks Emoose for Account Encrypt/Decrypt code!
This commit is contained in:
Gliniak 2024-09-28 10:42:32 +02:00 committed by Radosław Gliński
parent 9ba0f18c82
commit 9bdd07590c
28 changed files with 1775 additions and 298 deletions

View File

@ -36,7 +36,9 @@
#include "xenia/gpu/d3d12/d3d12_command_processor.h"
#include "xenia/gpu/graphics_system.h"
#include "xenia/hid/input_system.h"
#include "xenia/kernel/xam/profile_manager.h"
#include "xenia/kernel/xam/xam_module.h"
#include "xenia/kernel/xam/xam_state.h"
#include "xenia/ui/file_picker.h"
#include "xenia/ui/graphics_provider.h"
#include "xenia/ui/imgui_dialog.h"
@ -147,6 +149,14 @@ DEFINE_int32(recent_titles_entry_amount, 10,
"recently played titles.",
"General");
namespace xe {
namespace kernel {
namespace xam {
extern std::atomic<int> xam_dialogs_shown_;
}
} // namespace kernel
} // namespace xe
namespace xe {
namespace app {
@ -250,6 +260,14 @@ void EmulatorWindow::ShutdownGraphicsSystemPresenterPainting() {
}
void EmulatorWindow::OnEmulatorInitialized() {
if (!emulator_->kernel_state()
->xam_state()
->profile_manager()
->GetProfilesCount()) {
new NoProfileDialog(imgui_drawer_.get(), this);
disable_hotkeys_ = true;
}
emulator_initialized_ = true;
window_->SetMainMenuEnabled(true);
// When the user can see that the emulator isn't initializing anymore (the
@ -593,6 +611,15 @@ bool EmulatorWindow::Initialize() {
}
main_menu->AddChild(std::move(file_menu));
// Profile Menu
auto profile_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Profile");
{
profile_menu->AddChild(MenuItem::Create(
MenuItem::Type::kString, "&Show Profile Menu", "",
std::bind(&EmulatorWindow::ToggleProfilesConfigDialog, this)));
}
main_menu->AddChild(std::move(profile_menu));
// CPU menu.
auto cpu_menu = MenuItem::Create(MenuItem::Type::kPopup, "&CPU");
{
@ -1104,6 +1131,10 @@ void EmulatorWindow::InstallContent() {
summary += "\n";
}
if (content_installation_details.count(XContentType::kProfile)) {
emulator_->kernel_state()->xam_state()->profile_manager()->ReloadProfiles();
}
xe::ui::ImGuiDialog::ShowMessageBox(imgui_drawer_.get(),
"Content Installation Summary", summary);
}
@ -1296,24 +1327,13 @@ void EmulatorWindow::CreateZarchive() {
}
void EmulatorWindow::ShowContentDirectory() {
std::filesystem::path target_path;
auto content_root = emulator_->content_root();
if (!emulator_->is_title_open() || !emulator_->kernel_state()) {
target_path = content_root;
} else {
// TODO(gibbed): expose this via ContentManager?
auto title_id =
fmt::format("{:08X}", emulator_->kernel_state()->title_id());
auto package_root = content_root / title_id;
target_path = package_root;
if (!std::filesystem::exists(content_root)) {
std::filesystem::create_directories(content_root);
}
if (!std::filesystem::exists(target_path)) {
std::filesystem::create_directories(target_path);
}
LaunchFileExplorer(target_path);
LaunchFileExplorer(content_root);
}
void EmulatorWindow::CpuTimeScalarReset() {
@ -1382,6 +1402,23 @@ void EmulatorWindow::ToggleDisplayConfigDialog() {
}
}
void EmulatorWindow::ToggleProfilesConfigDialog() {
if (!profile_config_dialog_) {
disable_hotkeys_ = true;
emulator_->kernel_state()->BroadcastNotification(kXNotificationIDSystemUI,
1);
profile_config_dialog_ =
std::make_unique<ProfileConfigDialog>(imgui_drawer_.get(), this);
kernel::xam::xam_dialogs_shown_++;
} else {
disable_hotkeys_ = false;
emulator_->kernel_state()->BroadcastNotification(kXNotificationIDSystemUI,
0);
profile_config_dialog_.reset();
kernel::xam::xam_dialogs_shown_--;
}
}
void EmulatorWindow::ToggleControllerVibration() {
auto input_sys = emulator()->input_system();
if (input_sys) {
@ -1579,7 +1616,13 @@ EmulatorWindow::ControllerHotKey EmulatorWindow::ProcessControllerHotkey(
// Default return value
EmulatorWindow::ControllerHotKey Unknown_hotkey = {};
if (buttons == 0) return Unknown_hotkey;
if (buttons == 0) {
return Unknown_hotkey;
}
if (disable_hotkeys_.load()) {
return Unknown_hotkey;
}
// Hotkey cool-down to prevent toggling too fast
const std::chrono::milliseconds delay(75);
@ -1790,7 +1833,8 @@ void EmulatorWindow::GamepadHotKeys() {
while (true) {
auto input_lock = input_sys->lock();
for (uint32_t user_index = 0; user_index < MAX_USERS; ++user_index) {
for (uint32_t user_index = 0; user_index < XUserMaxUserCount;
++user_index) {
X_RESULT result = input_sys->GetState(user_index, &state);
// Release the lock before processing the hotkey
@ -1962,6 +2006,17 @@ xe::X_STATUS EmulatorWindow::RunTitle(
auto result = emulator_->LaunchPath(abs_path);
disable_hotkeys_ = false;
if (profile_config_dialog_) {
profile_config_dialog_.reset();
kernel::xam::xam_dialogs_shown_--;
}
if (display_config_dialog_) {
display_config_dialog_.reset();
}
imgui_drawer_.get()->ClearDialogs();
if (result) {

View File

@ -25,7 +25,7 @@
#include "xenia/ui/windowed_app_context.h"
#include "xenia/xbox.h"
#define MAX_USERS 4
#include "xenia/app/profile_dialogs.h"
namespace xe {
namespace app {
@ -93,6 +93,12 @@ class EmulatorWindow {
void SaveImage(const std::filesystem::path& path,
const xe::ui::RawImage& image);
void ToggleProfilesConfigDialog();
void SetHotkeysState(bool enabled) { disable_hotkeys_ = !enabled; }
// We need to store it somewhere so there will be no situation when there are
// multiple instances opened.
std::unique_ptr<CreateProfileDialog> profile_creation_dialog_;
// Types of button functions for hotkeys.
enum class ButtonFunctions {
ToggleFullscreen,
@ -257,12 +263,17 @@ class EmulatorWindow {
std::unique_ptr<ui::ImmediateDrawer> immediate_drawer_;
bool emulator_initialized_ = false;
std::atomic<bool> disable_hotkeys_ = false;
std::string base_title_;
bool initializing_shader_storage_ = false;
std::unique_ptr<DisplayConfigDialog> display_config_dialog_;
// Storing pointers and toggling dialog state is useful for broadcasting
// messages back to guest.
std::unique_ptr<ProfileConfigDialog> profile_config_dialog_;
std::vector<RecentTitleEntry> recently_launched_titles_;
};

View File

@ -0,0 +1,333 @@
/**
******************************************************************************
* 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/app/profile_dialogs.h"
#include <algorithm>
#include "xenia/app/emulator_window.h"
#include "xenia/base/system.h"
namespace xe {
namespace app {
void CreateProfileDialog::OnDraw(ImGuiIO& io) {
auto profile_manager = emulator_window_->emulator()
->kernel_state()
->xam_state()
->profile_manager();
const auto window_position =
ImVec2(GetIO().DisplaySize.x * 0.40f, GetIO().DisplaySize.y * 0.44f);
ImGui::SetNextWindowPos(window_position, ImGuiCond_FirstUseEver);
ImGui::SetNextWindowBgAlpha(1.0f);
bool dialog_open = true;
if (!ImGui::Begin("Create Profile", &dialog_open,
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_HorizontalScrollbar)) {
ImGui::End();
emulator_window_->profile_creation_dialog_.reset();
return;
}
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
!ImGui::IsAnyItemActive() && !ImGui::IsMouseClicked(0)) {
ImGui::SetKeyboardFocusHere(0);
}
ImGui::TextUnformatted("Gamertag:");
ImGui::InputText("##Gamertag", gamertag, sizeof(gamertag));
const std::string gamertag_string = std::string(gamertag);
if (profile_manager->IsGamertagValid(gamertag_string)) {
if (ImGui::Button("Create")) {
if (profile_manager->CreateProfile(gamertag_string, migration) &&
migration) {
emulator_window_->emulator()->DataMigration(0xB13EBABEBABEBABE);
}
std::fill(std::begin(gamertag), std::end(gamertag), '\0');
dialog_open = false;
}
ImGui::SameLine();
}
if (ImGui::Button("Cancel")) {
std::fill(std::begin(gamertag), std::end(gamertag), '\0');
dialog_open = false;
}
if (!dialog_open) {
ImGui::End();
emulator_window_->profile_creation_dialog_.reset();
return;
}
ImGui::End();
}
void NoProfileDialog::OnDraw(ImGuiIO& io) {
auto profile_manager = emulator_window_->emulator()
->kernel_state()
->xam_state()
->profile_manager();
if (profile_manager->GetProfilesCount()) {
delete this;
return;
}
const auto window_position =
ImVec2(GetIO().DisplaySize.x * 0.35f, GetIO().DisplaySize.y * 0.4f);
ImGui::SetNextWindowPos(window_position, ImGuiCond_FirstUseEver);
ImGui::SetNextWindowBgAlpha(1.0f);
bool dialog_open = true;
if (!ImGui::Begin("No Profiles Found", &dialog_open,
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_HorizontalScrollbar)) {
ImGui::End();
delete this;
return;
}
const std::string message =
"There is no profile available! You will not be able to save without "
"one.\n\nWould you like to create one?";
ImGui::TextUnformatted(message.c_str());
ImGui::Separator();
ImGui::NewLine();
const auto content_files = xe::filesystem::ListDirectories(
emulator_window_->emulator()->content_root());
if (content_files.empty()) {
if (ImGui::Button("Create Profile") &&
!emulator_window_->profile_creation_dialog_) {
emulator_window_->profile_creation_dialog_ =
std::make_unique<CreateProfileDialog>(
emulator_window_->imgui_drawer(), emulator_window_);
}
} else {
if (ImGui::Button("Create profile & migrate data") &&
!emulator_window_->profile_creation_dialog_) {
emulator_window_->profile_creation_dialog_ =
std::make_unique<CreateProfileDialog>(
emulator_window_->imgui_drawer(), emulator_window_, true);
}
}
ImGui::SameLine();
if (ImGui::Button("Open profile menu")) {
emulator_window_->ToggleProfilesConfigDialog();
}
ImGui::SameLine();
if (ImGui::Button("Close") || !dialog_open) {
emulator_window_->SetHotkeysState(true);
ImGui::End();
delete this;
return;
}
ImGui::End();
}
void ProfileConfigDialog::OnDraw(ImGuiIO& io) {
if (!emulator_window_->emulator() ||
!emulator_window_->emulator()->kernel_state() ||
!emulator_window_->emulator()->kernel_state()->xam_state()) {
return;
}
auto profile_manager = emulator_window_->emulator()
->kernel_state()
->xam_state()
->profile_manager();
if (!profile_manager) {
return;
}
auto profiles = profile_manager->GetProfiles();
ImGui::SetNextWindowPos(ImVec2(40, 40), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowBgAlpha(0.8f);
bool dialog_open = true;
if (!ImGui::Begin("Profiles Menu", &dialog_open,
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_HorizontalScrollbar)) {
ImGui::End();
return;
}
if (profiles->empty()) {
ImGui::TextUnformatted("No profiles found!");
ImGui::Spacing();
ImGui::Separator();
}
for (auto& [xuid, account] : *profiles) {
ImGui::PushID(static_cast<int>(xuid));
const uint8_t user_index =
profile_manager->GetUserIndexAssignedToProfile(xuid);
DrawProfileContent(xuid, user_index, &account);
ImGui::PopID();
ImGui::Spacing();
ImGui::Separator();
}
ImGui::Spacing();
if (ImGui::Button("Create Profile") &&
!emulator_window_->profile_creation_dialog_) {
emulator_window_->profile_creation_dialog_ =
std::make_unique<CreateProfileDialog>(emulator_window_->imgui_drawer(),
emulator_window_);
}
ImGui::End();
if (!dialog_open) {
emulator_window_->ToggleProfilesConfigDialog();
return;
}
}
bool ProfileConfigDialog::DrawProfileContent(const uint64_t xuid,
const uint8_t user_index,
const X_XAMACCOUNTINFO* account) {
auto profile_manager = emulator_window_->emulator()
->kernel_state()
->xam_state()
->profile_manager();
const float default_image_size = 75.0f;
auto position = ImGui::GetCursorPos();
const float selectable_height =
ImGui::GetTextLineHeight() *
5; // 3 is for amount of lines of text behind image/object.
const auto font = emulator_window_->imgui_drawer()->GetIO().Fonts->Fonts[0];
const auto text_size = font->CalcTextSizeA(
font->FontSize, FLT_MAX, -1.0f,
fmt::format("XUID: {:016X}\n", 0xB13EBABEBABEBABE).c_str());
const auto image_scale = selectable_height / default_image_size;
const auto image_size = ImVec2(default_image_size * image_scale,
default_image_size * image_scale);
// This includes 10% to include empty spaces between border and elements.
auto selectable_region_size =
ImVec2((image_size.x + text_size.x) * 1.10f, selectable_height);
if (ImGui::Selectable("##Selectable", selected_xuid_ == xuid,
ImGuiSelectableFlags_SpanAllColumns,
selectable_region_size)) {
selected_xuid_ = xuid;
}
if (ImGui::BeginPopupContextItem("Profile Menu")) {
if (user_index == static_cast<uint8_t>(-1)) {
if (ImGui::MenuItem("Login")) {
profile_manager->Login(xuid);
}
if (ImGui::BeginMenu("Login to slot:")) {
for (uint8_t i = 0; i < XUserMaxUserCount; i++) {
if (ImGui::MenuItem(fmt::format("slot {}", i).c_str())) {
profile_manager->Login(xuid, i);
}
}
ImGui::EndMenu();
}
} else {
if (ImGui::MenuItem("Logout")) {
profile_manager->Logout(user_index);
}
}
ImGui::MenuItem("Modify (unsupported)");
ImGui::MenuItem("Show Achievements (unsupported)");
if (ImGui::MenuItem("Show Content Directory")) {
const auto path = profile_manager->GetProfileContentPath(
xuid, emulator_window_->emulator()->kernel_state()->title_id());
if (!std::filesystem::exists(path)) {
std::filesystem::create_directories(path);
}
std::thread path_open(LaunchFileExplorer, path);
path_open.detach();
}
if (!emulator_window_->emulator()->is_title_open()) {
ImGui::Separator();
if (ImGui::BeginMenu("Delete Profile")) {
ImGui::BeginTooltip();
ImGui::TextUnformatted(
fmt::format("You're about to delete profile: {} (XUID: {:016X}). "
"This will remove all data assigned to this profile "
"including savefiles. Are you sure?",
account->GetGamertagString(), xuid)
.c_str());
ImGui::EndTooltip();
if (ImGui::MenuItem("Yes, delete it!")) {
profile_manager->DeleteProfile(xuid);
ImGui::EndMenu();
ImGui::EndPopup();
return false;
}
ImGui::EndMenu();
}
}
ImGui::EndPopup();
}
ImGui::SameLine();
ImGui::SetCursorPos(position);
// In the future it can be replaced with profile icon.
ImGui::Image(user_index < XUserMaxUserCount
? imgui_drawer()->GetNotificationIcon(user_index)
: nullptr,
ImVec2(default_image_size * image_scale,
default_image_size * image_scale));
ImGui::SameLine();
position = ImGui::GetCursorPos();
ImGui::TextUnformatted(
fmt::format("User: {}\n", account->GetGamertagString()).c_str());
ImGui::SameLine();
ImGui::SetCursorPos(position);
ImGui::SetCursorPosY(position.y + ImGui::GetTextLineHeight());
ImGui::TextUnformatted(fmt::format("XUID: {:016X}\n", xuid).c_str());
if (user_index != static_cast<uint8_t>(-1)) {
ImGui::SameLine();
ImGui::SetCursorPos(position);
ImGui::SetCursorPosY(position.y + 2 * ImGui::GetTextLineHeight());
ImGui::TextUnformatted(
fmt::format("Assigned to slot: {}\n", user_index).c_str());
}
return true;
}
} // namespace app
} // namespace xe

View File

@ -0,0 +1,76 @@
/**
******************************************************************************
* 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_APP_PROFILE_DIALOGS_H_
#define XENIA_APP_PROFILE_DIALOGS_H_
#include "xenia/ui/imgui_dialog.h"
#include "xenia/ui/imgui_drawer.h"
#include "xenia/xbox.h"
namespace xe {
namespace app {
class EmulatorWindow;
class CreateProfileDialog final : public ui::ImGuiDialog {
public:
CreateProfileDialog(ui::ImGuiDrawer* imgui_drawer,
EmulatorWindow* emulator_window,
bool with_migration = false)
: ui::ImGuiDialog(imgui_drawer),
emulator_window_(emulator_window),
migration(with_migration) {
memset(gamertag, 0, sizeof(gamertag));
}
protected:
void OnDraw(ImGuiIO& io) override;
bool migration = false;
char gamertag[16] = "";
EmulatorWindow* emulator_window_;
};
class NoProfileDialog final : public ui::ImGuiDialog {
public:
NoProfileDialog(ui::ImGuiDrawer* imgui_drawer,
EmulatorWindow* emulator_window)
: ui::ImGuiDialog(imgui_drawer), emulator_window_(emulator_window) {}
protected:
void OnDraw(ImGuiIO& io) override;
EmulatorWindow* emulator_window_;
};
class ProfileConfigDialog final : public ui::ImGuiDialog {
public:
ProfileConfigDialog(ui::ImGuiDrawer* imgui_drawer,
EmulatorWindow* emulator_window)
: ui::ImGuiDialog(imgui_drawer), emulator_window_(emulator_window) {
memset(gamertag, 0, sizeof(gamertag));
}
protected:
void OnDraw(ImGuiIO& io) override;
private:
bool DrawProfileContent(const uint64_t xuid, const uint8_t user_index,
const X_XAMACCOUNTINFO* account);
uint64_t selected_xuid_ = 0;
char gamertag[16] = "";
EmulatorWindow* emulator_window_;
};
} // namespace app
} // namespace xe
#endif

View File

@ -50,6 +50,7 @@
#include "xenia/ui/file_picker.h"
#include "xenia/ui/imgui_dialog.h"
#include "xenia/ui/imgui_drawer.h"
#include "xenia/ui/imgui_host_notification.h"
#include "xenia/ui/window.h"
#include "xenia/ui/windowed_app_context.h"
#include "xenia/vfs/device.h"
@ -594,6 +595,163 @@ X_STATUS Emulator::LaunchDefaultModule(const std::filesystem::path& path) {
return result;
}
X_STATUS Emulator::DataMigration(const uint64_t xuid) {
uint32_t failure_count = 0;
const std::string xuid_string = fmt::format("{:016X}", xuid);
const std::string common_xuid_string = fmt::format("{:016X}", 0);
const std::filesystem::path path_to_profile_data =
content_root_ / xuid_string / "FFFE07D1" / "00010000" / xuid_string;
// Filter directories inside. First we need to find any content type
// directories.
// Savefiles must go to user specific directory
// Everything else goes to common
const auto titles_to_move = xe::filesystem::FilterByName(
xe::filesystem::ListDirectories(content_root_),
std::regex("[A-F0-9]{8}"));
for (const auto& title : titles_to_move) {
if (xe::path_to_utf8(title.name) == "FFFE07D1" ||
xe::path_to_utf8(title.name) == "00000000") {
// SKip any dashboard/profile related data that was previously installed
continue;
}
const auto content_type_dirs = xe::filesystem::FilterByName(
xe::filesystem::ListDirectories(title.path / title.name),
std::regex("[A-F0-9]{8}"));
for (const auto& content_type : content_type_dirs) {
const std::string used_xuid =
xe::path_to_utf8(content_type.name) == "00000001"
? xuid_string
: common_xuid_string;
const auto previous_path = content_root_ / title.name / content_type.name;
const auto path = content_root_ / used_xuid / title.name;
if (!std::filesystem::exists(path)) {
std::filesystem::create_directories(path);
}
std::error_code ec;
std::filesystem::rename(previous_path, path / content_type.name, ec);
if (ec) {
failure_count++;
XELOGW("{}: Moving from: {} to: {} failed! Error message: {} ({:08X})",
__func__, xe::path_to_utf8(previous_path),
xe::path_to_utf8(path / content_type.name), ec.message(),
ec.value());
}
}
// Other directories:
// Headers - Just copy everything to both common and xuid locations
// profile - ?
if (std::filesystem::exists(title.path / title.name / "Headers")) {
const auto xuid_path =
content_root_ / xuid_string / title.name / "Headers";
std::filesystem::create_directories(xuid_path);
std::error_code ec;
// Copy to specific user
std::filesystem::copy(title.path / title.name / "Headers", xuid_path,
std::filesystem::copy_options::recursive |
std::filesystem::copy_options::skip_existing,
ec);
if (ec) {
failure_count++;
XELOGW("{}: Copying from: {} to: {} failed! Error message: {} ({:08X})",
__func__, xe::path_to_utf8(title.path / title.name / "Headers"),
xe::path_to_utf8(xuid_path), ec.message(), ec.value());
}
const auto header_types =
xe::filesystem::ListDirectories(title.path / title.name / "Headers");
if (!(header_types.size() == 1 &&
header_types.at(0).name == "00000001")) {
const auto common_path =
content_root_ / common_xuid_string / title.name / "Headers";
std::filesystem::create_directories(common_path);
// Copy to common, skip cases where only savefile header is available
std::filesystem::copy(title.path / title.name / "Headers", common_path,
std::filesystem::copy_options::recursive |
std::filesystem::copy_options::skip_existing,
ec);
if (ec) {
failure_count++;
XELOGW(
"{}: Copying from: {} to: {} failed! Error message: {} ({:08X})",
__func__, xe::path_to_utf8(title.path / title.name / "Headers"),
xe::path_to_utf8(common_path), ec.message(), ec.value());
}
}
if (!ec) {
// Remove previous directory
std::error_code ec;
std::filesystem::remove_all(title.path / title.name / "Headers", ec);
}
}
if (std::filesystem::exists(title.path / title.name / "profile")) {
// Find directory with previous username. There should be only one!
const auto old_profile_data =
xe::filesystem::ListDirectories(title.path / title.name / "profile");
xe::filesystem::FileInfo& entry_to_copy = xe::filesystem::FileInfo();
if (old_profile_data.size() != 1) {
for (const auto& entry : old_profile_data) {
if (entry.name == "User") {
entry_to_copy = entry;
}
}
} else {
entry_to_copy = old_profile_data.front();
}
const auto path_from =
title.path / title.name / "profile" / entry_to_copy.name;
std::error_code ec;
// Move files from inside to outside for convenience
std::filesystem::rename(path_from, path_to_profile_data / title.name, ec);
if (ec) {
failure_count++;
XELOGW("{}: Moving from: {} to: {} failed! Error message: {} ({:08X})",
__func__, xe::path_to_utf8(path_from),
xe::path_to_utf8(path_to_profile_data / title.name),
ec.message(), ec.value());
} else {
std::error_code ec;
std::filesystem::remove_all(title.path / title.name / "profile", ec);
}
}
const auto remaining_file_list =
xe::filesystem::ListDirectories(title.path / title.name);
if (remaining_file_list.empty()) {
std::error_code ec;
std::filesystem::remove_all(title.path / title.name, ec);
}
}
std::string migration_status_message =
fmt::format("Migration finished with {} {}.", failure_count,
failure_count == 1 ? "error" : "errors");
if (failure_count) {
migration_status_message.append(
" For more information check xenia.log file.");
}
new xe::ui::HostNotificationWindow(imgui_drawer_, "Migration Status",
migration_status_message, 0);
return X_STATUS_SUCCESS;
}
X_STATUS Emulator::InstallContentPackage(
const std::filesystem::path& path,
ContentInstallationInfo& installation_info) {
@ -613,16 +771,18 @@ X_STATUS Emulator::InstallContentPackage(
(vfs::XContentContainerDevice*)device.get();
std::filesystem::path installation_path =
content_root() / fmt::format("{:08X}", dev->title_id()) /
content_root() / fmt::format("{:016X}", dev->xuid()) /
fmt::format("{:08X}", dev->title_id()) /
fmt::format("{:08X}", dev->content_type()) / path.filename();
std::filesystem::path header_path =
content_root() / fmt::format("{:08X}", dev->title_id()) / "Headers" /
content_root() / fmt::format("{:016X}", dev->xuid()) /
fmt::format("{:08X}", dev->title_id()) / "Headers" /
fmt::format("{:08X}", dev->content_type()) / path.filename();
installation_info.installation_path =
fmt::format("{:08X}/{:08X}/{}", dev->title_id(), dev->content_type(),
xe::path_to_utf8(path.filename()));
fmt::format("{:016X}/{:08X}/{:08X}/{}", dev->xuid(), dev->title_id(),
dev->content_type(), xe::path_to_utf8(path.filename()));
installation_info.content_name =
xe::to_utf8(dev->content_header().display_name());

View File

@ -231,6 +231,9 @@ class Emulator {
std::string content_name;
};
// Migrates data from content to content/xuid with respect to common data.
X_STATUS DataMigration(const uint64_t xuid);
// Extract content of package to content specific directory.
X_STATUS InstallContentPackage(const std::filesystem::path& path,
ContentInstallationInfo& installation_info);

View File

@ -14,6 +14,7 @@
#include "xenia/base/profiling.h"
#include "xenia/hid/hid_flags.h"
#include "xenia/hid/input_driver.h"
#include "xenia/kernel/util/shim_utils.h"
namespace xe {
namespace hid {
@ -39,7 +40,7 @@ void InputSystem::AddDriver(std::unique_ptr<InputDriver> driver) {
void InputSystem::UpdateUsedSlot(InputDriver* driver, uint8_t slot,
bool connected) {
if (slot == 0xFF) {
if (slot == XUserIndexAny) {
slot = 0;
}
@ -50,6 +51,10 @@ void InputSystem::UpdateUsedSlot(InputDriver* driver, uint8_t slot,
XELOGI(controller_slot_state_change_message[connected].c_str(), slot);
connected_slots.flip(slot);
if (kernel::kernel_state()) {
kernel::kernel_state()->BroadcastNotification(
kXNotificationIDSystemInputDevicesChanged, 0);
}
if (driver) {
X_INPUT_CAPABILITIES capabilities = {};
@ -147,15 +152,14 @@ void InputSystem::ToggleVibration() {
// Send instant update to vibration state to prevent awaiting for next tick.
X_INPUT_VIBRATION vibration = X_INPUT_VIBRATION();
for (uint8_t user_index = 0; user_index < max_allowed_controllers;
user_index++) {
for (uint8_t user_index = 0; user_index < XUserMaxUserCount; user_index++) {
SetState(user_index, &vibration);
}
}
void InputSystem::AdjustDeadzoneLevels(const uint8_t slot,
X_INPUT_GAMEPAD* gamepad) {
if (slot > max_allowed_controllers) {
if (slot > XUserMaxUserCount) {
return;
}

View File

@ -27,8 +27,6 @@ class Window;
namespace xe {
namespace hid {
static constexpr uint8_t max_allowed_controllers = 4;
class InputSystem {
public:
explicit InputSystem(xe::ui::Window* window);
@ -51,7 +49,7 @@ class InputSystem {
void ToggleVibration();
const std::bitset<max_allowed_controllers> GetConnectedSlots() const {
const std::bitset<XUserMaxUserCount> GetConnectedSlots() const {
return connected_slots;
}
@ -72,8 +70,8 @@ class InputSystem {
std::vector<std::unique_ptr<InputDriver>> drivers_;
std::bitset<max_allowed_controllers> connected_slots = {};
std::array<std::pair<joystick_value, joystick_value>, max_allowed_controllers>
std::bitset<XUserMaxUserCount> connected_slots = {};
std::array<std::pair<joystick_value, joystick_value>, XUserMaxUserCount>
controllers_max_joystick_value = {};
xe_unlikely_mutex lock_;

View File

@ -282,7 +282,7 @@ X_RESULT SDLInputDriver::GetKeystroke(uint32_t users, uint32_t flags,
// TODO(JoelLinn): Figure out the flags
// https://github.com/evilC/UCR/blob/0489929e2a8e39caa3484c67f3993d3fba39e46f/Libraries/XInput.ahk#L85-L98
assert(sdl_events_initialized_ && sdl_gamecontroller_initialized_);
bool user_any = users == 0xFF;
bool user_any = users == XUserIndexAny;
if (users >= HID_SDL_USER_COUNT && !user_any) {
return X_ERROR_BAD_ARGUMENTS;
}

View File

@ -208,7 +208,7 @@ X_RESULT XInputInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags,
// we are not passing back an uninitialized X_INPUT_KEYSTROKE structure.
// If any user (0xFF) is polled this bug does not occur but GetCapabilities
// would fail so we need to skip it.
if (user_index != 0xFF) {
if (user_index != XUserIndexAny) {
XINPUT_CAPABILITIES caps;
auto xigc = (decltype(&XInputGetCapabilities))XInputGetCapabilities_;
result = xigc(user_index, 0, &caps);

View File

@ -106,6 +106,10 @@ KernelState::~KernelState() {
KernelState* KernelState::shared() { return shared_kernel_state_; }
uint32_t KernelState::title_id() const {
if (!executable_module_) {
return 0;
}
assert_not_null(executable_module_);
xex2_opt_execution_info* exec_info = 0;
@ -577,7 +581,7 @@ std::vector<xam::XCONTENT_AGGREGATE_DATA> KernelState::FindTitleUpdate(
}
return xam_state_->content_manager()->ListContent(
1, xe::XContentType::kInstaller, title_id);
1, 0, title_id, xe::XContentType::kInstaller);
}
const object_ref<UserModule> KernelState::LoadTitleUpdate(
@ -589,7 +593,7 @@ const object_ref<UserModule> KernelState::LoadTitleUpdate(
}
X_RESULT open_status =
content_manager()->OpenContent("UPDATE", *title_update, disc_number);
content_manager()->OpenContent("UPDATE", 0, *title_update, disc_number);
// Use the corresponding patch for the launch module
std::filesystem::path patch_xexp;

View File

@ -27,8 +27,6 @@ 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;
@ -62,24 +60,28 @@ ContentManager::ContentManager(KernelState* kernel_state,
ContentManager::~ContentManager() = default;
std::filesystem::path ContentManager::ResolvePackageRoot(
XContentType content_type, uint32_t title_id) {
uint64_t xuid, uint32_t title_id, XContentType content_type) const {
if (title_id == kCurrentlyRunningTitleId) {
title_id = kernel_state_->title_id();
}
auto xuid_str = fmt::format("{:016X}", xuid);
auto title_id_str = fmt::format("{:08X}", title_id);
auto content_type_str = fmt::format("{:08X}", uint32_t(content_type));
auto content_type_str =
fmt::format("{:08X}", static_cast<uint32_t>(content_type));
// Package root path:
// content_root/title_id/content_type/
return root_path_ / title_id_str / content_type_str;
return root_path_ / xuid_str / title_id_str / content_type_str;
}
std::filesystem::path ContentManager::ResolvePackagePath(
const XCONTENT_AGGREGATE_DATA& data, const uint32_t disc_number) {
const uint64_t xuid, const XCONTENT_AGGREGATE_DATA& data,
const uint32_t disc_number) {
// Content path:
// content_root/title_id/content_type/data_file_name/
auto get_package_path = [&, data, disc_number](const uint32_t title_id) {
auto package_root = ResolvePackageRoot(data.content_type, title_id);
auto package_root = ResolvePackageRoot(xuid, title_id, data.content_type);
std::string final_name = xe::string_util::trim(data.file_name());
std::filesystem::path package_path = package_root / xe::to_path(final_name);
@ -91,7 +93,7 @@ std::filesystem::path ContentManager::ResolvePackagePath(
if (data.content_type == XContentType::kPublisher) {
const std::unordered_set<uint32_t> title_ids =
FindPublisherTitleIds(data.title_id);
FindPublisherTitleIds(xuid, data.title_id);
for (const auto& title_id : title_ids) {
auto package_path = get_package_path(title_id);
@ -108,24 +110,30 @@ std::filesystem::path ContentManager::ResolvePackagePath(
}
std::filesystem::path ContentManager::ResolvePackageHeaderPath(
const std::string_view file_name, XContentType content_type,
uint32_t title_id) {
const std::string_view file_name, uint64_t xuid, uint32_t title_id,
const XContentType content_type) const {
if (title_id == kCurrentlyRunningTitleId) {
title_id = kernel_state_->title_id();
}
if (content_type == XContentType::kMarketplaceContent) {
xuid = 0;
}
auto xuid_str = fmt::format("{:016X}", xuid);
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_root/xuid/title_id/Headers/content_type/
return root_path_ / xuid_str / title_id_str / kGameContentHeaderDirName /
content_type_str / final_name;
}
std::unordered_set<uint32_t> ContentManager::FindPublisherTitleIds(
uint32_t base_title_id) const {
const uint64_t xuid, uint32_t base_title_id) const {
if (base_title_id == kCurrentlyRunningTitleId) {
base_title_id = kernel_state_->title_id();
}
@ -134,9 +142,10 @@ std::unordered_set<uint32_t> ContentManager::FindPublisherTitleIds(
std::string publisher_id_regex =
fmt::format("^{:04X}.*", static_cast<uint16_t>(base_title_id >> 16));
// Get all publisher entries
auto publisher_entries =
xe::filesystem::FilterByName(xe::filesystem::ListDirectories(root_path_),
std::regex(publisher_id_regex));
auto publisher_entries = xe::filesystem::FilterByName(
xe::filesystem::ListDirectories(root_path_ /
fmt::format("{:016X}", xuid)),
std::regex(publisher_id_regex));
for (const auto& entry : publisher_entries) {
std::filesystem::path path_to_publisher_dir =
@ -159,23 +168,20 @@ std::unordered_set<uint32_t> ContentManager::FindPublisherTitleIds(
}
std::vector<XCONTENT_AGGREGATE_DATA> ContentManager::ListContent(
uint32_t device_id, XContentType content_type, uint32_t title_id) {
const uint32_t device_id, const uint64_t xuid, const uint32_t title_id,
const XContentType content_type) const {
std::vector<XCONTENT_AGGREGATE_DATA> result;
if (title_id == kCurrentlyRunningTitleId) {
title_id = kernel_state_->title_id();
}
std::unordered_set<uint32_t> title_ids = {title_id};
if (content_type == XContentType::kPublisher) {
title_ids = FindPublisherTitleIds(title_id);
title_ids = FindPublisherTitleIds(xuid, title_id);
}
for (const uint32_t& title_id : title_ids) {
// Search path:
// content_root/title_id/type_name/*
auto package_root = ResolvePackageRoot(content_type, title_id);
// content_root/xuid/title_id/type_name/*
auto package_root = ResolvePackageRoot(xuid, title_id, content_type);
auto file_infos = xe::filesystem::ListFiles(package_root);
for (const auto& file_info : file_infos) {
@ -186,8 +192,8 @@ std::vector<XCONTENT_AGGREGATE_DATA> ContentManager::ListContent(
XCONTENT_AGGREGATE_DATA content_data;
if (XSUCCEEDED(ReadContentHeaderFile(xe::path_to_utf8(file_info.name),
content_type, content_data,
title_id))) {
xuid, title_id, content_type,
content_data))) {
result.emplace_back(std::move(content_data));
} else {
content_data.device_id = device_id;
@ -195,6 +201,7 @@ std::vector<XCONTENT_AGGREGATE_DATA> ContentManager::ListContent(
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;
content_data.xuid = xuid;
result.emplace_back(std::move(content_data));
}
}
@ -203,9 +210,9 @@ std::vector<XCONTENT_AGGREGATE_DATA> ContentManager::ListContent(
}
std::unique_ptr<ContentPackage> ContentManager::ResolvePackage(
const std::string_view root_name, const XCONTENT_AGGREGATE_DATA& data,
const uint32_t disc_number) {
auto package_path = ResolvePackagePath(data, disc_number);
const std::string_view root_name, const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data, const uint32_t disc_number) {
auto package_path = ResolvePackagePath(xuid, data, disc_number);
if (!std::filesystem::exists(package_path)) {
return nullptr;
}
@ -217,15 +224,16 @@ std::unique_ptr<ContentPackage> ContentManager::ResolvePackage(
return package;
}
bool ContentManager::ContentExists(const XCONTENT_AGGREGATE_DATA& data) {
auto path = ResolvePackagePath(data);
bool ContentManager::ContentExists(const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data) {
auto path = ResolvePackagePath(xuid, data);
return std::filesystem::exists(path);
}
X_RESULT ContentManager::WriteContentHeaderFile(
const XCONTENT_AGGREGATE_DATA* data) {
const uint64_t xuid, const XCONTENT_AGGREGATE_DATA* data) {
auto header_path = ResolvePackageHeaderPath(
data->file_name(), data->content_type, data->title_id);
data->file_name(), xuid, data->title_id, data->content_type);
auto parent_path = header_path.parent_path();
if (!std::filesystem::exists(parent_path)) {
@ -245,12 +253,12 @@ X_RESULT ContentManager::WriteContentHeaderFile(
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) {
X_RESULT ContentManager::ReadContentHeaderFile(
const std::string_view file_name, const uint64_t xuid,
const uint32_t title_id, XContentType content_type,
XCONTENT_AGGREGATE_DATA& data) const {
auto header_file_path =
ResolvePackageHeaderPath(file_name, content_type, title_id);
ResolvePackageHeaderPath(file_name, xuid, title_id, content_type);
constexpr uint32_t header_size = sizeof(XCONTENT_AGGREGATE_DATA);
if (std::filesystem::exists(header_file_path)) {
@ -275,15 +283,15 @@ X_RESULT ContentManager::ReadContentHeaderFile(const std::string_view file_name,
// usually requires title_id to be provided
// Kinda simple workaround for that, but still assumption
data.title_id = title_id;
data.xuid = kernel_state_->xam_state()
->GetUserProfile(static_cast<uint32_t>(0))
->xuid();
data.xuid = xuid;
return X_STATUS_SUCCESS;
}
return X_STATUS_NO_SUCH_FILE;
}
X_RESULT ContentManager::CreateContent(const std::string_view root_name,
const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data) {
auto global_lock = global_critical_region_.Acquire();
@ -292,7 +300,7 @@ X_RESULT ContentManager::CreateContent(const std::string_view root_name,
return X_ERROR_ALREADY_EXISTS;
}
auto package_path = ResolvePackagePath(data);
auto package_path = ResolvePackagePath(xuid, data);
if (std::filesystem::exists(package_path)) {
// Exists, must not!
return X_ERROR_ALREADY_EXISTS;
@ -302,7 +310,7 @@ X_RESULT ContentManager::CreateContent(const std::string_view root_name,
return X_ERROR_ACCESS_DENIED;
}
auto package = ResolvePackage(root_name, data);
auto package = ResolvePackage(root_name, xuid, data);
assert_not_null(package);
open_packages_.insert({string_key::create(root_name), package.release()});
@ -311,6 +319,7 @@ X_RESULT ContentManager::CreateContent(const std::string_view root_name,
}
X_RESULT ContentManager::OpenContent(const std::string_view root_name,
const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data,
const uint32_t disc_number) {
auto global_lock = global_critical_region_.Acquire();
@ -320,14 +329,14 @@ X_RESULT ContentManager::OpenContent(const std::string_view root_name,
return X_ERROR_ALREADY_EXISTS;
}
auto package_path = ResolvePackagePath(data, disc_number);
auto package_path = ResolvePackagePath(xuid, data, disc_number);
if (!std::filesystem::exists(package_path)) {
// Does not exist, must be created.
return X_ERROR_FILE_NOT_FOUND;
}
// Open package.
auto package = ResolvePackage(root_name, data, disc_number);
auto package = ResolvePackage(root_name, xuid, data, disc_number);
assert_not_null(package);
open_packages_.insert({string_key::create(root_name), package.release()});
@ -352,9 +361,11 @@ X_RESULT ContentManager::CloseContent(const std::string_view root_name) {
}
X_RESULT ContentManager::GetContentThumbnail(
const XCONTENT_AGGREGATE_DATA& data, std::vector<uint8_t>* buffer) {
const uint64_t xuid, const XCONTENT_AGGREGATE_DATA& data,
std::vector<uint8_t>* buffer) {
auto global_lock = global_critical_region_.Acquire();
auto package_path = ResolvePackagePath(data);
auto package_path = ResolvePackagePath(xuid, data);
auto thumb_path = package_path / kThumbnailFileName;
if (std::filesystem::exists(thumb_path)) {
auto file = xe::filesystem::OpenFile(thumb_path, "rb");
@ -369,9 +380,10 @@ X_RESULT ContentManager::GetContentThumbnail(
}
X_RESULT ContentManager::SetContentThumbnail(
const XCONTENT_AGGREGATE_DATA& data, std::vector<uint8_t> buffer) {
const uint64_t xuid, const XCONTENT_AGGREGATE_DATA& data,
std::vector<uint8_t> buffer) {
auto global_lock = global_critical_region_.Acquire();
auto package_path = ResolvePackagePath(data);
auto package_path = ResolvePackagePath(xuid, data);
std::filesystem::create_directories(package_path);
if (std::filesystem::exists(package_path)) {
auto thumb_path = package_path / kThumbnailFileName;
@ -384,7 +396,8 @@ X_RESULT ContentManager::SetContentThumbnail(
}
}
X_RESULT ContentManager::DeleteContent(const XCONTENT_AGGREGATE_DATA& data) {
X_RESULT ContentManager::DeleteContent(const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data) {
auto global_lock = global_critical_region_.Acquire();
if (IsContentOpen(data)) {
@ -392,7 +405,7 @@ X_RESULT ContentManager::DeleteContent(const XCONTENT_AGGREGATE_DATA& data) {
return X_ERROR_ACCESS_DENIED;
}
auto package_path = ResolvePackagePath(data);
auto package_path = ResolvePackagePath(xuid, data);
if (std::filesystem::remove_all(package_path) > 0) {
return X_ERROR_SUCCESS;
} else {
@ -400,15 +413,13 @@ X_RESULT ContentManager::DeleteContent(const XCONTENT_AGGREGATE_DATA& data) {
}
}
std::filesystem::path ContentManager::ResolveGameUserContentPath() {
std::filesystem::path ContentManager::ResolveGameUserContentPath(
const uint64_t xuid) {
auto xuid_str = fmt::format("{:016X}", xuid);
auto title_id = fmt::format("{:08X}", kernel_state_->title_id());
auto user_name = xe::to_path(kernel_state_->xam_state()
->GetUserProfile(static_cast<uint32_t>(0))
->name());
// Per-game per-profile data location:
// content_root/title_id/profile/user_name
return root_path_ / title_id / kGameUserContentDirName / user_name;
return root_path_ / xuid_str / kDashboardStringID / "00010000" / xuid_str /
title_id;
}
bool ContentManager::IsContentOpen(const XCONTENT_AGGREGATE_DATA& data) const {

View File

@ -142,45 +142,52 @@ class ContentManager {
const std::filesystem::path& root_path);
~ContentManager();
std::vector<XCONTENT_AGGREGATE_DATA> ListContent(uint32_t device_id,
XContentType content_type,
uint32_t title_id = -1);
std::vector<XCONTENT_AGGREGATE_DATA> ListContent(
const uint32_t device_id, const uint64_t xuid, const uint32_t title_id,
const XContentType content_type) const;
std::unique_ptr<ContentPackage> ResolvePackage(
const std::string_view root_name, const XCONTENT_AGGREGATE_DATA& data,
const uint32_t disc_number = -1);
const std::string_view root_name, const uint64_t xuid,
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);
bool ContentExists(const uint64_t xuid, const XCONTENT_AGGREGATE_DATA& data);
X_RESULT WriteContentHeaderFile(const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA* data_raw);
X_RESULT ReadContentHeaderFile(const std::string_view file_name,
const uint64_t xuid, const uint32_t title_id,
XContentType content_type,
XCONTENT_AGGREGATE_DATA& data,
const uint32_t title_id = -1);
X_RESULT CreateContent(const std::string_view root_name,
XCONTENT_AGGREGATE_DATA& data) const;
X_RESULT CreateContent(const std::string_view root_name, const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data);
X_RESULT OpenContent(const std::string_view root_name,
X_RESULT OpenContent(const std::string_view root_name, const uint64_t xuid,
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,
X_RESULT GetContentThumbnail(const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data,
std::vector<uint8_t>* buffer);
X_RESULT SetContentThumbnail(const XCONTENT_AGGREGATE_DATA& data,
X_RESULT SetContentThumbnail(const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data,
std::vector<uint8_t> buffer);
X_RESULT DeleteContent(const XCONTENT_AGGREGATE_DATA& data);
std::filesystem::path ResolveGameUserContentPath();
X_RESULT DeleteContent(const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data);
std::filesystem::path ResolveGameUserContentPath(const uint64_t xuid);
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,
std::filesystem::path ResolvePackageRoot(
const uint64_t xuid, const uint32_t title_id,
const XContentType content_type) const;
std::filesystem::path ResolvePackagePath(const uint64_t xuid,
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);
const std::string_view file_name, uint64_t xuid, uint32_t title_id,
const XContentType content_type) const;
std::unordered_set<uint32_t> FindPublisherTitleIds(
const uint64_t xuid,
uint32_t base_title_id = kCurrentlyRunningTitleId) const;
KernelState* kernel_state_;

View File

@ -0,0 +1,534 @@
/**
******************************************************************************
* 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/profile_manager.h"
#include <filesystem>
#include <vector>
#include "third_party/fmt/include/fmt/format.h"
#include "xenia/base/logging.h"
#include "xenia/emulator.h"
#include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/util/crypto_utils.h"
#include "xenia/vfs/devices/host_path_device.h"
DEFINE_string(logged_profile_slot_0_xuid, "",
"XUID of the profile to load on boot in slot 0", "Profiles");
DEFINE_string(logged_profile_slot_1_xuid, "",
"XUID of the profile to load on boot in slot 1", "Profiles");
DEFINE_string(logged_profile_slot_2_xuid, "",
"XUID of the profile to load on boot in slot 2", "Profiles");
DEFINE_string(logged_profile_slot_3_xuid, "",
"XUID of the profile to load on boot in slot 3", "Profiles");
namespace xe {
namespace kernel {
namespace xam {
bool ProfileManager::DecryptAccountFile(const uint8_t* data,
X_XAMACCOUNTINFO* output, bool devkit) {
const uint8_t* key = util::GetXeKey(0x19, devkit);
if (!key) {
return false; // this shouldn't happen...
}
// Generate RC4 key from data hash
uint8_t rc4_key[0x14];
util::HmacSha(key, 0x10, data, 0x10, 0, 0, 0, 0, rc4_key, 0x14);
uint8_t dec_data[sizeof(X_XAMACCOUNTINFO) + 8];
// Decrypt data
util::RC4(rc4_key, 0x10, data + 0x10, sizeof(dec_data), dec_data,
sizeof(dec_data));
// Verify decrypted data against hash
uint8_t data_hash[0x14];
util::HmacSha(key, 0x10, dec_data, sizeof(dec_data), 0, 0, 0, 0, data_hash,
0x14);
if (std::memcmp(data, data_hash, 0x10) == 0) {
// Copy account data to output
std::memcpy(output, dec_data + 8, sizeof(X_XAMACCOUNTINFO));
// Swap gamertag endian
xe::copy_and_swap<char16_t>(output->gamertag, output->gamertag, 0x10);
return true;
}
return false;
}
void ProfileManager::EncryptAccountFile(const X_XAMACCOUNTINFO* input,
uint8_t* output, bool devkit) {
const uint8_t* key = util::GetXeKey(0x19, devkit);
if (!key) {
return; // this shouldn't happen...
}
X_XAMACCOUNTINFO* output_acct =
reinterpret_cast<X_XAMACCOUNTINFO*>(output + 0x18);
std::memcpy(output_acct, input, sizeof(X_XAMACCOUNTINFO));
// Swap gamertag endian
xe::copy_and_swap<char16_t>(output_acct->gamertag, output_acct->gamertag,
0x10);
// Set confounder, should be random but meh
std::memset(output + 0x10, 0xFD, 8);
// Encrypted data = xam account info + 8 byte confounder
uint32_t enc_data_size = sizeof(X_XAMACCOUNTINFO) + 8;
// Set data hash
uint8_t data_hash[0x14];
util::HmacSha(key, 0x10, output + 0x10, enc_data_size, 0, 0, 0, 0, data_hash,
0x14);
std::memcpy(output, data_hash, 0x10);
// Generate RC4 key from data hash
uint8_t rc4_key[0x14];
util::HmacSha(key, 0x10, data_hash, 0x10, 0, 0, 0, 0, rc4_key, 0x14);
// Encrypt data
util::RC4(rc4_key, 0x10, output + 0x10, enc_data_size, output + 0x10,
enc_data_size);
}
ProfileManager::ProfileManager(KernelState* kernel_state)
: kernel_state_(kernel_state) {
logged_profiles_.clear();
accounts_.clear();
LoadAccounts(FindProfiles());
if (!cvars::logged_profile_slot_0_xuid.empty()) {
Login(xe::string_util::from_string<uint64_t>(
cvars::logged_profile_slot_0_xuid, true),
0);
}
if (!cvars::logged_profile_slot_1_xuid.empty()) {
Login(xe::string_util::from_string<uint64_t>(
cvars::logged_profile_slot_1_xuid, true),
1);
}
if (!cvars::logged_profile_slot_2_xuid.empty()) {
Login(xe::string_util::from_string<uint64_t>(
cvars::logged_profile_slot_2_xuid, true),
2);
}
if (!cvars::logged_profile_slot_3_xuid.empty()) {
Login(xe::string_util::from_string<uint64_t>(
cvars::logged_profile_slot_3_xuid, true),
3);
}
}
ProfileManager::~ProfileManager() {}
void ProfileManager::ReloadProfiles() { LoadAccounts(FindProfiles()); }
UserProfile* ProfileManager::GetProfile(const uint64_t xuid) const {
const uint8_t user_index = GetUserIndexAssignedToProfile(xuid);
if (user_index >= XUserMaxUserCount) {
return nullptr;
}
return GetProfile(user_index);
}
UserProfile* ProfileManager::GetProfile(uint8_t user_index) const {
if (user_index == XUserIndexNone) {
return nullptr;
}
if (user_index == XUserIndexAny) {
for (uint8_t i = 0; i < XUserMaxUserCount; i++) {
if (!logged_profiles_.count(i)) {
continue;
}
return logged_profiles_.at(i).get();
}
}
if (!logged_profiles_.count(user_index)) {
return nullptr;
}
return logged_profiles_.at(user_index).get();
}
bool ProfileManager::LoadAccount(const uint64_t xuid) {
const std::string xuid_as_string = fmt::format("{:016X}", xuid);
XELOGI("{}: Loading Account: {}", __func__, xuid_as_string);
MountProfile(xuid);
const std::string guest_path = xuid_as_string + ":\\Account";
xe::vfs::File* output_file;
xe::vfs::FileAction action = {};
auto status = kernel_state_->file_system()->OpenFile(
nullptr, guest_path, xe::vfs::FileDisposition::kOpen,
xe::vfs::FileAccess::kFileReadData, false, true, &output_file, &action);
if (XFAILED(status) || !output_file || !output_file->entry()) {
XELOGI("{}: Failed to open Account file: {:08X}", __func__, status);
DismountProfile(xuid);
return false;
}
std::vector<uint8_t> file_data;
file_data.resize(output_file->entry()->size());
size_t bytes_read = 0;
output_file->ReadSync(file_data.data(), output_file->entry()->size(), 0,
&bytes_read);
output_file->Destroy();
if (bytes_read < sizeof(X_XAMACCOUNTINFO)) {
DismountProfile(xuid);
return false;
}
X_XAMACCOUNTINFO tmp_acct;
if (!ProfileManager::DecryptAccountFile(file_data.data(), &tmp_acct)) {
if (!ProfileManager::DecryptAccountFile(file_data.data(), &tmp_acct,
true)) {
XELOGW("Failed to decrypt account data file for XUID: {}",
xuid_as_string);
return false;
}
}
// We don't need profile to be mounted anymore, so for now let's close it.
// We need it only when we want to login into this account!
DismountProfile(xuid);
accounts_.insert({xuid, tmp_acct});
return true;
}
void ProfileManager::LoadAccounts(const std::vector<uint64_t> profiles_xuids) {
for (const auto& path : profiles_xuids) {
LoadAccount(path);
}
}
bool ProfileManager::MountProfile(const uint64_t xuid) {
std::filesystem::path profile_path = GetProfilePath(xuid);
std::string mount_path = fmt::format("{:016X}", xuid) + ':';
auto device =
std::make_unique<vfs::HostPathDevice>(mount_path, profile_path, false);
if (!device->Initialize()) {
XELOGE(
"MountProfile: Unable to mount {} profile; file not found or "
"corrupted.",
xe::path_to_utf8(profile_path));
return false;
}
return kernel_state_->file_system()->RegisterDevice(std::move(device));
}
bool ProfileManager::DismountProfile(const uint64_t xuid) {
return kernel_state_->file_system()->UnregisterDevice(
fmt::format("{:016X}", xuid) + ':');
}
void ProfileManager::Login(const uint64_t xuid, const uint8_t user_index) {
if (logged_profiles_.size() >= 4 && user_index >= XUserMaxUserCount) {
XELOGE(
"Cannot login account with XUID: {:016X} due to lack of free slots "
"(Max 4 accounts at once)",
xuid);
return;
}
if (user_index < XUserMaxUserCount) {
const auto& profile = logged_profiles_.find(user_index);
if (profile != logged_profiles_.cend()) {
if (profile->second && profile->second->xuid() == xuid) {
// Do nothing! User is already signed in to that slot.
return;
}
}
}
// Find if xuid is already logged in. We might want to logout.
for (auto& logged_profile : logged_profiles_) {
if (logged_profile.second->xuid() == xuid) {
Logout(logged_profile.first);
}
}
if (!accounts_.count(xuid)) {
return;
}
auto& profile = accounts_[xuid];
const uint8_t assigned_user_slot =
user_index < XUserMaxUserCount ? user_index : FindFirstFreeProfileSlot();
XELOGI("Loaded {} (GUID: {:016X}) to slot {}", profile.GetGamertagString(),
xuid, assigned_user_slot);
logged_profiles_[assigned_user_slot] =
std::make_unique<UserProfile>(xuid, &profile);
kernel_state_->BroadcastNotification(kXNotificationIDSystemSignInChanged,
GetUsedUserSlots().to_ulong());
UpdateConfig(xuid, assigned_user_slot);
}
void ProfileManager::Logout(const uint8_t user_index) {
auto profile = logged_profiles_.find(user_index);
if (profile == logged_profiles_.cend()) {
return;
}
DismountProfile(profile->second->xuid());
logged_profiles_.erase(profile);
kernel_state_->BroadcastNotification(kXNotificationIDSystemSignInChanged,
GetUsedUserSlots().to_ulong());
UpdateConfig(0, user_index);
}
std::vector<uint64_t> ProfileManager::FindProfiles() const {
// Info: Profile directory name is also it's offline xuid
std::vector<uint64_t> profiles_xuids;
auto profiles_directory = xe::filesystem::FilterByName(
xe::filesystem::ListDirectories(
kernel_state_->emulator()->content_root()),
std::regex("[0-9A-F]{16}"));
for (const auto& profile : profiles_directory) {
const std::string profile_xuid = xe::path_to_utf8(profile.name);
if (profile_xuid == fmt::format("{:016X}", 0)) {
continue;
}
if (!std::filesystem::exists(
profile.path / profile.name / kDashboardStringID /
fmt::format("{:08X}", XContentType::kProfile) / profile.name)) {
XELOGE("Profile {} doesn't have profile package!", profile_xuid);
continue;
}
XELOGE("{}: Adding profile {} to profile list", __func__, profile_xuid);
profiles_xuids.push_back(
xe::string_util::from_string<uint64_t>(profile_xuid, true));
}
XELOGE("ProfileManager: Found {} Profiles", profiles_xuids.size());
return profiles_xuids;
}
uint8_t ProfileManager::FindFirstFreeProfileSlot() const {
if (!IsAnyProfileSignedIn()) {
return 0;
}
std::bitset<XUserMaxUserCount> used_slots = {};
for (const auto& [index, entry] : logged_profiles_) {
used_slots.set(index);
}
for (uint8_t i = 0; i < used_slots.size(); ++i) {
if (!used_slots[i]) {
return i;
}
}
return -1;
}
std::bitset<XUserMaxUserCount> ProfileManager::GetUsedUserSlots() const {
std::bitset<XUserMaxUserCount> used_slots = {};
for (const auto& [index, entry] : logged_profiles_) {
if (!entry) {
continue;
}
used_slots.set(index);
}
return used_slots;
}
uint8_t ProfileManager::GetUserIndexAssignedToProfile(
const uint64_t xuid) const {
for (const auto& [index, entry] : logged_profiles_) {
if (!entry) {
continue;
}
if (entry->xuid() != xuid) {
continue;
}
return index;
}
return -1;
}
std::filesystem::path ProfileManager::GetProfileContentPath(
const uint64_t xuid, const uint32_t title_id) const {
std::filesystem::path profile_content_path =
kernel_state_->emulator()->content_root() / fmt::format("{:016X}", xuid);
if (title_id != -1 && title_id != 0) {
profile_content_path =
profile_content_path / fmt::format("{:08X}", title_id);
}
return profile_content_path;
}
std::filesystem::path ProfileManager::GetProfilePath(
const uint64_t xuid) const {
return GetProfilePath(fmt::format("{:016X}", xuid));
}
std::filesystem::path ProfileManager::GetProfilePath(
const std::string xuid) const {
return kernel_state_->emulator()->content_root() / xuid / kDashboardStringID /
fmt::format("{:08X}", XContentType::kProfile) / xuid;
}
bool ProfileManager::CreateProfile(const std::string gamertag,
bool default_xuid) {
const auto xuid = !default_xuid ? GenerateXuid() : 0xB13EBABEBABEBABE;
if (!std::filesystem::create_directories(GetProfilePath(xuid))) {
return false;
}
if (!MountProfile(xuid)) {
return false;
}
const bool is_account_created = CreateAccount(xuid, gamertag);
if (is_account_created && default_xuid) {
Login(xuid);
}
return is_account_created;
}
bool ProfileManager::CreateAccount(const uint64_t xuid,
const std::string gamertag) {
const std::string guest_path =
xe::string_util::to_hex_string(xuid) + ":\\Account";
xe::vfs::File* output_file;
xe::vfs::FileAction action = {};
auto status = kernel_state_->file_system()->OpenFile(
nullptr, guest_path, xe::vfs::FileDisposition::kCreate,
xe::vfs::FileAccess::kFileWriteData, false, true, &output_file, &action);
if (XFAILED(status) || !output_file || !output_file->entry()) {
XELOGI("{}: Failed to open Account file for creation: {:08X}", __func__,
status);
DismountProfile(xuid);
return false;
}
X_XAMACCOUNTINFO account = {};
std::u16string gamertag_u16 = xe::to_utf16(gamertag);
string_util::copy_truncating(account.gamertag, gamertag_u16,
sizeof(account.gamertag));
std::vector<uint8_t> encrypted_data;
encrypted_data.resize(sizeof(X_XAMACCOUNTINFO) + 0x18);
EncryptAccountFile(&account, encrypted_data.data());
size_t written_bytes = 0;
output_file->WriteSync(encrypted_data.data(), encrypted_data.size(), 0,
&written_bytes);
output_file->Destroy();
DismountProfile(xuid);
accounts_.insert({xuid, account});
return true;
}
void ProfileManager::UpdateConfig(const uint64_t xuid, const uint8_t slot) {
if (slot >= XUserMaxUserCount) {
return;
}
const std::string hex_xuid = xe::string_util::to_hex_string(xuid);
switch (slot) {
case 0:
OVERRIDE_string(logged_profile_slot_0_xuid, hex_xuid);
break;
case 1:
OVERRIDE_string(logged_profile_slot_1_xuid, hex_xuid);
break;
case 2:
OVERRIDE_string(logged_profile_slot_2_xuid, hex_xuid);
break;
case 3:
OVERRIDE_string(logged_profile_slot_3_xuid, hex_xuid);
break;
default:
break;
}
return;
}
bool ProfileManager::DeleteProfile(const uint64_t xuid) {
const uint8_t user_index = GetUserIndexAssignedToProfile(xuid);
if (user_index < XUserMaxUserCount) {
Logout(user_index);
}
DismountProfile(xuid);
if (accounts_.count(xuid)) {
accounts_.erase(xuid);
}
std::error_code ec;
std::filesystem::remove_all(GetProfileContentPath(xuid), ec);
if (ec) {
XELOGE("Cannot remove profile: {}", ec.message());
return false;
}
return true;
}
bool ProfileManager::IsGamertagValid(const std::string gamertag) {
if (gamertag.empty()) {
return false;
}
if (gamertag.length() > 15) {
return false;
}
// Gamertag cannot start with a number.
if (std::isdigit(gamertag.at(0))) {
return false;
}
return std::find_if(gamertag.cbegin(), gamertag.cend(), [](char c) {
return !(std::isalnum(c) || (c == ' '));
}) == gamertag.cend();
}
} // namespace xam
} // namespace kernel
} // namespace xe

View File

@ -0,0 +1,142 @@
/**
******************************************************************************
* 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_PROFILE_MANAGER_H_
#define XENIA_KERNEL_XAM_PROFILE_MANAGER_H_
#include <bitset>
#include <random>
#include <string>
#include <vector>
#include "third_party/fmt/include/fmt/format.h"
#include "xenia/base/string.h"
#include "xenia/kernel/xam/user_profile.h"
#include "xenia/xbox.h"
namespace xe {
namespace kernel {
class KernelState;
} // namespace kernel
} // namespace xe
namespace xe {
namespace kernel {
namespace xam {
constexpr uint32_t kDashboardID = 0xFFFE07D1;
const static std::string kDashboardStringID =
fmt::format("{:08X}", kDashboardID);
enum class XTileType {
kAchievement,
kGameIcon,
kGamerTile,
kGamerTileSmall,
kLocalGamerTile,
kLocalGamerTileSmall,
kBkgnd,
kAwardedGamerTile,
kAwardedGamerTileSmall,
kGamerTileByImageId,
kPersonalGamerTile,
kPersonalGamerTileSmall,
kGamerTileByKey,
kAvatarGamerTile,
kAvatarGamerTileSmall,
kAvatarFullBody
};
// TODO: find filenames of other tile types that are stored in profile
static const std::map<XTileType, std::string> kTileFileNames = {
{XTileType::kPersonalGamerTile, "tile_64.png"},
{XTileType::kPersonalGamerTileSmall, "tile_32.png"},
{XTileType::kAvatarGamerTile, "avtr_64.png"},
{XTileType::kAvatarGamerTileSmall, "avtr_32.png"},
};
class ProfileManager {
public:
static bool DecryptAccountFile(const uint8_t* data, X_XAMACCOUNTINFO* output,
bool devkit = false);
static void EncryptAccountFile(const X_XAMACCOUNTINFO* input, uint8_t* output,
bool devkit = false);
// Profile:
// - Account
// - GPDs (Dashboard, titles)
// Loading Profile means load everything
// Loading Account means load basic data
ProfileManager(KernelState* kernel_state);
~ProfileManager();
bool CreateProfile(const std::string gamertag, bool default_xuid = false);
// bool CreateProfile(const X_XAMACCOUNTINFO* account_info);
bool DeleteProfile(const uint64_t xuid);
bool MountProfile(const uint64_t xuid);
bool DismountProfile(const uint64_t xuid);
void Login(const uint64_t xuid, const uint8_t user_index = -1);
void Logout(const uint8_t user_index);
bool LoadAccount(const uint64_t xuid);
void LoadAccounts(const std::vector<uint64_t> profiles_xuids);
void ReloadProfiles();
UserProfile* GetProfile(const uint64_t xuid) const;
UserProfile* GetProfile(const uint8_t user_index) const;
uint8_t GetUserIndexAssignedToProfile(const uint64_t xuid) const;
std::map<uint64_t, X_XAMACCOUNTINFO>* GetProfiles() { return &accounts_; }
uint32_t GetProfilesCount() const {
return static_cast<uint32_t>(accounts_.size());
}
bool IsAnyProfileSignedIn() const { return !logged_profiles_.empty(); }
std::filesystem::path GetProfileContentPath(
const uint64_t xuid, const uint32_t title_id = -1) const;
static bool IsGamertagValid(const std::string gamertag);
private:
void UpdateConfig(const uint64_t xuid, const uint8_t slot);
bool CreateAccount(const uint64_t xuid, const std::string gamertag);
std::filesystem::path GetProfilePath(const uint64_t xuid) const;
std::filesystem::path GetProfilePath(const std::string xuid) const;
std::vector<uint64_t> FindProfiles() const;
uint8_t FindFirstFreeProfileSlot() const;
std::bitset<XUserMaxUserCount> GetUsedUserSlots() const;
uint64_t GenerateXuid() const {
std::random_device rd;
std::mt19937 gen(rd());
return ((uint64_t)0xE03 << 52) + (gen() % (1 << 31));
}
std::map<uint64_t, X_XAMACCOUNTINFO> accounts_;
std::map<uint8_t, std::unique_ptr<UserProfile>> logged_profiles_;
KernelState* kernel_state_;
};
} // namespace xam
} // namespace kernel
} // namespace xe
#endif // XENIA_KERNEL_XAM_PROFILE_MANAGER_H_

View File

@ -19,15 +19,11 @@ namespace xe {
namespace kernel {
namespace xam {
UserProfile::UserProfile(uint8_t index) {
UserProfile::UserProfile(uint64_t xuid, X_XAMACCOUNTINFO* account_info)
: xuid_(xuid), account_info_(*account_info) {
// 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 + 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
@ -136,7 +132,7 @@ UserSetting* UserProfile::GetSetting(uint32_t setting_id) {
void UserProfile::LoadSetting(UserSetting* setting) {
if (setting->is_title_specific()) {
const std::filesystem::path content_dir =
kernel_state()->content_manager()->ResolveGameUserContentPath();
kernel_state()->content_manager()->ResolveGameUserContentPath(xuid_);
const std::string setting_id_str =
fmt::format("{:08X}", setting->GetSettingId());
const std::filesystem::path file_path = content_dir / setting_id_str;
@ -182,7 +178,7 @@ void UserProfile::SaveSetting(UserSetting* setting) {
if (setting->is_title_specific() &&
setting->GetSettingSource() == X_USER_PROFILE_SETTING_SOURCE::TITLE) {
const std::filesystem::path content_dir =
kernel_state()->content_manager()->ResolveGameUserContentPath();
kernel_state()->content_manager()->ResolveGameUserContentPath(xuid_);
std::filesystem::create_directories(content_dir);

View File

@ -153,10 +153,10 @@ class UserSetting {
class UserProfile {
public:
UserProfile(uint8_t index);
UserProfile(uint64_t xuid, X_XAMACCOUNTINFO* account_info);
uint64_t xuid() const { return xuid_; }
std::string name() const { return name_; }
std::string name() const { return account_info_.GetGamertagString(); }
uint32_t signin_state() const { return 1; }
uint32_t type() const { return 1 | 2; /* local | online profile? */ }
@ -170,7 +170,8 @@ class UserProfile {
private:
uint64_t xuid_;
std::string name_;
X_XAMACCOUNTINFO account_info_;
std::vector<std::unique_ptr<UserSetting>> setting_list_;
std::unordered_map<uint32_t, UserSetting*> settings_;

View File

@ -92,28 +92,66 @@ dword_result_t XamContentCreateEnumerator_entry(
*buffer_size_ptr = sizeof(XCONTENT_DATA) * items_per_enumerate;
}
uint64_t xuid = 0;
if (user_index != XUserIndexNone) {
const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index);
if (!user) {
return X_ERROR_NO_SUCH_USER;
}
xuid = user->xuid();
}
auto e = make_object<XStaticEnumerator<XCONTENT_DATA>>(kernel_state(),
items_per_enumerate);
auto result = e->Initialize(0xFF, 0xFE, 0x20005, 0x20007, 0);
auto result = e->Initialize(XUserIndexAny, 0xFE, 0x20005, 0x20007, 0);
if (XFAILED(result)) {
return result;
}
std::vector<XCONTENT_AGGREGATE_DATA> enumerated_content = {};
if (!device_info || device_info->device_id == DummyDeviceId::HDD) {
// Get all content data.
auto content_datas = kernel_state()->content_manager()->ListContent(
static_cast<uint32_t>(DummyDeviceId::HDD),
XContentType(uint32_t(content_type)));
for (const auto& content_data : content_datas) {
auto item = e->AppendItem();
*item = content_data;
if (xuid) {
auto user_enumerated_data =
kernel_state()->content_manager()->ListContent(
static_cast<uint32_t>(DummyDeviceId::HDD), xuid,
kernel_state()->title_id(), XContentType(uint32_t(content_type)));
enumerated_content.insert(enumerated_content.end(),
user_enumerated_data.cbegin(),
user_enumerated_data.cend());
}
if (!(content_flags & 0x00001000)) {
auto common_enumerated_data =
kernel_state()->content_manager()->ListContent(
static_cast<uint32_t>(DummyDeviceId::HDD), 0,
kernel_state()->title_id(), XContentType(uint32_t(content_type)));
enumerated_content.insert(enumerated_content.end(),
common_enumerated_data.cbegin(),
common_enumerated_data.cend());
}
// Remove duplicates
enumerated_content.erase(
std::unique(enumerated_content.begin(), enumerated_content.end()),
enumerated_content.end());
}
if (!device_info || device_info->device_id == DummyDeviceId::ODD) {
// TODO(gibbed): disc drive content
}
for (const auto& content_data : enumerated_content) {
auto item = e->AppendItem();
*item = content_data;
XELOGI("{}: Adding: {} (Filename: {}) to enumerator result", __func__,
xe::to_utf8(content_data.display_name()), content_data.file_name());
}
XELOGD("XamContentCreateEnumerator: added {} items to enumerator",
e->item_count());
@ -131,6 +169,17 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
lpdword_t license_mask_ptr,
dword_t cache_size, qword_t content_size,
lpvoid_t overlapped_ptr) {
uint64_t xuid = 0;
if (user_index != XUserIndexNone) {
const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index);
if (!user) {
return X_ERROR_NO_SUCH_USER;
}
xuid = user->xuid();
}
XCONTENT_AGGREGATE_DATA content_data;
if (content_data_size == sizeof(XCONTENT_DATA)) {
content_data = *content_data_ptr.as<XCONTENT_DATA*>();
@ -141,13 +190,17 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
return X_ERROR_INVALID_PARAMETER;
}
if (content_data.content_type == XContentType::kMarketplaceContent) {
xuid = 0;
}
auto content_manager = kernel_state()->content_manager();
if (overlapped_ptr && disposition_ptr) {
*disposition_ptr = 0;
}
auto run = [content_manager, root_name = root_name.value(), flags,
auto run = [content_manager, xuid, root_name = root_name.value(), flags,
content_data, disposition_ptr, license_mask_ptr, overlapped_ptr](
uint32_t& extended_error, uint32_t& length) -> X_RESULT {
X_RESULT result = X_ERROR_INVALID_PARAMETER;
@ -155,7 +208,7 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
switch (flags & 0xF) {
case 1: // CREATE_NEW
// Fail if exists.
if (content_manager->ContentExists(content_data)) {
if (content_manager->ContentExists(xuid, content_data)) {
result = X_ERROR_ALREADY_EXISTS;
} else {
disposition = kDispositionState::Create;
@ -163,14 +216,14 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
break;
case 2: // CREATE_ALWAYS
// Overwrite existing, if any.
if (content_manager->ContentExists(content_data)) {
content_manager->DeleteContent(content_data);
if (content_manager->ContentExists(xuid, content_data)) {
content_manager->DeleteContent(xuid, content_data);
}
disposition = kDispositionState::Create;
break;
case 3: // OPEN_EXISTING
// Open only if exists.
if (!content_manager->ContentExists(content_data)) {
if (!content_manager->ContentExists(xuid, content_data)) {
result = X_ERROR_PATH_NOT_FOUND;
} else {
disposition = kDispositionState::Open;
@ -178,7 +231,7 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
break;
case 4: // OPEN_ALWAYS
// Create if needed.
if (!content_manager->ContentExists(content_data)) {
if (!content_manager->ContentExists(xuid, content_data)) {
disposition = kDispositionState::Create;
} else {
disposition = kDispositionState::Open;
@ -186,10 +239,10 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
break;
case 5: // TRUNCATE_EXISTING
// Fail if doesn't exist, if does exist delete and recreate.
if (!content_manager->ContentExists(content_data)) {
if (!content_manager->ContentExists(xuid, content_data)) {
result = X_ERROR_PATH_NOT_FOUND;
} else {
content_manager->DeleteContent(content_data);
content_manager->DeleteContent(xuid, content_data);
disposition = kDispositionState::Create;
}
break;
@ -199,12 +252,12 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
}
if (disposition == kDispositionState::Create) {
result = content_manager->CreateContent(root_name, content_data);
result = content_manager->CreateContent(root_name, xuid, content_data);
if (XSUCCEEDED(result)) {
content_manager->WriteContentHeaderFile(&content_data);
content_manager->WriteContentHeaderFile(xuid, &content_data);
}
} else if (disposition == kDispositionState::Open) {
result = content_manager->OpenContent(root_name, content_data);
result = content_manager->OpenContent(root_name, xuid, content_data);
}
if (license_mask_ptr && XSUCCEEDED(result)) {
@ -265,7 +318,7 @@ 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) {
return xeXamContentCreate(0xFE, root_name, content_data_ptr,
return xeXamContentCreate(XUserIndexNone, root_name, content_data_ptr,
sizeof(XCONTENT_AGGREGATE_DATA), flags,
disposition_ptr, license_mask_ptr, cache_size,
content_size, overlapped_ptr);
@ -319,15 +372,21 @@ dword_result_t XamContentGetCreator_entry(dword_t user_index,
return X_ERROR_INVALID_PARAMETER;
}
const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index);
if (!user) {
return X_ERROR_NO_SUCH_USER;
}
XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();
auto run = [content_data, user_index, is_creator_ptr, creator_xuid_ptr,
overlapped_ptr](uint32_t& extended_error,
uint32_t& length) -> X_RESULT {
auto run = [content_data, xuid = user->xuid(), user_index, is_creator_ptr,
creator_xuid_ptr, overlapped_ptr](uint32_t& extended_error,
uint32_t& length) -> X_RESULT {
X_RESULT result = X_ERROR_SUCCESS;
bool content_exists =
kernel_state()->content_manager()->ContentExists(content_data);
kernel_state()->content_manager()->ContentExists(xuid, content_data);
if (content_exists) {
if (content_data.content_type == XContentType::kSavedGame) {
@ -377,6 +436,12 @@ dword_result_t XamContentGetThumbnail_entry(dword_t user_index,
lpvoid_t buffer_ptr,
lpdword_t buffer_size_ptr,
lpunknown_t overlapped_ptr) {
const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index);
if (!user) {
return X_ERROR_NO_SUCH_USER;
}
assert_not_null(buffer_size_ptr);
uint32_t buffer_size = *buffer_size_ptr;
XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();
@ -384,7 +449,7 @@ dword_result_t XamContentGetThumbnail_entry(dword_t user_index,
// Get thumbnail (if it exists).
std::vector<uint8_t> buffer;
auto result = kernel_state()->content_manager()->GetContentThumbnail(
content_data, &buffer);
user->xuid(), content_data, &buffer);
*buffer_size_ptr = uint32_t(buffer.size());
@ -416,13 +481,19 @@ dword_result_t XamContentSetThumbnail_entry(dword_t user_index,
lpvoid_t buffer_ptr,
dword_t buffer_size,
lpunknown_t overlapped_ptr) {
const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index);
if (!user) {
return X_ERROR_NO_SUCH_USER;
}
XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();
// Buffer is PNG data.
auto buffer = std::vector<uint8_t>((uint8_t*)buffer_ptr,
(uint8_t*)buffer_ptr + buffer_size);
auto result = kernel_state()->content_manager()->SetContentThumbnail(
content_data, std::move(buffer));
user->xuid(), content_data, std::move(buffer));
if (overlapped_ptr) {
kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result);
@ -436,9 +507,20 @@ DECLARE_XAM_EXPORT1(XamContentSetThumbnail, kContent, kImplemented);
dword_result_t XamContentDelete_entry(dword_t user_index,
lpvoid_t content_data_ptr,
lpunknown_t overlapped_ptr) {
if (user_index >= XUserMaxUserCount) {
return X_ERROR_ACCESS_DENIED;
}
const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index);
if (!user) {
return X_ERROR_NO_SUCH_USER;
}
XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();
auto result = kernel_state()->content_manager()->DeleteContent(content_data);
auto result = kernel_state()->content_manager()->DeleteContent(user->xuid(),
content_data);
if (overlapped_ptr) {
kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result);
@ -453,7 +535,8 @@ dword_result_t XamContentDeleteInternal_entry(lpvoid_t content_data_ptr,
lpunknown_t overlapped_ptr) {
// INFO: Analysis of xam.xex shows that "internal" functions are wrappers with
// 0xFE as user_index
return XamContentDelete_entry(0xFE, content_data_ptr, overlapped_ptr);
return XamContentDelete_entry(XUserIndexNone, content_data_ptr,
overlapped_ptr);
}
DECLARE_XAM_EXPORT1(XamContentDeleteInternal, kContent, kImplemented);

View File

@ -91,7 +91,7 @@ dword_result_t XamContentAggregateCreateEnumerator_entry(qword_t xuid,
auto e = make_object<XStaticEnumerator<XCONTENT_AGGREGATE_DATA>>(
kernel_state(), 1);
X_KENUMERATOR_CONTENT_AGGREGATE* extra;
auto result = e->Initialize(0xFF, 0xFE, 0x2000E, 0x20010, 0, &extra);
auto result = e->Initialize(XUserIndexAny, 0xFE, 0x2000E, 0x20010, 0, &extra);
if (XFAILED(result)) {
return result;
}
@ -115,8 +115,8 @@ dword_result_t XamContentAggregateCreateEnumerator_entry(qword_t xuid,
for (auto& title_id : title_ids) {
// Get all content data.
auto content_datas = kernel_state()->content_manager()->ListContent(
static_cast<uint32_t>(DummyDeviceId::HDD), content_type_enum,
title_id);
static_cast<uint32_t>(DummyDeviceId::HDD), xuid == -1 ? 0 : xuid,
title_id, content_type_enum);
for (const auto& content_data : content_datas) {
auto item = e->AppendItem();
assert_not_null(item);

View File

@ -159,7 +159,7 @@ dword_result_t XamContentCreateDeviceEnumerator_entry(dword_t content_type,
auto e = make_object<XStaticEnumerator<X_CONTENT_DEVICE_DATA>>(kernel_state(),
max_count);
auto result = e->Initialize(0xFE, 0xFE, 0x2000A, 0x20009, 0);
auto result = e->Initialize(XUserIndexNone, 0xFE, 0x2000A, 0x20009, 0);
if (XFAILED(result)) {
return result;
}

View File

@ -32,7 +32,7 @@ constexpr uint32_t XINPUT_FLAG_ANYDEVICE = 0xFF;
constexpr uint32_t XINPUT_FLAG_ANY_USER = 1 << 30;
dword_result_t XAutomationpUnbindController_entry(dword_t user_index) {
if (user_index > 4) {
if (user_index >= XUserMaxUserCount) {
return 0;
}
@ -79,7 +79,8 @@ dword_result_t XamInputGetCapabilitiesEx_entry(
}
uint32_t actual_user_index = user_index;
if ((actual_user_index & 0xFF) == 0xFF || (flags & XINPUT_FLAG_ANY_USER)) {
if ((actual_user_index & XUserIndexAny) == XUserIndexAny ||
(flags & XINPUT_FLAG_ANY_USER)) {
// Always pin user to 0.
actual_user_index = 0;
}
@ -105,7 +106,7 @@ dword_result_t XamInputGetState_entry(dword_t user_index, dword_t flags,
if (input_state) {
memset((void*)input_state.host_address(), 0, sizeof(X_INPUT_STATE));
}
if (user_index >= 4) {
if (user_index >= XUserMaxUserCount) {
return X_ERROR_DEVICE_NOT_CONNECTED;
}
@ -118,7 +119,8 @@ dword_result_t XamInputGetState_entry(dword_t user_index, dword_t flags,
uint32_t actual_user_index = user_index;
// chrispy: change this, logic is not right
if ((actual_user_index & 0xFF) == 0xFF || (flags & XINPUT_FLAG_ANY_USER)) {
if ((actual_user_index & XUserIndexAny) == XUserIndexAny ||
(flags & XINPUT_FLAG_ANY_USER)) {
// Always pin user to 0.
actual_user_index = 0;
}
@ -134,19 +136,13 @@ dword_result_t XamInputSetState_entry(
dword_t user_index,
dword_t flags, /* flags, as far as i can see, is not used*/
pointer_t<X_INPUT_VIBRATION> vibration) {
if (user_index >= 4) {
if (user_index >= XUserMaxUserCount) {
return X_E_DEVICE_NOT_CONNECTED;
}
if (!vibration) {
return X_ERROR_BAD_ARGUMENTS;
}
uint32_t actual_user_index = user_index;
if ((user_index & 0xFF) == 0xFF) {
// Always pin user to 0.
actual_user_index = 0;
}
auto input_system = kernel_state()->emulator()->input_system();
auto lock = input_system->lock();
return input_system->SetState(user_index, vibration);
@ -170,7 +166,8 @@ dword_result_t XamInputGetKeystroke_entry(
}
uint32_t actual_user_index = user_index;
if ((actual_user_index & 0xFF) == 0xFF || (flags & XINPUT_FLAG_ANY_USER)) {
if ((actual_user_index & XUserIndexAny) == XUserIndexAny ||
(flags & XINPUT_FLAG_ANY_USER)) {
// Always pin user to 0.
actual_user_index = 0;
}
@ -197,7 +194,7 @@ dword_result_t XamInputGetKeystrokeEx_entry(
uint32_t user_index = *user_index_ptr;
auto input_system = kernel_state()->emulator()->input_system();
auto lock = input_system->lock();
if ((user_index & 0xFF) == 0xFF) {
if ((user_index & XUserIndexAny) == XUserIndexAny) {
// Always pin user to 0.
user_index = 0;
}
@ -206,7 +203,7 @@ dword_result_t XamInputGetKeystrokeEx_entry(
// That flag means we should iterate over every connected controller and
// check which one have pending request.
auto result = X_ERROR_DEVICE_NOT_CONNECTED;
for (uint32_t i = 0; i < 4; i++) {
for (uint32_t i = 0; i < XUserMaxUserCount; i++) {
auto result = input_system->GetKeystroke(i, flags, keystroke);
// Return result from first user that have pending request
@ -235,7 +232,7 @@ X_HRESULT_result_t XamUserGetDeviceContext_entry(dword_t user_index,
// set zero just to be safe.
*out_ptr = 0;
if (kernel_state()->xam_state()->IsUserSignedIn(user_index) ||
(user_index & 0xFF) == 0xFF) {
(user_index & XUserIndexAny) == XUserIndexAny) {
*out_ptr = (uint32_t)user_index;
return X_E_SUCCESS;
} else {

View File

@ -10,10 +10,6 @@
#include "xenia/kernel/xam/xam_state.h"
#include "xenia/emulator.h"
DEFINE_uint32(max_signed_profiles, 4,
"Limits how many profiles can be assigned. Possible values: 1-4",
"Kernel");
namespace xe {
namespace kernel {
namespace xam {
@ -29,8 +25,7 @@ XamState::XamState(Emulator* emulator, KernelState* kernel_state)
content_manager_ =
std::make_unique<ContentManager>(kernel_state, content_root);
user_profiles_.emplace(0, std::make_unique<xam::UserProfile>(0));
profile_manager_ = std::make_unique<ProfileManager>(kernel_state);
achievement_manager_ = std::make_unique<AchievementManager>();
AppManager::RegisterApps(kernel_state, app_manager_.get());
@ -43,47 +38,20 @@ XamState::~XamState() {
}
UserProfile* XamState::GetUserProfile(uint32_t user_index) const {
if (!IsUserSignedIn(user_index)) {
if (user_index >= XUserMaxUserCount && user_index < XUserIndexLatest) {
return nullptr;
}
return user_profiles_.at(user_index).get();
return profile_manager_->GetProfile(static_cast<uint8_t>(user_index));
}
UserProfile* XamState::GetUserProfile(uint64_t xuid) const {
for (const auto& [key, value] : user_profiles_) {
if (value->xuid() == xuid) {
return user_profiles_.at(key).get();
}
}
return nullptr;
}
void XamState::UpdateUsedUserProfiles() {
const std::bitset<4> used_slots = kernel_state_->GetConnectedUsers();
const uint32_t signed_profile_count =
std::max(static_cast<uint32_t>(1),
std::min(static_cast<uint32_t>(4), cvars::max_signed_profiles));
for (uint32_t i = 1; i < signed_profile_count; i++) {
bool is_used = used_slots.test(i);
if (IsUserSignedIn(i) && !is_used) {
user_profiles_.erase(i);
kernel_state_->BroadcastNotification(
kXNotificationIDSystemInputDevicesChanged, 0);
}
if (!IsUserSignedIn(i) && is_used) {
user_profiles_.emplace(i, std::make_unique<xam::UserProfile>(i));
kernel_state_->BroadcastNotification(
kXNotificationIDSystemInputDevicesChanged, 0);
}
}
return profile_manager_->GetProfile(xuid);
}
bool XamState::IsUserSignedIn(uint32_t user_index) const {
return user_profiles_.find(user_index) != user_profiles_.cend();
return profile_manager_->GetProfile(static_cast<uint8_t>(user_index)) !=
nullptr;
}
bool XamState::IsUserSignedIn(uint64_t xuid) const {

View File

@ -14,7 +14,7 @@
#include "xenia/kernel/xam/achievement_manager.h"
#include "xenia/kernel/xam/app_manager.h"
#include "xenia/kernel/xam/content_manager.h"
#include "xenia/kernel/xam/user_profile.h"
#include "xenia/kernel/xam/profile_manager.h"
namespace xe {
class Emulator;
@ -40,12 +40,11 @@ class XamState {
AchievementManager* achievement_manager() const {
return achievement_manager_.get();
}
ProfileManager* profile_manager() const { return profile_manager_.get(); }
UserProfile* GetUserProfile(uint32_t user_index) const;
UserProfile* GetUserProfile(uint64_t xuid) const;
void UpdateUsedUserProfiles();
bool IsUserSignedIn(uint32_t user_index) const;
bool IsUserSignedIn(uint64_t xuid) const;
@ -55,8 +54,7 @@ class XamState {
std::unique_ptr<AppManager> app_manager_;
std::unique_ptr<ContentManager> content_manager_;
std::unique_ptr<AchievementManager> achievement_manager_;
std::map<uint8_t, std::unique_ptr<UserProfile>> user_profiles_;
std::unique_ptr<ProfileManager> profile_manager_;
};
} // namespace xam

View File

@ -565,13 +565,13 @@ dword_result_t XamShowDeviceSelectorUI_entry(
return X_ERROR_INVALID_PARAMETER;
}
if ((user_index >= 4 && user_index != 0xFF) ||
if ((user_index >= XUserMaxUserCount && user_index != XUserIndexAny) ||
(content_flags & 0x83F00008) != 0 || !device_id_ptr) {
XOverlappedSetExtendedError(overlapped, X_ERROR_INVALID_PARAMETER);
return X_ERROR_INVALID_PARAMETER;
}
if (user_index != 0xFF &&
if (user_index != XUserIndexAny &&
!kernel_state()->xam_state()->IsUserSignedIn(user_index)) {
kernel_state()->CompleteOverlappedImmediate(overlapped,
X_ERROR_NO_SUCH_USER);
@ -676,7 +676,7 @@ dword_result_t XamShowMarketplaceUI_entry(dword_t user_index, dword_t ui_type,
// 1 - view content specified by offer id
// content_types:
// game specific, usually just -1
if (user_index >= 4) {
if (user_index >= XUserMaxUserCount) {
return X_ERROR_INVALID_PARAMETER;
}
@ -752,7 +752,7 @@ dword_result_t XamShowMarketplaceDownloadItemsUI_entry(
// ui_type:
// 1000 - free
// 1001 - paid
if (user_index >= 4 || !offers || num_offers > 6) {
if (user_index >= XUserMaxUserCount || !offers || num_offers > 6) {
return X_ERROR_INVALID_PARAMETER;
}
@ -819,6 +819,39 @@ dword_result_t XamShowMarketplaceDownloadItemsUI_entry(
}
DECLARE_XAM_EXPORT1(XamShowMarketplaceDownloadItemsUI, kUI, kSketchy);
dword_result_t XamShowSigninUI_entry(dword_t users_needed, dword_t unk_mask) {
// XN_SYS_UI (on)
kernel_state()->BroadcastNotification(kXNotificationIDSystemUI, 1);
// Mask values vary. Probably matching user types? Local/remote?
// Games seem to sit and loop until we trigger this notification:
auto run = [users_needed]() -> void {
uint32_t user_mask = 0;
uint32_t active_users = 0;
for (uint32_t i = 0; i < XUserMaxUserCount; i++) {
if (kernel_state()->xam_state()->IsUserSignedIn(i)) {
user_mask |= (1 << i);
active_users++;
if (active_users >= users_needed) break;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(150));
// XN_SYS_SIGNINCHANGED (players)
kernel_state()->BroadcastNotification(kXNotificationIDSystemSignInChanged,
user_mask);
// XN_SYS_UI (off)
kernel_state()->BroadcastNotification(kXNotificationIDSystemUI, 0);
};
std::thread thread(run);
thread.detach();
return X_ERROR_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamShowSigninUI, kUserProfiles, kStub);
} // namespace xam
} // namespace kernel
} // namespace xe

View File

@ -33,25 +33,32 @@ X_HRESULT_result_t XamUserGetXUID_entry(dword_t user_index, dword_t type_mask,
if (!xuid_ptr) {
return X_E_INVALIDARG;
}
*xuid_ptr = 0;
if (user_index >= XUserMaxUserCount) {
return X_E_INVALIDARG;
}
if (!kernel_state()->xam_state()->IsUserSignedIn(user_index)) {
return X_E_NO_SUCH_USER;
}
const auto& user_profile =
kernel_state()->xam_state()->GetUserProfile(user_index);
uint32_t result = X_E_NO_SUCH_USER;
uint64_t xuid = 0;
if (user_index < 4) {
if (kernel_state()->xam_state()->IsUserSignedIn(user_index)) {
const auto& user_profile =
kernel_state()->xam_state()->GetUserProfile(user_index);
auto type = user_profile->type() & type_mask;
if (type & (2 | 4)) {
// maybe online profile?
xuid = user_profile->xuid();
result = X_E_SUCCESS;
} else if (type & 1) {
// maybe offline profile?
xuid = user_profile->xuid();
result = X_E_SUCCESS;
}
}
} else {
result = X_E_INVALIDARG;
auto type = user_profile->type() & type_mask;
if (type & (2 | 4)) {
// maybe online profile?
xuid = user_profile->xuid();
result = X_E_SUCCESS;
} else if (type & 1) {
// maybe offline profile?
xuid = user_profile->xuid();
result = X_E_SUCCESS;
}
*xuid_ptr = xuid;
return result;
@ -62,12 +69,14 @@ dword_result_t XamUserGetSigninState_entry(dword_t user_index) {
// Yield, as some games spam this.
xe::threading::MaybeYield();
uint32_t signin_state = 0;
if (user_index < 4) {
if (kernel_state()->xam_state()->IsUserSignedIn(user_index)) {
const auto& user_profile =
kernel_state()->xam_state()->GetUserProfile(user_index);
signin_state = user_profile->signin_state();
}
if (user_index >= XUserMaxUserCount) {
return signin_state;
}
if (kernel_state()->xam_state()->IsUserSignedIn(user_index)) {
const auto& user_profile =
kernel_state()->xam_state()->GetUserProfile(user_index);
signin_state = user_profile->signin_state();
}
return signin_state;
}
@ -91,12 +100,10 @@ X_HRESULT_result_t XamUserGetSigninInfo_entry(
}
std::memset(info, 0, sizeof(X_USER_SIGNIN_INFO));
if (user_index > 3) {
if (user_index >= XUserMaxUserCount) {
return X_E_NO_SUCH_USER;
}
kernel_state()->xam_state()->UpdateUsedUserProfiles();
if (kernel_state()->xam_state()->IsUserSignedIn(user_index)) {
const auto& user_profile =
kernel_state()->xam_state()->GetUserProfile(user_index);
@ -113,7 +120,7 @@ DECLARE_XAM_EXPORT1(XamUserGetSigninInfo, kUserProfiles, kImplemented);
dword_result_t XamUserGetName_entry(dword_t user_index, lpstring_t buffer,
dword_t buffer_len) {
if (user_index >= 4) {
if (user_index >= XUserMaxUserCount) {
return X_ERROR_INVALID_PARAMETER;
}
@ -134,7 +141,7 @@ DECLARE_XAM_EXPORT1(XamUserGetName, kUserProfiles, kImplemented);
dword_result_t XamUserGetGamerTag_entry(dword_t user_index,
lpu16string_t buffer,
dword_t buffer_len) {
if (user_index >= 4) {
if (user_index >= XUserMaxUserCount) {
return X_E_INVALIDARG;
}
@ -231,9 +238,9 @@ uint32_t XamUserReadProfileSettingsEx(uint32_t title_id, uint32_t user_index,
return X_ERROR_INSUFFICIENT_BUFFER;
}
// Title ID = 0 means us.
// 0xfffe07d1 = profile?
if (!kernel_state()->xam_state()->IsUserSignedIn(user_index) && !xuids) {
auto user_profile = kernel_state()->xam_state()->GetUserProfile(user_index);
if (!user_profile && !xuids) {
if (overlapped) {
kernel_state()->CompleteOverlappedImmediate(
kernel_state()->memory()->HostToGuestVirtual(overlapped),
@ -243,8 +250,6 @@ uint32_t XamUserReadProfileSettingsEx(uint32_t title_id, uint32_t user_index,
return X_ERROR_NO_SUCH_USER;
}
auto user_profile = kernel_state()->xam_state()->GetUserProfile(user_index);
if (xuids) {
uint64_t user_xuid = static_cast<uint64_t>(xuids[0]);
if (!kernel_state()->xam_state()->IsUserSignedIn(user_xuid)) {
@ -259,6 +264,10 @@ uint32_t XamUserReadProfileSettingsEx(uint32_t title_id, uint32_t user_index,
user_profile = kernel_state()->xam_state()->GetUserProfile(user_xuid);
}
if (!user_profile) {
return X_ERROR_NO_SUCH_USER;
}
// First call asks for size (fill buffer_size_ptr).
// Second call asks for buffer contents with that size.
@ -355,9 +364,12 @@ dword_result_t XamUserWriteProfileSettings_entry(
if (!setting_count || !settings) {
return X_ERROR_INVALID_PARAMETER;
}
// Update and save settings.
const auto& user_profile =
kernel_state()->xam_state()->GetUserProfile(user_index);
// Skip writing data about users with id != 0 they're not supported
if (user_index > 0) {
if (!user_profile) {
if (overlapped) {
kernel_state()->CompleteOverlappedImmediate(
kernel_state()->memory()->HostToGuestVirtual(overlapped),
@ -366,9 +378,6 @@ dword_result_t XamUserWriteProfileSettings_entry(
}
return X_ERROR_SUCCESS;
}
// Update and save settings.
const auto& user_profile =
kernel_state()->xam_state()->GetUserProfile(user_index);
for (uint32_t n = 0; n < setting_count; ++n) {
const X_USER_PROFILE_SETTING& setting = settings[n];
@ -431,8 +440,8 @@ DECLARE_XAM_EXPORT1(XamUserWriteProfileSettings, kUserProfiles, kImplemented);
dword_result_t XamUserCheckPrivilege_entry(dword_t user_index, dword_t mask,
lpdword_t out_value) {
// checking all users?
if (user_index != 0xFF) {
if (user_index >= 4) {
if (user_index != XUserIndexAny) {
if (user_index >= XUserMaxUserCount) {
return X_ERROR_INVALID_PARAMETER;
}
@ -494,7 +503,7 @@ dword_result_t XamUserIsOnlineEnabled_entry(dword_t user_index) { return 1; }
DECLARE_XAM_EXPORT1(XamUserIsOnlineEnabled, kUserProfiles, kStub);
dword_result_t XamUserGetMembershipTier_entry(dword_t user_index) {
if (user_index >= 4) {
if (user_index >= XUserMaxUserCount) {
return X_ERROR_INVALID_PARAMETER;
}
@ -511,7 +520,7 @@ dword_result_t XamUserAreUsersFriends_entry(dword_t user_index, dword_t unk1,
uint32_t are_friends = 0;
X_RESULT result;
if (user_index >= 4) {
if (user_index >= XUserMaxUserCount) {
result = X_ERROR_INVALID_PARAMETER;
} else {
if (kernel_state()->xam_state()->IsUserSignedIn(user_index)) {
@ -550,40 +559,6 @@ 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 users_needed, dword_t unk_mask) {
// XN_SYS_UI (on)
kernel_state()->BroadcastNotification(kXNotificationIDSystemUI, 1);
kernel_state()->xam_state()->UpdateUsedUserProfiles();
// Mask values vary. Probably matching user types? Local/remote?
// Games seem to sit and loop until we trigger this notification:
auto run = [users_needed]() -> void {
uint32_t user_mask = 0;
uint32_t active_users = 0;
for (uint32_t i = 0; i < 4; i++) {
if (kernel_state()->xam_state()->IsUserSignedIn(i)) {
user_mask |= (1 << i);
active_users++;
if (active_users >= users_needed) break;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(150));
// XN_SYS_SIGNINCHANGED (players)
kernel_state()->BroadcastNotification(kXNotificationIDSystemSignInChanged,
user_mask);
// XN_SYS_UI (off)
kernel_state()->BroadcastNotification(kXNotificationIDSystemUI, 0);
};
std::thread thread(run);
thread.detach();
return X_ERROR_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamShowSigninUI, kUserProfiles, kStub);
dword_result_t XamUserCreateAchievementEnumerator_entry(
dword_t title_id, dword_t user_index, dword_t xuid, dword_t flags,
dword_t offset, dword_t count, lpdword_t buffer_size_ptr,
@ -592,7 +567,7 @@ dword_result_t XamUserCreateAchievementEnumerator_entry(
return X_ERROR_INVALID_PARAMETER;
}
if (user_index >= 4) {
if (user_index >= XUserMaxUserCount) {
return X_ERROR_INVALID_PARAMETER;
}
@ -724,7 +699,7 @@ dword_result_t XamUserGetSubscriptionType_entry(dword_t user_index,
dword_t unk2, dword_t unk3,
dword_t unk4, dword_t unk5,
dword_t unk6) {
if (!unk2 || !unk3 || user_index > 4) {
if (!unk2 || !unk3 || user_index >= XUserMaxUserCount) {
return X_E_INVALIDARG;
}
@ -749,7 +724,7 @@ dword_result_t XamUserCreateStatsEnumerator_entry(
return X_ERROR_INVALID_PARAMETER;
}
if (user_index >= 4) {
if (user_index >= XUserMaxUserCount) {
return X_ERROR_INVALID_PARAMETER;
}

View File

@ -340,7 +340,8 @@ struct XContentMetadata {
} description_ex_raw;
std::u16string display_name(XLanguage language) const {
uint32_t lang_id = uint32_t(language) - 1;
uint32_t lang_id =
language == XLanguage::kInvalid ? 1 : uint32_t(language) - 1;
if (lang_id >= kNumLanguagesV2) {
assert_always();

View File

@ -53,6 +53,8 @@ class XContentContainerDevice : public Device {
return files_total_size_ - sizeof(XContentContainerHeader);
}
uint64_t xuid() const { return header_->content_metadata.profile_id; }
uint32_t title_id() const {
return header_->content_metadata.execution_info.title_id;
}

View File

@ -14,6 +14,7 @@
#include <string>
#include "xenia/base/memory.h"
#include "xenia/base/string.h"
// TODO(benvanik): split this header, cleanup, etc.
// clang-format off
@ -259,6 +260,12 @@ struct X_UNICODE_STRING {
};
static_assert_size(X_UNICODE_STRING, 8);
constexpr uint8_t XUserMaxUserCount = 4;
constexpr uint8_t XUserIndexLatest = 0xFD;
constexpr uint8_t XUserIndexNone = 0xFE;
constexpr uint8_t XUserIndexAny = 0xFF;
// https://github.com/ThirteenAG/Ultimate-ASI-Loader/blob/master/source/xlive/xliveless.h
typedef uint32_t XNotificationID;
enum : XNotificationID {
@ -539,6 +546,84 @@ enum class XDeploymentType : uint32_t {
kUnknown = 0xFF,
};
#pragma pack(push, 4)
struct X_XAMACCOUNTINFO {
enum AccountReservedFlags {
kPasswordProtected = 0x10000000,
kLiveEnabled = 0x20000000,
kRecovering = 0x40000000,
kVersionMask = 0x000000FF
};
enum AccountUserFlags {
kPaymentInstrumentCreditCard = 1,
kCountryMask = 0xFF00,
kSubscriptionTierMask = 0xF00000,
kLanguageMask = 0x3E000000,
kParentalControlEnabled = 0x1000000,
};
enum AccountSubscriptionTier {
kSubscriptionTierSilver = 3,
kSubscriptionTierGold = 6,
kSubscriptionTierFamilyGold = 9
};
enum AccountLiveFlags { kAcctRequiresManagement = 1 };
xe::be<uint32_t> reserved_flags;
xe::be<uint32_t> live_flags;
char16_t gamertag[0x10];
xe::be<uint64_t> xuid_online; // 09....
xe::be<uint32_t> cached_user_flags;
xe::be<uint32_t> network_id;
char passcode[4];
char online_domain[0x14];
char online_kerberos_realm[0x18];
char online_key[0x10];
char passport_membername[0x72];
char passport_password[0x20];
char owner_passport_membername[0x72];
bool IsPasscodeEnabled() {
return static_cast<bool>(reserved_flags &
AccountReservedFlags::kPasswordProtected);
}
bool IsLiveEnabled() {
return static_cast<bool>(reserved_flags &
AccountReservedFlags::kLiveEnabled);
}
bool IsXUIDOffline() { return ((xuid_online >> 60) & 0xF) == 0xE; }
bool IsXUIDOnline() { return ((xuid_online >> 48) & 0xFFFF) == 0x9; }
bool IsXUIDValid() { return IsXUIDOffline() != IsXUIDOnline(); }
bool IsTeamXUID() {
return (xuid_online & 0xFF00000000000140) == 0xFE00000000000100;
}
uint32_t GetCountry() const {
return (cached_user_flags & kCountryMask) >> 8;
}
AccountSubscriptionTier GetSubscriptionTier() const {
return static_cast<AccountSubscriptionTier>(
(cached_user_flags & kSubscriptionTierMask) >> 20);
}
XLanguage GetLanguage() const {
return static_cast<XLanguage>((cached_user_flags & kLanguageMask) >> 25);
}
std::string GetGamertagString() const {
return xe::to_utf8(std::u16string(gamertag));
}
};
static_assert_size(X_XAMACCOUNTINFO, 0x17C);
#pragma pack(pop)
} // namespace xe
// clang-format on