From 06e0541e18e5bb8ab8d3d03660d3d82cb112f62b Mon Sep 17 00:00:00 2001 From: Duck912 <350528965+Duck912@users.noreply.github.com> Date: Sun, 1 Nov 2026 21:43:51 -0500 Subject: am: decode the native swkbd config for library version 0x160012 Fire Emblem: Fortune's Weave opens the keyboard with library version 0x160012 and a 0x530-byte config. Read with the Switch 1 layout, the byte taken as use_utf8 belonged to initial_string_offset, so the name entry returned UTF-16 and the game kept only its first letter. The text prefix of the common part keeps its layout; the flags, lengths, string offsets and dictionary fields move (checked against the game's nn::swkbd::keyboardConfig2kbdConfig). Version 0x140010 is unchanged. Assisted-by: Claude Opus 5.5 (debugging, implementation and drafting) Signed-off-by: Duck912 <350528965+Duck912@users.noreply.github.com> --- .../am/frontend/applet_software_keyboard.cpp | 8 +++ .../am/frontend/applet_software_keyboard_types.h | 60 ++++++++++++++++++++++ 2 files changed, 68 insertions(+) (limited to 'src/core') diff --git a/src/core/hle/service/am/frontend/applet_software_keyboard.cpp b/src/core/hle/service/am/frontend/applet_software_keyboard.cpp index db42781e18..061394bff8 100644 --- a/src/core/hle/service/am/frontend/applet_software_keyboard.cpp +++ b/src/core/hle/service/am/frontend/applet_software_keyboard.cpp @@ -222,6 +222,14 @@ void SoftwareKeyboard::InitializeForeground() { sizeof(SwkbdConfigNew)); swkbd_config_common.use_utf8 = true; break; + case SwkbdAppletVersion::Version1441810: { + // Fire Emblem: Fortune's Weave asks for UTF-8 and keeps the reply up to the first NUL, so the + // Switch 1 layout's use_utf8 (a byte of the native initial_string_offset) left one letter. + const bool decoded = + DecodeNativeSwkbdConfig(swkbd_config_data, swkbd_config_common, swkbd_config_new); + ASSERT(decoded); + break; + } default: UNIMPLEMENTED_MSG("Unknown SwkbdConfig revision={} with size={}", swkbd_applet_version, swkbd_config_data.size()); diff --git a/src/core/hle/service/am/frontend/applet_software_keyboard_types.h b/src/core/hle/service/am/frontend/applet_software_keyboard_types.h index f263a704cd..8ff8391aa1 100644 --- a/src/core/hle/service/am/frontend/applet_software_keyboard_types.h +++ b/src/core/hle/service/am/frontend/applet_software_keyboard_types.h @@ -7,6 +7,9 @@ #pragma once #include +#include +#include +#include #include "common/bit_field.h" #include "common/common_funcs.h" @@ -31,6 +34,7 @@ enum class SwkbdAppletVersion : u32_le { Version393227 = 0x6000B, // 6.0.0 - 7.0.1 Version524301 = 0x8000D, // 8.0.0+ Version1310736 = 0x140010, // Switch 2 (Pokemon Pokopia): 0x530-byte config, UTF-8 text + Version1441810 = 0x160012, // Switch 2 (Fire Emblem: Fortune's Weave): native 0x530-byte config }; enum class SwkbdType : u32 { @@ -184,8 +188,64 @@ struct SwkbdConfigNew { }; static_assert(sizeof(SwkbdConfigNew) == 0x4C8 - sizeof(SwkbdConfigCommon), "SwkbdConfigNew has incorrect size."); + +// SwkbdAppletVersion 0x160012: the text prefix of the common part keeps its layout, the flags, +// lengths, string offsets and dictionary fields move (checked against +// nn::swkbd::keyboardConfig2kbdConfig in Fire Emblem: Fortune's Weave's sdk). +struct SwkbdConfigNative { + std::array common_prefix{}; + bool use_utf8{}; + bool use_blur_background{}; + u32 max_text_length{}; + u32 min_text_length{}; + SwkbdPasswordMode password_mode{}; + SwkbdTextDrawType text_draw_type{}; + u32 initial_string_offset{}; + u32 initial_string_length{}; + u32 user_dictionary_offset{}; + u32 user_dictionary_entries{}; + std::array customized_dictionary_set_entries{}; + u8 total_customized_dictionary_set_entries{}; + bool use_text_check{}; + bool disable_cancel_button{}; + u8 trigger{}; + std::array text_grouping{}; + INSERT_PADDING_BYTES(0x80); +}; +static_assert(sizeof(SwkbdConfigNative) == 0x530); +static_assert(offsetof(SwkbdConfigNative, use_text_check) == 0x48D); +static_assert(offsetof(SwkbdConfigNative, text_grouping) == 0x490); #pragma pack(pop) +inline bool DecodeNativeSwkbdConfig(std::span data, SwkbdConfigCommon& common, + SwkbdConfigNew& config) { + if (data.size() != sizeof(SwkbdConfigNative)) { + return false; + } + SwkbdConfigNative native{}; + std::memcpy(&native, data.data(), sizeof(native)); + common = {}; + config = {}; + std::memcpy(static_cast(&common), native.common_prefix.data(), native.common_prefix.size()); + common.enable_return_button = native.common_prefix[0x1B] != 0; + common.use_utf8 = native.use_utf8; + common.use_blur_background = native.use_blur_background; + common.max_text_length = native.max_text_length; + common.min_text_length = native.min_text_length; + common.password_mode = native.password_mode; + common.text_draw_type = native.text_draw_type; + common.initial_string_offset = native.initial_string_offset; + common.initial_string_length = native.initial_string_length; + common.user_dictionary_offset = native.user_dictionary_offset; + common.user_dictionary_entries = native.user_dictionary_entries; + common.use_text_check = native.use_text_check; + config.text_grouping = native.text_grouping; + config.customized_dictionary_set_entries = native.customized_dictionary_set_entries; + config.total_customized_dictionary_set_entries = native.total_customized_dictionary_set_entries; + config.disable_cancel_button = native.disable_cancel_button; + return true; +} + struct SwkbdTextCheck { SwkbdTextCheckResult text_check_result{}; std::array text_check_message{}; -- cgit v1.2.3