diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index 67f8bde2464..1087dde479e 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -276,6 +276,14 @@ void EmulatorWindow::EmulatorWindowListener::OnKeyDown(ui::KeyEvent& e) { emulator_window_.OnKeyDown(e); } +void EmulatorWindow::EmulatorWindowListener::OnMouseDown(ui::MouseEvent& e) { + emulator_window_.OnMouseDown(e); +} + +void EmulatorWindow::EmulatorWindowListener::OnMouseUp(ui::MouseEvent& e) { + emulator_window_.OnMouseUp(e); +} + void EmulatorWindow::DisplayConfigGameConfigLoadCallback::PostGameConfigLoad() { emulator_window_.ApplyDisplayConfigForCvars(); } @@ -864,6 +872,40 @@ void EmulatorWindow::OnKeyDown(ui::KeyEvent& e) { e.set_handled(true); } +void EmulatorWindow::OnMouseDown(const ui::MouseEvent& e) { + ToggleFullscreenOnDoubleClick(); +} + +void EmulatorWindow::OnMouseUp(const ui::MouseEvent& e) { + last_mouse_up = steady_clock::now(); +} + +void EmulatorWindow::ToggleFullscreenOnDoubleClick() { + // this function tests if user has double clicked. + // if double click was achieved the fullscreen gets toggled + const auto now = steady_clock::now(); // current mouse event time + const int16_t mouse_down_max_threshold = 250; + const int16_t mouse_up_max_threshold = 250; + const int16_t mouse_up_down_max_delta = 100; + // max delta to prevent 'chaining' of double clicks with next mouse events + + const auto last_mouse_down_delta = diff_in_ms(now, last_mouse_down); + if (last_mouse_down_delta >= mouse_down_max_threshold) { + last_mouse_down = now; + return; + } + + const auto last_mouse_up_delta = diff_in_ms(now, last_mouse_up); + const auto mouse_event_deltas = diff_in_ms(last_mouse_up, last_mouse_down); + if (last_mouse_up_delta >= mouse_up_max_threshold) { + return; + } + + if (mouse_event_deltas < mouse_up_down_max_delta) { + ToggleFullscreen(); + } +} + void EmulatorWindow::FileDrop(const std::filesystem::path& path) { if (!emulator_initialized_) { return; diff --git a/src/xenia/app/emulator_window.h b/src/xenia/app/emulator_window.h index 962d0d521a8..b1536ca32ce 100644 --- a/src/xenia/app/emulator_window.h +++ b/src/xenia/app/emulator_window.h @@ -38,6 +38,8 @@ struct RecentTitleEntry { class EmulatorWindow { public: + using steady_clock = std::chrono::steady_clock; // stdlib steady clock + enum : size_t { // The UI is on top of the game and is open in special cases, so // lowest-priority. @@ -57,7 +59,17 @@ class EmulatorWindow { std::unique_ptr Gamepad_HotKeys_Listener; int32_t selected_title_index = -1; - + + static constexpr int64_t diff_in_ms( + const steady_clock::time_point t1, + const steady_clock::time_point t2) noexcept { + using ms = std::chrono::milliseconds; + return std::chrono::duration_cast(t1 - t2).count(); + } + + steady_clock::time_point last_mouse_up = steady_clock::now(); + steady_clock::time_point last_mouse_down = steady_clock::now(); + Emulator* emulator() const { return emulator_; } ui::WindowedAppContext& app_context() const { return app_context_; } ui::Window* window() const { return window_.get(); } @@ -131,6 +143,9 @@ class EmulatorWindow { void OnKeyDown(ui::KeyEvent& e) override; + void OnMouseDown(ui::MouseEvent& e) override; + void OnMouseUp(ui::MouseEvent& e) override; + private: EmulatorWindow& emulator_window_; }; @@ -184,7 +199,10 @@ class EmulatorWindow { void ApplyDisplayConfigForCvars(); void OnKeyDown(ui::KeyEvent& e); + void OnMouseDown(const ui::MouseEvent& e); + void ToggleFullscreenOnDoubleClick(); void FileDrop(const std::filesystem::path& filename); + void OnMouseUp(const ui::MouseEvent& e); void FileOpen(); void FileClose(); void InstallContent(); diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index c46cbe39b46..b878244cf80 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -1042,6 +1042,26 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path, } XELOGI("----------------- END OF ACHIEVEMENTS ----------------"); + XELOGI("-------------------- PROPERTIES --------------------"); + const std::vector properties_list = + db.GetProperties(); + + for (const kernel::util::XdbfPropertyTableEntry& entry : + properties_list) { + std::string label = db.GetStringTableEntry(language, entry.string_id); + XELOGI("{:08X} - {} - {}", entry.id, label, entry.data_size); + } + XELOGI("----------------- END OF PROPERTIES ----------------"); + + XELOGI("-------------------- CONTEXTS --------------------"); + const std::vector contexts_list = + db.GetContexts(); + + for (const kernel::util::XdbfContextTableEntry& entry : contexts_list) { + std::string label = db.GetStringTableEntry(language, entry.string_id); + XELOGI("{:08X} - {} - {}", entry.id, label, entry.unk2); + } + XELOGI("----------------- END OF CONTEXTS ----------------"); auto icon_block = db.icon(); if (icon_block) { display_window_->SetIcon(icon_block.buffer, icon_block.size); diff --git a/src/xenia/kernel/achievement_manager.cc b/src/xenia/kernel/achievement_manager.cc index e16571e06ca..92ccec06371 100644 --- a/src/xenia/kernel/achievement_manager.cc +++ b/src/xenia/kernel/achievement_manager.cc @@ -36,36 +36,38 @@ void AchievementManager::EarnAchievement(uint64_t xuid, uint32_t title_id, ui::ImGuiDrawer* imgui_drawer = emulator->imgui_drawer(); const util::XdbfGameData title_xdbf = kernel_state()->title_xdbf(); - const std::vector achievements = - title_xdbf.GetAchievements(); + const util::XdbfAchievementTableEntry achievement = + title_xdbf.GetAchievement(achievement_id); + + if (!achievement.id) { + return; + } + const XLanguage title_language = title_xdbf.GetExistingLanguage( static_cast(cvars::user_language)); - for (const util::XdbfAchievementTableEntry& entry : achievements) { - if (entry.id == achievement_id) { - const std::string label = - title_xdbf.GetStringTableEntry(title_language, entry.label_id); - const std::string desc = - title_xdbf.GetStringTableEntry(title_language, entry.description_id); - - XELOGI("Achievement unlocked: {}", label); - const std::string description = - fmt::format("{}G - {}", entry.gamerscore, label); - - unlocked_achievements[achievement_id] = Clock::QueryHostSystemTime(); - // Even if we disable popup we still should store info that this - // achievement was earned. - if (!cvars::show_achievement_notification) { - continue; - } - - app_context.CallInUIThread([imgui_drawer, description]() { - new xe::ui::AchievementNotificationWindow( - imgui_drawer, "Achievement unlocked", description, 0, - kernel_state()->notification_position_); - }); - } + const std::string label = + title_xdbf.GetStringTableEntry(title_language, achievement.label_id); + const std::string desc = title_xdbf.GetStringTableEntry( + title_language, achievement.description_id); + + XELOGI("Achievement unlocked: {}", label); + + unlocked_achievements[achievement_id] = Clock::QueryHostSystemTime(); + // Even if we disable popup we still should store info that this + // achievement was earned. + if (!cvars::show_achievement_notification) { + return; } + + const std::string description = + fmt::format("{}G - {}", achievement.gamerscore, label); + + app_context.CallInUIThread([imgui_drawer, description]() { + new xe::ui::AchievementNotificationWindow( + imgui_drawer, "Achievement unlocked", description, 0, + kernel_state()->notification_position_); + }); } bool AchievementManager::IsAchievementUnlocked(uint32_t achievement_id) { diff --git a/src/xenia/kernel/util/xdbf_utils.cc b/src/xenia/kernel/util/xdbf_utils.cc index 0670351656c..3424b79aa8e 100644 --- a/src/xenia/kernel/util/xdbf_utils.cc +++ b/src/xenia/kernel/util/xdbf_utils.cc @@ -17,10 +17,14 @@ constexpr fourcc_t kXdbfSignatureXdbf = make_fourcc("XDBF"); constexpr fourcc_t kXdbfSignatureXstc = make_fourcc("XSTC"); constexpr fourcc_t kXdbfSignatureXstr = make_fourcc("XSTR"); constexpr fourcc_t kXdbfSignatureXach = make_fourcc("XACH"); +constexpr fourcc_t kXdbfSignatureXprp = make_fourcc("XPRP"); +constexpr fourcc_t kXdbfSignatureXcxt = make_fourcc("XCXT"); constexpr uint64_t kXdbfIdTitle = 0x8000; constexpr uint64_t kXdbfIdXstc = 0x58535443; constexpr uint64_t kXdbfIdXach = 0x58414348; +constexpr uint64_t kXdbfIdXprp = 0x58505250; +constexpr uint64_t kXdbfIdXctx = 0x58435854; XdbfWrapper::XdbfWrapper(const uint8_t* data, size_t data_size) : data_(data), data_size_(data_size) { @@ -69,11 +73,11 @@ std::string XdbfWrapper::GetStringTableEntry(XLanguage language, } auto xstr_head = - reinterpret_cast(language_block.buffer); + reinterpret_cast(language_block.buffer); assert_true(xstr_head->magic == kXdbfSignatureXstr); assert_true(xstr_head->version == 1); - const uint8_t* ptr = language_block.buffer + sizeof(XdbfSectionHeader); + const uint8_t* ptr = language_block.buffer + sizeof(XdbfXstrSectionHeader); for (uint16_t i = 0; i < xstr_head->count; ++i) { auto entry = reinterpret_cast(ptr); ptr += sizeof(XdbfStringTableEntry); @@ -95,11 +99,11 @@ std::vector XdbfWrapper::GetAchievements() const { } auto xach_head = - reinterpret_cast(achievement_table.buffer); + reinterpret_cast(achievement_table.buffer); assert_true(xach_head->magic == kXdbfSignatureXach); assert_true(xach_head->version == 1); - const uint8_t* ptr = achievement_table.buffer + sizeof(XdbfSectionHeader); + const uint8_t* ptr = achievement_table.buffer + sizeof(XdbfXachSectionHeader); for (uint16_t i = 0; i < xach_head->count; ++i) { auto entry = reinterpret_cast(ptr); ptr += sizeof(XdbfAchievementTableEntry); @@ -108,6 +112,86 @@ std::vector XdbfWrapper::GetAchievements() const { return achievements; } +std::vector XdbfWrapper::GetProperties() const { + std::vector properties; + + auto property_table = GetEntry(XdbfSection::kMetadata, kXdbfIdXprp); + if (!property_table) { + return properties; + } + + auto xprp_head = + reinterpret_cast(property_table.buffer); + assert_true(xprp_head->magic == kXdbfSignatureXprp); + assert_true(xprp_head->version == 1); + + const uint8_t* ptr = property_table.buffer + sizeof(XdbfXprpSectionHeader); + for (uint16_t i = 0; i < xprp_head->count; ++i) { + auto entry = reinterpret_cast(ptr); + ptr += sizeof(XdbfPropertyTableEntry); + properties.push_back(*entry); + } + return properties; +} + +std::vector XdbfWrapper::GetContexts() const { + std::vector contexts; + + auto contexts_table = GetEntry(XdbfSection::kMetadata, kXdbfIdXctx); + if (!contexts_table) { + return contexts; + } + + auto xcxt_head = + reinterpret_cast(contexts_table.buffer); + assert_true(xcxt_head->magic == kXdbfSignatureXcxt); + assert_true(xcxt_head->version == 1); + + const uint8_t* ptr = contexts_table.buffer + sizeof(XdbfXcxtSectionHeader); + for (uint16_t i = 0; i < xcxt_head->count; ++i) { + auto entry = reinterpret_cast(ptr); + ptr += sizeof(XdbfContextTableEntry); + contexts.push_back(*entry); + } + return contexts; +} + +XdbfAchievementTableEntry XdbfWrapper::GetAchievement(const uint32_t id) const { + const auto achievements = GetAchievements(); + + for (const auto& entry : achievements) { + if (entry.id != id) { + continue; + } + return entry; + } + return {}; +} + +XdbfPropertyTableEntry XdbfWrapper::GetProperty(const uint32_t id) const { + const auto properties = GetProperties(); + + for (const auto& entry : properties) { + if (entry.id != id) { + continue; + } + return entry; + } + return {}; +} + +XdbfContextTableEntry XdbfWrapper::GetContext(const uint32_t id) const { + const auto contexts = GetContexts(); + + for (const auto& entry : contexts) { + if (entry.id != id) { + continue; + } + return entry; + } + return {}; +} + XLanguage XdbfGameData::GetExistingLanguage(XLanguage language_to_check) const { // A bit of a hack. Check if title in specific language exist. // If it doesn't then for sure language is not supported. diff --git a/src/xenia/kernel/util/xdbf_utils.h b/src/xenia/kernel/util/xdbf_utils.h index f08fd0d6fd7..94abd6c3df5 100644 --- a/src/xenia/kernel/util/xdbf_utils.h +++ b/src/xenia/kernel/util/xdbf_utils.h @@ -62,13 +62,37 @@ struct XdbfXstc { }; static_assert_size(XdbfXstc, 16); -struct XdbfSectionHeader { +struct XdbfXstrSectionHeader { xe::be magic; xe::be version; xe::be size; xe::be count; }; -static_assert_size(XdbfSectionHeader, 14); +static_assert_size(XdbfXstrSectionHeader, 14); + +struct XdbfXprpSectionHeader { + xe::be magic; + xe::be version; + xe::be size; + xe::be count; +}; +static_assert_size(XdbfXprpSectionHeader, 14); + +struct XdbfXachSectionHeader { + xe::be magic; + xe::be version; + xe::be size; + xe::be count; +}; +static_assert_size(XdbfXachSectionHeader, 14); + +struct XdbfXcxtSectionHeader { + xe::be magic; + xe::be version; + xe::be size; + xe::be count; +}; +static_assert_size(XdbfXcxtSectionHeader, 16); struct XdbfStringTableEntry { xe::be id; @@ -76,6 +100,22 @@ struct XdbfStringTableEntry { }; static_assert_size(XdbfStringTableEntry, 4); +struct XdbfContextTableEntry { + xe::be id; + xe::be unk1; + xe::be string_id; + xe::be unk2; + xe::be unk3; +}; +static_assert_size(XdbfContextTableEntry, 16); + +struct XdbfPropertyTableEntry { + xe::be id; + xe::be string_id; + xe::be data_size; +}; +static_assert_size(XdbfPropertyTableEntry, 8); + struct XdbfAchievementTableEntry { xe::be id; xe::be label_id; @@ -117,6 +157,12 @@ class XdbfWrapper { // Returns the empty string if the entry is not found. std::string GetStringTableEntry(XLanguage language, uint16_t string_id) const; std::vector GetAchievements() const; + std::vector GetProperties() const; + std::vector GetContexts() const; + + XdbfAchievementTableEntry GetAchievement(const uint32_t id) const; + XdbfPropertyTableEntry GetProperty(const uint32_t id) const; + XdbfContextTableEntry GetContext(const uint32_t id) const; private: const uint8_t* data_ = nullptr; diff --git a/src/xenia/kernel/xam/apps/xgi_app.cc b/src/xenia/kernel/xam/apps/xgi_app.cc index 9069820e82a..b15e3c459c3 100644 --- a/src/xenia/kernel/xam/apps/xgi_app.cc +++ b/src/xenia/kernel/xam/apps/xgi_app.cc @@ -43,6 +43,22 @@ X_HRESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr, uint32_t context_value = xe::load_and_swap(buffer + 20); XELOGD("XGIUserSetContextEx({:08X}, {:08X}, {:08X})", user_index, context_id, context_value); + + const util::XdbfGameData title_xdbf = kernel_state_->title_xdbf(); + if (title_xdbf.is_valid()) { + const auto context = title_xdbf.GetContext(context_id); + const XLanguage title_language = title_xdbf.GetExistingLanguage( + static_cast(XLanguage::kEnglish)); + const std::string desc = + title_xdbf.GetStringTableEntry(title_language, context.string_id); + XELOGD("XGIUserSetContextEx: {} - Set to value: {}", desc, + context_value); + + UserProfile* user_profile = kernel_state_->user_profile(user_index); + if (user_profile) { + user_profile->contexts_[context_id] = context_value; + } + } return X_E_SUCCESS; } case 0x000B0007: { @@ -52,6 +68,17 @@ X_HRESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr, uint32_t value_ptr = xe::load_and_swap(buffer + 24); XELOGD("XGIUserSetPropertyEx({:08X}, {:08X}, {}, {:08X})", user_index, property_id, value_size, value_ptr); + + const util::XdbfGameData title_xdbf = kernel_state_->title_xdbf(); + if (title_xdbf.is_valid()) { + const auto property = title_xdbf.GetContext(property_id); + const XLanguage title_language = title_xdbf.GetExistingLanguage( + static_cast(XLanguage::kEnglish)); + const std::string desc = + title_xdbf.GetStringTableEntry(title_language, property.string_id); + XELOGD("XGIUserSetPropertyEx: Setting property: {}", desc); + } + return X_E_SUCCESS; } case 0x000B0008: { @@ -133,6 +160,13 @@ X_HRESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr, context_ptr, context_id); uint32_t value = 0; if (context) { + UserProfile* user_profile = kernel_state_->user_profile(user_index); + if (user_profile) { + if (user_profile->contexts_.find(context_id) != + user_profile->contexts_.cend()) { + value = user_profile->contexts_[context_id]; + } + } xe::store_and_swap(context + 4, value); } return X_E_FAIL; diff --git a/src/xenia/kernel/xam/user_profile.h b/src/xenia/kernel/xam/user_profile.h index 0d0d001cad6..05ab4ed1d25 100644 --- a/src/xenia/kernel/xam/user_profile.h +++ b/src/xenia/kernel/xam/user_profile.h @@ -10,6 +10,7 @@ #ifndef XENIA_KERNEL_XAM_USER_PROFILE_H_ #define XENIA_KERNEL_XAM_USER_PROFILE_H_ +#include #include #include #include @@ -232,6 +233,8 @@ class UserProfile { void AddSetting(std::unique_ptr setting); Setting* GetSetting(uint32_t setting_id); + std::map contexts_; + private: uint64_t xuid_; std::string name_;