Add a PlayStation choice to the button layout.

pad_layout = playstation plays a pad by label as xbox does, named for a PlayStation pad: cross A, circle B, square X,
triangle Y. The prompts follow it like the Xbox layout, by label. Under Automatic the note says PlayStation for a pad
whose bottom button is labelled cross. The layout description in each menu language now says what every choice does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-08 23:23:59 -05:00
parent 612c02311d
commit 276325d9ff
9 changed files with 44 additions and 22 deletions

View File

@@ -14,6 +14,7 @@ amplitude before the strength (0.2 to 1.5, default 0.6; lower makes weak pulses
- `nintendo` (default): by position, as on a Switch (the table below).
- `xbox`: south A, east B, west X, north Y, as an Xbox pad labels them; on a PlayStation pad cross is A, circle B, square X, triangle Y.
- `playstation`: the same buttons as `xbox` under the PlayStation name: cross A, circle B, square X, triangle Y.
- `auto`: `nintendo` for a pad whose east button SDL reports labelled A (Switch Pro Controller, Nintendo-layout pads), `xbox` for the others.
The log names the layout in effect (`input: gamepad layout ...`) and each gamepad's first press
@@ -28,7 +29,7 @@ letters unless *Button prompts* says otherwise (below).
- the letter in the action prompt (top right);
- the ocarina's note buttons.
A, B, X and Y show the button the *Button layout* presses: with `nintendo` (by position) Switch A is the right face button, so its prompts show B or circle, B shows A or cross, X shows Y or triangle and Y shows X or square; with `xbox` and `automatic` (by label) A shows A or cross, B shows B or circle, X shows X or square and Y shows Y or triangle. The game reads the font and the interface textures once at start, so a change shows after a restart (the row says so; the menu's Restart button restarts). A button remapped on this tab keeps its old prompt.
A, B, X and Y show the button the *Button layout* presses: with `nintendo` (by position) Switch A is the right face button, so its prompts show B or circle, B shows A or cross, X shows Y or triangle and Y shows X or square; with `xbox`, `playstation` and `automatic` (by label) A shows A or cross, B shows B or circle, X shows X or square and Y shows Y or triangle. The game reads the font and the interface textures once at start, so a change shows after a restart (the row says so; the menu's Restart button restarts). A button remapped on this tab keeps its old prompt.
The prompts live in four overlay folders next to the exe, `romfs-patches-xbox`, `romfs-patches-xbox-label`, `romfs-patches-playstation` and `romfs-patches-playstation-label` (by position and by label), which `runtime/romfs.c` loads in place of the game's files. `tools/pad_prompts.py` draws them on the player's machine from the player's own RomFS and a folder of button art, which the repository does not hold:

View File

@@ -288,7 +288,8 @@
# rumble_triggers = 0
# Which gamepad button is Switch A: nintendo (the default: the Switch's buttons by position on any pad, east A,
# south B, west Y, north X), xbox (south A, east B, west X, north Y, as an Xbox pad labels them; cross A on a
# PlayStation pad) or auto (nintendo for a pad with Nintendo labels, xbox for the others). The bind_ lines below apply
# PlayStation pad), playstation (the same as xbox: cross A, circle B, square X, triangle Y) or auto (nintendo for a
# pad with Nintendo labels, xbox for the others). The bind_ lines below apply
# on top. Changes while the game runs take effect at once. 0.1.9's xbox_prompts is read as nintendo with prompts = xbox.
# pad_layout = nintendo
# The buttons the game's prompts show: nintendo (the game's own), xbox (LB RB LT RT, Menu, View) or playstation (L1 R1

View File

@@ -150,7 +150,7 @@ static const CfgMeta metas[] = {
// Sound, Controls, Menu
{"VOLUME", "100", "0..100", LIVE}, {"MENU_SOUND_VOLUME", "80", "0..100", LIVE}, {"AUDIO_CHANNELS", "stereo", "auto|stereo|5.1", RESTART},
{"MOTION", "1", "0|1", LIVE}, {"RUMBLE_STRENGTH", "130", "0..300", LIVE}, {"RUMBLE_CURVE", "0.6", "0.2..1.5", LIVE},
{"RUMBLE_TRIGGERS", "0", "0|1", LIVE}, {"PAD_LAYOUT", "nintendo", "auto|nintendo|xbox", LIVE},
{"RUMBLE_TRIGGERS", "0", "0|1", LIVE}, {"PAD_LAYOUT", "nintendo", "auto|nintendo|xbox|playstation", LIVE},
{"PROMPTS", "nintendo", "nintendo|xbox|playstation|pc", RESTART},
{"STICK_DEADZONE", "8", "0..50", LIVE}, {"STICK_SENS", "1", "0.1..3", RESTART},
{"LSTICK_DEADZONE", "", "0..50", LIVE}, {"RSTICK_DEADZONE", "", "0..50", LIVE},

View File

@@ -319,6 +319,8 @@ int probe_cfg(void) {
CHECK(input_pad_layout_pick("xbox_prompts", 0) == PAD_LAYOUT_NINTENDO &&
input_pad_layout_pick("Xbox_Prompts", 1) == PAD_LAYOUT_NINTENDO,
"pad layout: 0.1.9's xbox_prompts keeps the Nintendo positions (only the prompts change, romfs.c overlay_init)");
CHECK(input_pad_layout_pick("PlayStation", 1) == PAD_LAYOUT_XBOX && input_pad_layout_pick("playstation", 0) == PAD_LAYOUT_XBOX,
"pad layout: playstation plays by label as xbox does (cross A), whatever the pad's labels");
// Button prompts (input_map.c)
CHECK(input_prompts_pick(NULL, "auto") == PROMPTS_NINTENDO && input_prompts_pick("", "xbox") == PROMPTS_NINTENDO &&
input_prompts_pick("Xbox", "nintendo") == PROMPTS_XBOX && input_prompts_pick("nintendo", "xbox") == PROMPTS_NINTENDO,
@@ -351,8 +353,8 @@ int probe_cfg(void) {
// romfs-patches-xbox shows A as B and romfs-patches-playstation A as circle (tools/pad_prompts.py FACE). That is
// right exactly where the layout puts A on the east button, which an Xbox pad labels B and a PlayStation pad circle.
input_map_reset();
static const char *const layouts[] = {"nintendo", "xbox", "auto", "xbox_prompts"};
for (int i = 0; i < 4; i++) {
static const char *const layouts[] = {"nintendo", "xbox", "playstation", "auto", "xbox_prompts"};
for (int i = 0; i < 5; i++) {
int a_east = input_map_pad_buttons((uint8_t[PAD_COUNT]){[PAD_EAST] = 1}, input_pad_layout_pick(layouts[i], 0)) == 1ull << B_A;
CHECK(a_east == EQ(input_prompts_folder(PROMPTS_XBOX, layouts[i]), "romfs-patches-xbox"),
"prompts: the folder names A as B exactly when the layout puts A on the button labelled B");

View File

@@ -291,7 +291,7 @@ void input_map_line(const InputMap *m, int action, char *out, size_t n) {
int input_pad_layout_pick(const char *setting, int east_labelled_a) {
if (setting && (!strcasecmp(setting, "nintendo") || !strcasecmp(setting, "xbox_prompts"))) return PAD_LAYOUT_NINTENDO;
if (setting && !strcasecmp(setting, "xbox")) return PAD_LAYOUT_XBOX;
if (setting && (!strcasecmp(setting, "xbox") || !strcasecmp(setting, "playstation"))) return PAD_LAYOUT_XBOX;
return east_labelled_a ? PAD_LAYOUT_NINTENDO : PAD_LAYOUT_XBOX;
}

View File

@@ -82,9 +82,10 @@ int input_map_bind(const char *button, const char *list);
// Applies the NX2NAVI_BIND_<action> settings (cfg_get) over the defaults: at start, and again when one of them changes.
void input_map_configure(void);
// The layout for a NX2NAVI_PAD_LAYOUT value: "nintendo" or "xbox" (any case); "xbox_prompts", 0.1.9's name for the Nintendo
// layout with the Xbox prompts, maps the buttons as "nintendo". Anything else ("auto", NULL) picks Nintendo when the
// pad's east button is labelled A (a Switch Pro Controller, a GuliKit) and Xbox otherwise.
// The layout for a NX2NAVI_PAD_LAYOUT value: "nintendo" or "xbox" (any case); "playstation" is the Xbox layout under its
// PlayStation name (cross A, circle B, square X, triangle Y); "xbox_prompts", 0.1.9's name for the Nintendo layout with
// the Xbox prompts, maps the buttons as "nintendo". Anything else ("auto", NULL) picks Nintendo when the pad's east
// button is labelled A (a Switch Pro Controller, a GuliKit) and Xbox otherwise.
int input_pad_layout_pick(const char *setting, int east_labelled_a);
// Button prompts (NX2NAVI_PROMPTS): the controller whose buttons the game's prompts show. Nintendo: the game's own. Xbox

View File

@@ -942,13 +942,16 @@ static void tab_sound(float scale) {
// A confirmation is open (Controls: restart, Advanced: reset): B and Esc close it, not the menu; L / R stay on the page.
static bool g_modal;
// The note under the layout: Automatic with a gamepad connected, the layout it picked ("in use: Xbox"). NULL otherwise.
// The note under the layout: Automatic with a gamepad connected, the layout it picked ("in use: Xbox"), named
// PlayStation for a pad whose bottom button is labelled cross. NULL otherwise.
static const char *pad_layout_note(void) {
int got = input_pad_layout();
if (got < 0 || strcmp(cfg_value("NX2NAVI_PAD_LAYOUT"), "auto")) return nullptr;
static char note[96];
snprintf(note, sizeof note, menu_text(UI_MENU_PAD_LAYOUT_IN_USE),
menu_text(got == PAD_LAYOUT_XBOX ? UI_MENU_PAD_LAYOUT_XBOX : UI_MENU_PAD_LAYOUT_NINTENDO));
UiTextId name = got != PAD_LAYOUT_XBOX ? UI_MENU_PAD_LAYOUT_NINTENDO
: input_pad_label(PAD_SOUTH) == PAD_LABEL_CROSS ? UI_MENU_PAD_LAYOUT_PLAYSTATION
: UI_MENU_PAD_LAYOUT_XBOX;
snprintf(note, sizeof note, menu_text(UI_MENU_PAD_LAYOUT_IN_USE), menu_text(name));
return note;
}
@@ -1064,8 +1067,9 @@ static void restart_confirm(float scale) {
static void tab_controls(float scale) {
opt_begin("##controls", scale);
static const UiTextId layout[] = {UI_MENU_PAD_LAYOUT_AUTO, UI_MENU_PAD_LAYOUT_NINTENDO, UI_MENU_PAD_LAYOUT_XBOX};
opt_choice("NX2NAVI_PAD_LAYOUT", UI_MENU_PAD_LAYOUT, layout, 3, UI_MENU_PAD_LAYOUT_DESC, pad_layout_note());
static const UiTextId layout[] = {UI_MENU_PAD_LAYOUT_AUTO, UI_MENU_PAD_LAYOUT_NINTENDO, UI_MENU_PAD_LAYOUT_XBOX,
UI_MENU_PAD_LAYOUT_PLAYSTATION};
opt_choice("NX2NAVI_PAD_LAYOUT", UI_MENU_PAD_LAYOUT, layout, 4, UI_MENU_PAD_LAYOUT_DESC, pad_layout_note());
// The first three values of the setting (cfg.c): pc stays readable in nx2navi.ini, but nothing makes its files for
// this game, so the row leaves it out. The game reads its prompts at start: the row's note says when a restart
// would bring others, and the bar's Restart button does it.

View File

@@ -219,9 +219,12 @@ static const char *const text_en[UI_TEXT_COUNT] = {
[UI_MENU_RUMBLE_TRIGGERS] = "Trigger vibration (Xbox)",
[UI_MENU_RUMBLE_TRIGGERS_DESC] = "Also vibrates the triggers on Xbox controllers.",
[UI_MENU_PAD_LAYOUT] = "Button layout",
[UI_MENU_PAD_LAYOUT_DESC] = "Sets which button acts as A.",
[UI_MENU_PAD_LAYOUT_DESC] = "Sets which button acts as A. Nintendo: by position, A right, B bottom, X top, Y "
"left. Xbox and PlayStation: by label, A bottom, B right, X left, Y top (on a "
"PlayStation pad cross, circle, square and triangle). Automatic: Nintendo for a pad "
"labelled the Nintendo way, by label for the others.",
[UI_MENU_PAD_LAYOUT_AUTO] = "Automatic", [UI_MENU_PAD_LAYOUT_NINTENDO] = "Nintendo (A right)",
[UI_MENU_PAD_LAYOUT_XBOX] = "Xbox (A bottom)",
[UI_MENU_PAD_LAYOUT_XBOX] = "Xbox (A bottom)", [UI_MENU_PAD_LAYOUT_PLAYSTATION] = "PlayStation (A bottom)",
[UI_MENU_PAD_LAYOUT_IN_USE] = "in use: %s",
[UI_MENU_PROMPTS] = "Button prompts",
[UI_MENU_PROMPTS_DESC] = "Which controller's buttons the game's prompts show, in text, in the action prompt and on "
@@ -691,9 +694,12 @@ static const char *const text_ru[UI_TEXT_COUNT] = {
[UI_MENU_RUMBLE_TRIGGERS] = "Вибрация триггеров (Xbox)",
[UI_MENU_RUMBLE_TRIGGERS_DESC] = "Вибрация также на курках контроллера Xbox.",
[UI_MENU_PAD_LAYOUT] = "Раскладка кнопок",
[UI_MENU_PAD_LAYOUT_DESC] = "Какая кнопка работает как A.",
[UI_MENU_PAD_LAYOUT_DESC] = "Какая кнопка работает как A. Nintendo: по положению, A справа, B снизу, X сверху, "
"Y слева. Xbox и PlayStation: по подписи, A снизу, B справа, X слева, Y сверху (на "
"контроллере PlayStation крест, круг, квадрат и треугольник). Автоматически: "
"Nintendo для контроллера с подписями Nintendo, по подписи для остальных.",
[UI_MENU_PAD_LAYOUT_AUTO] = "Автоматически", [UI_MENU_PAD_LAYOUT_NINTENDO] = "Nintendo (A справа)",
[UI_MENU_PAD_LAYOUT_XBOX] = "Xbox (A снизу)",
[UI_MENU_PAD_LAYOUT_XBOX] = "Xbox (A снизу)", [UI_MENU_PAD_LAYOUT_PLAYSTATION] = "PlayStation (A снизу)",
[UI_MENU_PAD_LAYOUT_IN_USE] = "сейчас: %s",
[UI_MENU_PROMPTS] = "Подсказки кнопок",
[UI_MENU_PROMPTS_DESC] = "Кнопки какого контроллера показывают подсказки игры: в тексте, в подсказке действия и на "
@@ -1076,9 +1082,13 @@ static const char *const text_pt_br[UI_TEXT_COUNT] = {
[UI_MENU_RUMBLE_TRIGGERS] = "Vibração dos gatilhos (Xbox)",
[UI_MENU_RUMBLE_TRIGGERS_DESC] = "Também vibra os gatilhos do controle Xbox.",
[UI_MENU_PAD_LAYOUT] = "Layout dos botões",
[UI_MENU_PAD_LAYOUT_DESC] = "Define qual botão funciona como A.",
[UI_MENU_PAD_LAYOUT_DESC] = "Define qual botão funciona como A. Nintendo: pela posição, A à direita, B embaixo, "
"X em cima, Y à esquerda. Xbox e PlayStation: pela letra, A embaixo, B à direita, X à "
"esquerda, Y em cima (num controle PlayStation xis, círculo, quadrado e triângulo). "
"Automático: Nintendo para um controle com as letras da Nintendo, pela letra para os "
"outros.",
[UI_MENU_PAD_LAYOUT_AUTO] = "Automático", [UI_MENU_PAD_LAYOUT_NINTENDO] = "Nintendo (A à direita)",
[UI_MENU_PAD_LAYOUT_XBOX] = "Xbox (A embaixo)",
[UI_MENU_PAD_LAYOUT_XBOX] = "Xbox (A embaixo)", [UI_MENU_PAD_LAYOUT_PLAYSTATION] = "PlayStation (A embaixo)",
[UI_MENU_PAD_LAYOUT_IN_USE] = "em uso: %s",
[UI_MENU_GROUP_STICKS] = "Analógicos",
[UI_MENU_LSTICK_DEADZONE] = "Zona morta do analógico esquerdo",
@@ -1592,10 +1602,13 @@ static const char *const text_zh_hans[UI_TEXT_COUNT] = {
[UI_MENU_RUMBLE_TRIGGERS] = "扳机振动(Xbox)",
[UI_MENU_RUMBLE_TRIGGERS_DESC] = "同时震动 Xbox 手柄的扳机。",
[UI_MENU_PAD_LAYOUT] = "按键布局",
[UI_MENU_PAD_LAYOUT_DESC] = "设置哪个按键作为 A 键。",
[UI_MENU_PAD_LAYOUT_DESC] = "设置哪个按键作为 A 键。Nintendo:按位置,A 在右、B 在下、X 在上、Y 在左。Xbox 和 PlayStation:"
"按标注,A 在下、B 在右、X 在左、Y 在上(PlayStation 手柄上为叉、圆圈、方块和三角)。自动:"
"Nintendo 标注的手柄用 Nintendo,其他手柄按标注。",
[UI_MENU_PAD_LAYOUT_AUTO] = "自动",
[UI_MENU_PAD_LAYOUT_NINTENDO] = "Nintendo(A 在右)",
[UI_MENU_PAD_LAYOUT_XBOX] = "Xbox(A 在下)",
[UI_MENU_PAD_LAYOUT_PLAYSTATION] = "PlayStation(A 在下)",
[UI_MENU_PAD_LAYOUT_IN_USE] = "使用中:%s",
[UI_MENU_GROUP_STICKS] = "摇杆",
[UI_MENU_LSTICK_DEADZONE] = "左摇杆死区", [UI_MENU_RSTICK_DEADZONE] = "右摇杆死区",

View File

@@ -99,7 +99,7 @@ typedef enum {
UI_MENU_RUMBLE_CURVE, UI_MENU_RUMBLE_CURVE_DESC,
UI_MENU_RUMBLE_TRIGGERS, UI_MENU_RUMBLE_TRIGGERS_DESC,
UI_MENU_PAD_LAYOUT, UI_MENU_PAD_LAYOUT_DESC, UI_MENU_PAD_LAYOUT_AUTO, UI_MENU_PAD_LAYOUT_NINTENDO,
UI_MENU_PAD_LAYOUT_XBOX, UI_MENU_PAD_LAYOUT_IN_USE,
UI_MENU_PAD_LAYOUT_XBOX, UI_MENU_PAD_LAYOUT_PLAYSTATION, UI_MENU_PAD_LAYOUT_IN_USE,
UI_MENU_PROMPTS, UI_MENU_PROMPTS_DESC, UI_MENU_PROMPTS_NINTENDO, UI_MENU_PROMPTS_XBOX, UI_MENU_PROMPTS_PLAYSTATION,
UI_MENU_PROMPTS_PC,
// the notes under the prompts: their files are missing or damaged, they change after restart, or why they are off