diff --git a/src/xenia/kernel/xam/xam_info.cc b/src/xenia/kernel/xam/xam_info.cc index 9369c9646..aaff814de 100644 --- a/src/xenia/kernel/xam/xam_info.cc +++ b/src/xenia/kernel/xam/xam_info.cc @@ -44,8 +44,6 @@ DEFINE_int32(avpack, 8, " 7 = TV PAL-60\n" " 8 = HDMI (default)", "Video"); -DECLARE_int32(user_country); -DECLARE_int32(user_language); DECLARE_uint32(audio_flag); DEFINE_bool(staging_mode, 0, @@ -90,40 +88,6 @@ dword_result_t XamGetOnlineSchema_entry() { } DECLARE_XAM_EXPORT1(XamGetOnlineSchema, kNone, kImplemented); -void XamFormatDateString_entry(dword_t locale_format, qword_t filetime, - lpvoid_t output_buffer, dword_t output_count) { - output_buffer.Zero(output_count * sizeof(char16_t)); - - auto tp = xe::chrono::WinSystemClock::to_sys( - xe::chrono::WinSystemClock::from_file_time(filetime)); - auto dp = date::floor(tp); - auto year_month_day = date::year_month_day{dp}; - - auto str = fmt::format(u"{:02d}/{:02d}/{}", - static_cast(year_month_day.month()), - static_cast(year_month_day.day()), - static_cast(year_month_day.year())); - xe::string_util::copy_and_swap_truncating(output_buffer.as(), str, - output_count); -} -DECLARE_XAM_EXPORT1(XamFormatDateString, kNone, kImplemented); - -void XamFormatTimeString_entry(dword_t user_index, qword_t filetime, - lpvoid_t output_buffer, dword_t output_count) { - output_buffer.Zero(output_count * sizeof(char16_t)); - - auto tp = xe::chrono::WinSystemClock::to_sys( - xe::chrono::WinSystemClock::from_file_time(filetime)); - auto dp = date::floor(tp); - auto time = date::hh_mm_ss{date::floor(tp - dp)}; - - auto str = fmt::format(u"{:02d}:{:02d}", time.hours().count(), - time.minutes().count()); - xe::string_util::copy_and_swap_truncating(output_buffer.as(), str, - output_count); -} -DECLARE_XAM_EXPORT1(XamFormatTimeString, kNone, kImplemented); - dword_result_t keXamBuildResourceLocator(uint64_t module, const std::u16string& container, const std::u16string& resource, @@ -253,61 +217,6 @@ dword_result_t XGetAVPack_entry() { } DECLARE_XAM_EXPORT1(XGetAVPack, kNone, kStub); -uint32_t xeXGetGameRegion() { - static uint32_t constexpr table[] = { - 0xFFFFu, 0x03FFu, 0x02FEu, 0x02FEu, 0x03FFu, 0x02FEu, 0x0201u, 0x03FFu, - 0x02FEu, 0x02FEu, 0x03FFu, 0x03FFu, 0x03FFu, 0x03FFu, 0x02FEu, 0x03FFu, - 0x00FFu, 0xFFFFu, 0x02FEu, 0x03FFu, 0x0102u, 0x03FFu, 0x03FFu, 0x02FEu, - 0x02FEu, 0x02FEu, 0x03FFu, 0x03FFu, 0x03FFu, 0x02FEu, 0x03FFu, 0x02FEu, - 0x02FEu, 0x02FEu, 0x02FEu, 0x02FEu, 0x02FEu, 0x02FEu, 0x03FFu, 0x03FFu, - 0x03FFu, 0x02FEu, 0x02FEu, 0x03FFu, 0x02FEu, 0x02FEu, 0x03FFu, 0x03FFu, - 0x03FFu, 0x02FEu, 0x02FEu, 0x03FFu, 0x03FFu, 0x0101u, 0x03FFu, 0x03FFu, - 0x03FFu, 0x03FFu, 0x03FFu, 0x03FFu, 0x02FEu, 0x02FEu, 0x02FEu, 0x02FEu, - 0x03FFu, 0x03FFu, 0x02FEu, 0x02FEu, 0x03FFu, 0x0102u, 0x03FFu, 0x00FFu, - 0x03FFu, 0x03FFu, 0x02FEu, 0x02FEu, 0x0201u, 0x03FFu, 0x03FFu, 0x03FFu, - 0x03FFu, 0x03FFu, 0x02FEu, 0x03FFu, 0x02FEu, 0x03FFu, 0x03FFu, 0x02FEu, - 0x02FEu, 0x03FFu, 0x02FEu, 0x03FFu, 0x02FEu, 0x02FEu, 0xFFFFu, 0x03FFu, - 0x03FFu, 0x03FFu, 0x03FFu, 0x02FEu, 0x03FFu, 0x03FFu, 0x02FEu, 0x00FFu, - 0x03FFu, 0x03FFu, 0x03FFu, 0x03FFu, 0x03FFu, 0x03FFu, 0x03FFu}; - auto country = static_cast(cvars::user_country); - return country < xe::countof(table) ? table[country] : 0xFFFFu; -} - -dword_result_t XGetGameRegion_entry() { return xeXGetGameRegion(); } -DECLARE_XAM_EXPORT1(XGetGameRegion, kNone, kStub); - -XLanguage xeGetLanguage(bool extended_languages_support) { - auto desired_language = static_cast(cvars::user_language); - uint32_t region = xeXGetGameRegion(); - auto max_languages = extended_languages_support ? XLanguage::kMaxLanguages - : XLanguage::kSChinese; - if (desired_language < max_languages) { - return desired_language; - } - if ((region & 0xff00) != 0x100) { - return XLanguage::kEnglish; - } - switch (region) { - case 0x101: // NTSC-J (Japan) - return XLanguage::kJapanese; - case 0x102: // NTSC-J (China) - return extended_languages_support ? XLanguage::kSChinese - : XLanguage::kEnglish; - default: - return XLanguage::kKorean; - } -} - -dword_result_t XGetLanguage_entry() { - return static_cast(xeGetLanguage(false)); -} -DECLARE_XAM_EXPORT1(XGetLanguage, kNone, kImplemented); - -dword_result_t XamGetLanguage_entry() { - return static_cast(xeGetLanguage(true)); -} -DECLARE_XAM_EXPORT1(XamGetLanguage, kNone, kImplemented); - dword_result_t XamGetCurrentTitleId_entry() { return kernel_state()->emulator()->title_id(); } diff --git a/src/xenia/kernel/xam/xam_locale.cc b/src/xenia/kernel/xam/xam_locale.cc index 5c33fbbe9..c1e99d283 100644 --- a/src/xenia/kernel/xam/xam_locale.cc +++ b/src/xenia/kernel/xam/xam_locale.cc @@ -17,8 +17,11 @@ #include "xenia/kernel/xthread.h" #include "xenia/xbox.h" -DECLARE_int32(user_country); +#include "third_party/fmt/include/fmt/format.h" +#include "third_party/fmt/include/fmt/xchar.h" +DECLARE_int32(user_country); +DECLARE_int32(user_language); // TODO(gibbed): put these forward decls in a header somewhere. namespace xe { @@ -453,6 +456,126 @@ dword_result_t XamGetLocaleDateFormat_entry(dword_t locale) { DECLARE_XAM_EXPORT1(XamGetLocaleDateFormat, kLocale, kImplemented); +void XamFormatDateString_entry(dword_t locale_format, qword_t filetime, + lpvoid_t output_buffer, dword_t output_count) { + output_buffer.Zero(output_count * sizeof(char16_t)); + + auto tp = xe::chrono::WinSystemClock::to_sys( + xe::chrono::WinSystemClock::from_file_time(filetime)); + auto dp = date::floor(tp); + auto year_month_day = date::year_month_day{dp}; + + auto str = fmt::format(u"{:02d}/{:02d}/{}", + static_cast(year_month_day.month()), + static_cast(year_month_day.day()), + static_cast(year_month_day.year())); + xe::string_util::copy_and_swap_truncating(output_buffer.as(), str, + output_count); +} +DECLARE_XAM_EXPORT1(XamFormatDateString, kNone, kImplemented); + +void XamFormatTimeString_entry(dword_t user_index, qword_t filetime, + lpvoid_t output_buffer, dword_t output_count) { + output_buffer.Zero(output_count * sizeof(char16_t)); + + auto tp = xe::chrono::WinSystemClock::to_sys( + xe::chrono::WinSystemClock::from_file_time(filetime)); + auto dp = date::floor(tp); + auto time = date::hh_mm_ss{date::floor(tp - dp)}; + + auto str = fmt::format(u"{:02d}:{:02d}", time.hours().count(), + time.minutes().count()); + xe::string_util::copy_and_swap_truncating(output_buffer.as(), str, + output_count); +} +DECLARE_XAM_EXPORT1(XamFormatTimeString, kNone, kImplemented); + +uint32_t xeXGetGameRegion() { + static uint32_t constexpr table[] = { + 0xFFFFu, 0x03FFu, 0x02FEu, 0x02FEu, 0x03FFu, 0x02FEu, 0x0201u, 0x03FFu, + 0x02FEu, 0x02FEu, 0x03FFu, 0x03FFu, 0x03FFu, 0x03FFu, 0x02FEu, 0x03FFu, + 0x00FFu, 0xFFFFu, 0x02FEu, 0x03FFu, 0x0102u, 0x03FFu, 0x03FFu, 0x02FEu, + 0x02FEu, 0x02FEu, 0x03FFu, 0x03FFu, 0x03FFu, 0x02FEu, 0x03FFu, 0x02FEu, + 0x02FEu, 0x02FEu, 0x02FEu, 0x02FEu, 0x02FEu, 0x02FEu, 0x03FFu, 0x03FFu, + 0x03FFu, 0x02FEu, 0x02FEu, 0x03FFu, 0x02FEu, 0x02FEu, 0x03FFu, 0x03FFu, + 0x03FFu, 0x02FEu, 0x02FEu, 0x03FFu, 0x03FFu, 0x0101u, 0x03FFu, 0x03FFu, + 0x03FFu, 0x03FFu, 0x03FFu, 0x03FFu, 0x02FEu, 0x02FEu, 0x02FEu, 0x02FEu, + 0x03FFu, 0x03FFu, 0x02FEu, 0x02FEu, 0x03FFu, 0x0102u, 0x03FFu, 0x00FFu, + 0x03FFu, 0x03FFu, 0x02FEu, 0x02FEu, 0x0201u, 0x03FFu, 0x03FFu, 0x03FFu, + 0x03FFu, 0x03FFu, 0x02FEu, 0x03FFu, 0x02FEu, 0x03FFu, 0x03FFu, 0x02FEu, + 0x02FEu, 0x03FFu, 0x02FEu, 0x03FFu, 0x02FEu, 0x02FEu, 0xFFFFu, 0x03FFu, + 0x03FFu, 0x03FFu, 0x03FFu, 0x02FEu, 0x03FFu, 0x03FFu, 0x02FEu, 0x00FFu, + 0x03FFu, 0x03FFu, 0x03FFu, 0x03FFu, 0x03FFu, 0x03FFu, 0x03FFu}; + auto country = static_cast(cvars::user_country); + return country < xe::countof(table) ? table[country] : 0xFFFFu; +} + +dword_result_t XGetGameRegion_entry() { return xeXGetGameRegion(); } +DECLARE_XAM_EXPORT1(XGetGameRegion, kNone, kStub); + +XLanguage xeGetLanguage(bool extended_languages_support) { + auto desired_language = static_cast(cvars::user_language); + uint32_t region = xeXGetGameRegion(); + auto max_languages = extended_languages_support ? XLanguage::kMaxLanguages + : XLanguage::kSChinese; + if (desired_language < max_languages) { + return desired_language; + } + if ((region & 0xff00) != 0x100) { + return XLanguage::kEnglish; + } + switch (region) { + case 0x101: // NTSC-J (Japan) + return XLanguage::kJapanese; + case 0x102: // NTSC-J (China) + return extended_languages_support ? XLanguage::kSChinese + : XLanguage::kEnglish; + default: + return XLanguage::kKorean; + } +} + +dword_result_t XGetLanguage_entry() { + return static_cast(xeGetLanguage(false)); +} +DECLARE_XAM_EXPORT1(XGetLanguage, kNone, kImplemented); + +dword_result_t XamGetLanguage_entry() { + return static_cast(xeGetLanguage(true)); +} +DECLARE_XAM_EXPORT1(XamGetLanguage, kNone, kImplemented); + +pointer_result_t XamGetLanguageLocaleFallbackString_entry(dword_t language) { + assert_false(language > 17); + return kernel_state()->xam_state()->GetLanguageFallbackAddress(language); +} +DECLARE_XAM_EXPORT1(XamGetLanguageLocaleFallbackString, kNone, kImplemented); + +dword_result_t XamGetLanguageTypeface_entry(dword_t language, + dword_t buffer_size, + dword_t buffer) { + std::u16string path{}; + + if (language == 0x11) { + path = u"file://media:/XenonSCLatin.xtt"; + } else if (language == 8) { + path = u"file://media:/XenonCLatin.xtt"; + } else { + path = u"file://media:/XenonJKLatin.xtt"; + } + xe::string_util::copy_and_swap_truncating( + kernel_state()->memory()->TranslateVirtual(buffer), path, + path.size() + 1); + + return X_STATUS_SUCCESS; +} +DECLARE_XAM_EXPORT1(XamGetLanguageTypeface, kNone, kImplemented); + +pointer_result_t XamGetLanguageTypefacePatch_entry(dword_t language) { + return 0; +} +DECLARE_XAM_EXPORT1(XamGetLanguageTypefacePatch, kNone, kStub); + } // namespace xam } // namespace kernel } // namespace xe diff --git a/src/xenia/kernel/xam/xam_state.cc b/src/xenia/kernel/xam/xam_state.cc index 1881c0206..80ab51460 100644 --- a/src/xenia/kernel/xam/xam_state.cc +++ b/src/xenia/kernel/xam/xam_state.cc @@ -30,9 +30,36 @@ XamState::XamState(Emulator* emulator, KernelState* kernel_state) std::make_unique(kernel_state, user_tracker_.get()); achievement_manager_ = std::make_unique(); + LoadLanguageLocaleFallback(); + AppManager::RegisterApps(kernel_state, app_manager_.get()); } +void XamState::LoadLanguageLocaleFallback() { + const std::array locale_data = { + u"", u"", u"ja-JP", u"de-DE", u"fr-FR", u"es-ES", + u"it-IT", u"ko-KR", u"zh-TW", u"pt-BR", u"zh-CHS", u"pl-PL", + u"ru-RU", u"sv-SE", u"tr-TR", u"nb-NO", u"nl-NL", u"zh-CHS"}; + + constexpr uint32_t array_start = 0x80D00000; + + if (kernel_state_->memory() + ->LookupHeap(0x80000000) + ->AllocFixed(array_start, 0xC8, 0x1000, kMemoryAllocationCommit, + kMemoryProtectRead | kMemoryProtectWrite)) { + char16_t* ptr = + kernel_state_->memory()->TranslateVirtual(array_start); + + for (size_t i = 1; i < locale_data.size(); i++) { + language_fallback_address_[i] = + kernel_state_->memory()->HostToGuestVirtual(ptr); + ptr += xe::string_util::copy_and_swap_truncating( + ptr, locale_data.at(i), locale_data.at(i).size() + 1) + + 1; + } + } +} + UserProfile* XamState::GetUserProfile(uint32_t user_index) const { if (user_index >= XUserMaxUserCount && user_index < XUserIndexLatest) { return nullptr; diff --git a/src/xenia/kernel/xam/xam_state.h b/src/xenia/kernel/xam/xam_state.h index 5a704f0ba..f8f3daac9 100644 --- a/src/xenia/kernel/xam/xam_state.h +++ b/src/xenia/kernel/xam/xam_state.h @@ -62,6 +62,10 @@ class XamState { return xam_dialogs_shown_ > 0 || xam_nui_dialogs_shown_ > 0; } + uint32_t GetLanguageFallbackAddress(uint32_t index) const { + return language_fallback_address_[index]; + } + X_DASH_APP_INFO dash_app_info_ = {}; uint32_t dash_backstack_nodes_count_ = 0; X_DASH_BACKSTACK_DATA dash_backstack_data_[2] = {}; @@ -71,6 +75,8 @@ class XamState { std::atomic xam_nui_dialogs_shown_ = {0}; private: + void LoadLanguageLocaleFallback(); + KernelState* kernel_state_; std::unique_ptr app_manager_; @@ -80,6 +86,9 @@ class XamState { std::unique_ptr profile_manager_; std::unique_ptr spa_info_; + + // Custom XAM stuff + std::array language_fallback_address_{}; }; } // namespace xam