// totk.ini: file-backed defaults for the TOTK_* environment variables (docs/controls.md, totk.ini.example). // Format: "key = value" lines, '#' or ';' at the start of a line begins a comment, [sections] only group. A key is a // variable name without "TOTK_" (an optional TOTK_ prefix is accepted), case-insensitive. A value is the rest of the // line, trimmed, optionally in double quotes; an empty value counts as unset. The environment wins over the file. // // Live settings: cfg_set changes a value in memory (readers see the new pointer; the old string is kept, so a pointer // cfg_get handed out stays valid) and cfg_save writes only the changed keys back, keeping the rest of the file. // Readers take no lock: an entry is complete before n_entries counts it, and value pointers are swapped atomically. #include #include #include #include #include #include "cfg.h" #include "host.h" #include "platform/platform.h" #define PREFIX "TOTK_" #define PREFIX_LEN 5 #define CFG_MAX_KEYS 512 typedef struct { char *key, *value; int dirty, removed; } CfgEntry; // key upper-case, without the prefix; value NULL after cfg_reset static CfgEntry entries[CFG_MAX_KEYS]; static int n_entries; static unsigned version; static PlatMutex set_lock = PLAT_MUTEX_INIT; // cfg_set, cfg_reset, cfg_save static int save_ok = 1; // the last cfg_save_to wrote (or had nothing to write) // Every name the runtime reads (and the patch sets in generated/), without the prefix. A file key outside this list is // logged as unknown. A new variable goes here too, or its ini key logs a harmless "unknown key" line, and gets a // description in totk.ini.example or docs/settings.md (tests/settings_doc_check.py checks both). static const char *const known[] = { "AB", "AMIIBO_DIR", "ANISO", "APP_POOL", "ASPECT", "ASPECT_PATCH", "ASSUME_FOCUS", "ASTC", "ASTC_BC", "ASTC_TIME", "ASYNC_PREEMPT", "AUDIO", "AUDIO_CHANNELS", "AUDIO_OFF", "AUDIO_PREFILL_MS", "BACKGROUND_WINDOW", "BIND_A", "BIND_B", "BIND_DOWN", "BIND_L", "BIND_LCLICK", "BIND_LEFT", "BIND_LSTICK_DOWN", "BIND_LSTICK_LEFT", "BIND_LSTICK_RIGHT", "BIND_LSTICK_UP", "BIND_MINUS", "BIND_PLUS", "BIND_R", "BIND_RCLICK", "BIND_RIGHT", "BIND_RSTICK_DOWN", "BIND_RSTICK_LEFT", "BIND_RSTICK_RIGHT", "BIND_RSTICK_UP", "BIND_SHAKE", "BIND_SKIP", "BIND_STATS", "BIND_UP", "BIND_X", "BIND_Y", "BIND_ZL", "BIND_ZR", "BLOCK_VIEWS_3D", "CLIMB_FIX", "CLOTH_REALTIME", "COLOR_BRIGHTNESS", "COLOR_CONTRAST", "COLOR_GAMMA", "COLOR_PRESET", "COLOR_SATURATION", "CONTACT_DEBUG", "CONTACT_SHADOWS", "CORE_DIAG", "CRIT", "CRIT_OUT", "DESC_TEMPLATE", "DESWIZZLE", "DEVICE_PROFILE", "DIALOGUE_CLICK", "DISCORD_APP_ID", "DISCORD_IMAGE", "DISCORD_RPC", "DLAA", "DLAA_JITTER", "DLAA_MV", "DLAA_MASKDEBUG", "DLAA_MVDEBUG", "DLAA_OBJMV", "DLAA_SURVEY", "DLAA_TIMES", "DLSS", "DLSS_CHAR_MASK", "DLSS_PRESET", "DLSS_REACTIVE", "DUMP", "DUMP_BUF", "DUMP_DIR", "DUMP_LOG", "DYNAMIC_RES", "DYN_DELTA", "EXEFS", "FEEDBACK", "FIXED_RES_PATCH", "FIX_C0", "FIX_ZERO", "FLIGHT", "FLIGHT_AT", "FLIGHT_DIR", "FLIGHT_FRAMES", "FLIGHT_SPIKE_MS", "FLIGHT_THUMBS", "FLUSH_SPAN", "FOV", "FOV_PATCH", "FPS60", "FPS_LIMIT", "FRAME_QUEUE", "FREEZE_IDLE", "FREE_CORES", "FULLSCREEN", "FXAA", "GPL", "GPL_DRAW_BUILD", "GPU", "GPU_INDEX", "GPU_NAME", "GRASS_RANGE", "HDR", "HDR_GAME_NITS", "HDR_HUE", "HDR_PEAK_NITS", "HDR_UI_NITS", "HIGHFPS_FIX", "INPUT", "INPUT_RECORD", "INPUT_SCRIPT", "KSPIN_US", "LANG", "LIB_PREBUILD", "LIB_PREBUILD_KEEP", "LIB_PREBUILD_THREADS", "LOCALE", "LOD_GUARD", "LSTICK_DEADZONE", "MAP_CLICK", "MEMORY", "MENU_LANG", "MENU_TEST", "MENU_FONT_FONTCONFIG", "MEM_RESERVE_MIB", "MENU_CURSOR", "MENU_CURSOR_LOG", "MENU_TEST_DIR", "MENU_TEST_SET", "MINIMAL_LAYOUT", "MIRROR_GAP", "MODS_DIR", "MODS_DISABLED", "MODS_ORDER", "MODS_REBUILD", "MOTION", "MOUSE_AIM_SCALE", "MOUSE_MODE", "MOUSE_DIRECT_PATCH", "MOUSE_SENS", "MOVIE_ASPECT", "MOVIE_HOST", "MOVIE_PACE", "MVOBJ_ALL", "MVOBJ_BANKS", "MVOBJ_EQUAL", "MVOBJ_EXACT", "MVOBJ_EXEC", "MVOBJ_NOC4", "MVOBJ_ONLY", "MVOBJ_TIMES", "MVPREV_CHECK", "MVPREV_GROW", "MVPREV_SKIP", "MVPREV_TRUST", "MV_CHECK", "MV_SURVEY", "ONE_PER_CORE", "OPERATION_MODE", "OUTPUT_RES", "PAD_LAYOUT", "PASS_CRUMBS", "PATCH_OUTPUT", "PATCH_SCENE", "PCORE", "PCSS", "PCSS_K", "PERF_OVERLAY", "PERF_SLOW_MS", "PIPELINE_CACHE", "PIPE_MEMO", "PIPE_PREBUILD", "PIPE_SKIP", "PIPE_SYNC", "PIPE_WAIT", "POINTER_IMAGE", "PREWARM_XLATE", "PROBE_MOVIE", "PROBE_MOVIE_BENCH", "PROBE_MOVIE_SHOT", "PROFILE", "PROFILE_CHAINS", "PROFILE_DIR", "PROFILE_FRAMES", "PROFILE_MAIN", "PROFILE_RENDER", "PROMPTS", "PUSH_SKIP", "PUSH_STAGE", "RDOC", "RDOC_DIR", "RDOC_DLL", "REALTIME", "REC_ASYNC", "REC_END", "REC_SPIN_US", "REEXEC", "REFLEX", "RENDER_SCALE", "REPLAY_ASYNC", "REPLAY_CHUNKS", "REPLAY_CHUNK_DRAWS", "REPLAY_SKEW_US", "REPLAY_STATE_CHECK", "REPLAY_WORKERS", "REPLAY_CHECK", "RESOLVE_CHECK", "RESTART_WAIT", "ROMFS", "ROMFS_INDEX", "ROMFS_MMAP", "ROMFS_MODS", "ROMFS_PATCHES", "ROMFS_PROOF_DIR", "ROMFS_VERIFY", "RSTICK_DEADZONE", "RUMBLE", "RUMBLE_CURVE", "RUMBLE_STRENGTH", "RUMBLE_TRIGGERS", "SHADER_CACHE", "SHADER_MEMO", "SHADOW_DEFER", "SHADOW_RANGE", "SHADOW_RES", "SHADOW_SCALE", "SHADOW_SNAP", "SHARED_FONTS", "SHARPNESS", "SHOTS", "SHOTS_DIR", "SHOTS_FULL", "SPEED_PROBE", "SPIN", "SPIN_HANDOFF_US", "SPLIT_DRAWS", "SPLIT_US", "STICK_DEADZONE", "STICK_SENS", "SWKBD_ANSWER", "SYNC_AHEAD", "SWKBD_TEST", "TEST_OOM", "TEST_PAD", "TEST_PREPARE_SHADERS", "TEX_ALIAS", "TEX_BLOCK_MIB", "TEX_HEAP_EXTRA_MB", "TEX_MEMO", "TEX_RELEASED_MIB", "TEX_VRAM_PRESSURE", "TIMER_1MS", "TRACE", "TXD_TRANSPOSE", "UBO_SNAPSHOT", "UB_OVERLAY", "UB_OVERLAY_OUT", "UPSCALER", "VB_USED", "VK_DYN_A2C", "VK_DYN_VERTEX", "VK_NO_FLOAT64", "VK_PRESENT", "VK_RECORDER", "VK_SSBO_ALIGN", "VK_SUBGROUP", "VK_SUBGROUP_PIN", "VK_VALIDATION", "VMA_BLOCK_MIB", "VOLUME", "VRAM_HEADROOM", "VRAM_LIMIT_MIB", "VS_OUTS", "WALK_KEY", "WALK_SCALE", "WB_EXACT", "WB_FENCE_LIMIT", "WB_MERGE", "WINDOW_SIZE", "XLATE", "XLATE_ID", "XLATE_KEEP", "XLATE_PROCS", "ZSTD_CHECK", "ZSTD_NATIVE", }; // The totk.ini section a key cfg_set may write goes to when the file lacks it (totk.ini.example's sections). static const struct { const char *key, *section; } sections[] = { {"FPS_LIMIT", "frame rate"}, {"FPS60", "frame rate"}, {"FRAME_QUEUE", "frame rate"}, {"REALTIME", "frame rate"}, {"CLOTH_REALTIME", "frame rate"}, {"HIGHFPS_FIX", "frame rate"}, {"CLIMB_FIX", "frame rate"}, {"FULLSCREEN", "display"}, {"WINDOW_SIZE", "display"}, {"PERF_OVERLAY", "display"}, {"VK_PRESENT", "display"}, {"GPU_INDEX", "display"}, {"OUTPUT_RES", "display"}, {"RENDER_SCALE", "display"}, {"UPSCALER", "display"}, {"SHARPNESS", "display"}, {"DYNAMIC_RES", "display"}, {"ASPECT", "display"}, {"MOVIE_ASPECT", "display"}, {"FOV", "display"}, {"ANISO", "display"}, {"COLOR_PRESET", "display"}, {"COLOR_BRIGHTNESS", "display"}, {"COLOR_CONTRAST", "display"}, {"COLOR_SATURATION", "display"}, {"COLOR_GAMMA", "display"}, {"HDR", "display"}, {"HDR_PEAK_NITS", "display"}, {"HDR_GAME_NITS", "display"}, {"HDR_UI_NITS", "display"}, {"HDR_HUE", "display"}, {"SHADOW_RES", "display"}, {"DLAA", "display"}, {"DLSS", "display"}, {"DLSS_PRESET", "display"}, {"DLSS_CHAR_MASK", "display"}, {"REFLEX", "display"}, {"FXAA", "display"}, {"DEVICE_PROFILE", "display"}, {"ASSUME_FOCUS", "display"}, {"DISCORD_RPC", "discord"}, {"DISCORD_APP_ID", "discord"}, {"DISCORD_IMAGE", "discord"}, {"SHADOW_SCALE", "display"}, {"SHADOW_RANGE", "display"}, {"GRASS_RANGE", "display"}, {"CONTACT_SHADOWS", "display"}, {"PCSS", "display"}, {"MODS_DIR", "mods"}, {"MODS_ORDER", "mods"}, {"MODS_DISABLED", "mods"}, {"MODS_REBUILD", "mods"}, {"TEX_HEAP_EXTRA_MB", "mods"}, {"MENU_LANG", "language"}, {"MENU_FONT_FONTCONFIG", "language"}, {"LANG", "language"}, {"OPERATION_MODE", "language"}, {"VOLUME", "audio"}, {"AUDIO_CHANNELS", "audio"}, {"MOTION", "input"}, {"RUMBLE_STRENGTH", "input"}, {"RUMBLE_CURVE", "input"}, {"RUMBLE_TRIGGERS", "input"}, {"PAD_LAYOUT", "input"}, {"PROMPTS", "input"}, {"STICK_DEADZONE", "input"}, {"LSTICK_DEADZONE", "input"}, {"RSTICK_DEADZONE", "input"}, {"STICK_SENS", "input"}, {"MENU_CURSOR", "input"}, {"MAP_CLICK", "input"}, {"DIALOGUE_CLICK", "input"}, {"POINTER_IMAGE", "input"}, {"MOUSE_MODE", "input"}, {"MOUSE_SENS", "input"}, {"MOUSE_AIM_SCALE", "input"}, {"WALK_KEY", "input"}, {"WALK_SCALE", "input"}, {"INPUT_RECORD", "input"}, {"BIND_A", "input"}, {"BIND_B", "input"}, {"BIND_X", "input"}, {"BIND_Y", "input"}, {"BIND_LCLICK", "input"}, {"BIND_RCLICK", "input"}, {"BIND_L", "input"}, {"BIND_R", "input"}, {"BIND_ZL", "input"}, {"BIND_ZR", "input"}, {"BIND_PLUS", "input"}, {"BIND_MINUS", "input"}, {"BIND_LEFT", "input"}, {"BIND_UP", "input"}, {"BIND_RIGHT", "input"}, {"BIND_DOWN", "input"}, {"BIND_LSTICK_LEFT", "input"}, {"BIND_LSTICK_UP", "input"}, {"BIND_LSTICK_RIGHT", "input"}, {"BIND_LSTICK_DOWN", "input"}, {"BIND_RSTICK_LEFT", "input"}, {"BIND_RSTICK_UP", "input"}, {"BIND_RSTICK_RIGHT", "input"}, {"BIND_RSTICK_DOWN", "input"}, {"BIND_SHAKE", "input"}, {"ASYNC_PREEMPT", "performance"}, {"REPLAY_ASYNC", "performance"}, {"REPLAY_WORKERS", "performance"}, {"FREE_CORES", "performance"}, {"TIMER_1MS", "performance"}, {"PIPE_WAIT", "performance"}, {"PIPE_SKIP", "performance"}, {"LIB_PREBUILD", "performance"}, {"LIB_PREBUILD_THREADS", "performance"}, {"PIPE_PREBUILD", "performance"}, {"PREWARM_XLATE", "performance"}, {"GPL", "performance"}, {"ASTC", "performance"}, {"MEMORY", "performance"}, {"TEX_ALIAS", "performance"}, {"TEX_VRAM_PRESSURE", "performance"}, {"TEX_RELEASED_MIB", "performance"}, {"TEX_BLOCK_MIB", "performance"}, {"VMA_BLOCK_MIB", "performance"}, {"ASTC_BC", "performance"}, {"LOD_GUARD", "performance"}, {"FIX_C0", "performance"}, {"FIX_ZERO", "performance"}, {"BIND_SKIP", "performance"}, {"SHADER_MEMO", "performance"}, {"PIPE_MEMO", "performance"}, {"TEX_MEMO", "performance"}, {"VB_USED", "performance"}, {"DESC_TEMPLATE", "performance"}, {"SYNC_AHEAD", "performance"}, {"SHADOW_DEFER", "performance"}, {"PUSH_SKIP", "performance"}, {"SPLIT_DRAWS", "performance"}, {"SPLIT_US", "performance"}, {"VK_VALIDATION", "performance"}, {"VK_DYN_VERTEX", "performance"}, {"VK_DYN_A2C", "performance"}, {"VK_SUBGROUP_PIN", "performance"}, {"TRACE", "diagnostics"}, {"FLIGHT", "diagnostics"}, {"PASS_CRUMBS", "diagnostics"}, {"FLIGHT_SPIKE_MS", "diagnostics"}, {"WB_FENCE_LIMIT", "diagnostics"}, {"WB_MERGE", "diagnostics"}, {"WB_EXACT", "diagnostics"}, {"MV_SURVEY", "diagnostics"}, }; // The settings the menu shows: the value in effect when the key is unset, and what the key accepts. The only place a // default lives: consumers read cfg_value / cfg_int / cfg_double / cfg_bool. A row with an empty default has no fixed // value (it depends on the machine); its consumer reads cfg_get and keeps an "unset" branch. #define LIVE 1 #define RESTART 0 static const CfgMeta metas[] = { // Screen {"FULLSCREEN", "0", "0|1", LIVE}, {"WINDOW_SIZE", "1920x1080", NULL, LIVE}, {"VK_PRESENT", "mailbox", "mailbox|fifo|immediate", LIVE}, {"GPU_INDEX", "", NULL, RESTART}, {"PERF_OVERLAY", "off", "off|fps|full", LIVE}, {"OUTPUT_RES", "1920x1080", NULL, RESTART}, {"RENDER_SCALE", "game", "game|50|67|75|100|125|150|200", RESTART}, {"DYNAMIC_RES", "1", "0|1", LIVE}, // resolution.c res_dynres_live {"UPSCALER", "fsr", "fsr|bilinear", LIVE}, {"SHARPNESS", "80", "0..100", LIVE}, {"DLAA", "0", "0|1", RESTART}, {"DLSS", "dlaa", "dlaa|quality|balanced|performance|ultra_performance", LIVE}, {"DLSS_PRESET", "default", "default|k|j", LIVE}, {"DLSS_CHAR_MASK", "65", "0|35|65|100", LIVE}, {"REFLEX", "off", "off|on|boost", RESTART}, {"FXAA", "1", "0|1", LIVE}, {"ASPECT", "fit", "fit|stretch", LIVE}, {"MOVIE_ASPECT", "keep", "keep|stretch", LIVE}, {"FOV", "100", "70..140", LIVE}, {"ANISO", "game", "game|2|4|8|16", RESTART}, // vk_texture.c sampler_features // colour adjustment (vk_color.c); the preset is the menu's, the four values are what the present uses {"COLOR_PRESET", "default", "default|vivid|clarity|custom", LIVE}, {"COLOR_BRIGHTNESS", "0", "-30..30", LIVE}, {"COLOR_CONTRAST", "100", "70..150", LIVE}, {"COLOR_SATURATION", "100", "50..200", LIVE}, {"COLOR_GAMMA", "1", "0.8..1.2", LIVE}, // HDR output (vk_hdr.c): on at start (the tonemap shader is swapped as shaders load); the nits apply at once {"HDR", "0", "0|1", RESTART}, {"HDR_PEAK_NITS", "1000", "400..10000", LIVE}, {"HDR_GAME_NITS", "203", "80..500", LIVE}, {"HDR_UI_NITS", "203", "80..500", LIVE}, {"HDR_HUE", "80", "0..100", LIVE}, {"DEVICE_PROFILE", "auto", "auto|desktop|deck", RESTART}, {"SHADOW_RES", "game", "game|256|512|768|1024|1536|2048", RESTART}, {"SHADOW_SCALE", "1", "1..4", RESTART}, {"SHADOW_RANGE", "100", "50..400", RESTART}, {"GRASS_RANGE", "100", "50..400", RESTART}, {"CONTACT_SHADOWS", "0", "0..100", RESTART}, {"PCSS", "0", "0..200", RESTART}, // Game {"FPS_LIMIT", "60", "30|40|45|60|90|120|144|display", LIVE}, {"FPS60", "1", "0|1", LIVE}, {"FRAME_QUEUE", "0", "0|1", LIVE}, {"REALTIME", "1", "0|1", RESTART}, {"CLOTH_REALTIME", "1", "0|1", RESTART}, {"MODS_REBUILD", "auto", "auto|off", RESTART}, {"HIGHFPS_FIX", "1", "0|1", RESTART}, {"CLIMB_FIX", "1", "0|1", RESTART}, {"LANG", "", "ja|en-US|fr|de|it|es|zh-CN|ko|nl|pt|ru|zh-TW|en-GB|fr-CA|es-419|zh-Hans|zh-Hant|pt-BR", RESTART}, {"OPERATION_MODE", "docked", "docked|handheld", RESTART}, {"ASSUME_FOCUS", "1", "0|1", LIVE}, // read on every focus change (ov_system.c) // Sound, Controls, Menu {"VOLUME", "100", "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", "auto", "auto|nintendo|xbox", LIVE}, {"PROMPTS", "nintendo", "nintendo|xbox|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}, {"MOUSE_MODE", "direct", "stick|direct", LIVE}, {"MOUSE_SENS", "1", "0..10", LIVE}, {"MOUSE_AIM_SCALE", "1", "0.1..3", LIVE}, {"MENU_CURSOR", "1", "0|1", LIVE}, {"MAP_CLICK", "0", "0|1", LIVE}, // input.c: the left mouse button is A on the Purah Pad's map {"DIALOGUE_CLICK", "0", "0|1", LIVE}, // menu_cursor.c: in a dialogue box left click is B, right click A {"POINTER_IMAGE", "0", "0|1", LIVE}, // input_win.c / input_sdl.c: the game window's own pointer (cursor_art.c) {"WALK_KEY", "none", NULL, LIVE}, {"WALK_SCALE", "0.51", "0.2..0.9", LIVE}, {"MENU_LANG", "", "en|ru|pt-BR|zh-Hans", LIVE}, // plat_font_path on Linux: 1 asks fontconfig which file draws the language, 0 keeps the fixed distribution // paths. A rebuild of the atlas reads it, so a change shows on the next window size or language switch. {"MENU_FONT_FONTCONFIG", "1", "0|1", LIVE}, {"AMIIBO_DIR", "", NULL, LIVE}, // Discord Rich Presence (discord_rpc.c): off by default, the Menu tab of the settings menu switches it. {"DISCORD_RPC", "0", "0|1", LIVE}, {"DISCORD_APP_ID", "1567232599846293504", NULL, LIVE}, {"DISCORD_IMAGE", "totk_512x512", NULL, LIVE}, // Advanced: scheduling {"ASYNC_PREEMPT", "1", "0|1|2", RESTART}, {"REPLAY_ASYNC", "0", "0|1", RESTART}, {"REPLAY_WORKERS", "0", "0..16", RESTART}, {"REPLAY_CHUNKS", "0", "0..64", RESTART}, {"REPLAY_CHUNK_DRAWS", "128", "1..100000", RESTART}, {"REPLAY_STATE_CHECK", "0", "0|1", RESTART}, {"REPLAY_SKEW_US", "0", "0..100000", RESTART}, {"SYNC_AHEAD", "0", "0|1", RESTART}, {"SHADOW_DEFER", "1", "0|1", RESTART}, {"FREE_CORES", "0", "0|1", RESTART}, {"TIMER_1MS", "1", "0|1", RESTART}, // shaders and pipelines {"PIPE_WAIT", "default", "default|draw", RESTART}, {"PIPE_SKIP", "fullscreen", "strict|any|fullscreen", RESTART}, {"LIB_PREBUILD", "1", "0|1", RESTART}, {"LIB_PREBUILD_THREADS", "", "1..64", RESTART}, {"PIPE_PREBUILD", "1", "0|1", RESTART}, {"PREWARM_XLATE", "", "1..64", RESTART}, {"GPL", "1", "0|1", RESTART}, // textures and memory {"ASTC", "auto", "auto|gpu|native|cpu", RESTART}, {"MEMORY", "auto", "auto|import|mirror|verify", RESTART}, {"TEX_ALIAS", "1", "0|1", RESTART}, {"TEX_VRAM_PRESSURE", "90", "0..100", RESTART}, {"TEX_RELEASED_MIB", "256", "0..65536", RESTART}, {"TEX_BLOCK_MIB", "64", "0..1024", RESTART}, {"VMA_BLOCK_MIB", "64", "0..1024", RESTART}, {"ASTC_BC", "", "0|1", RESTART}, {"ZSTD_NATIVE", "1", "0|1", RESTART}, {"ZSTD_CHECK", "0", "0|1", RESTART}, // render fixes {"LOD_GUARD", "1", "0|1", RESTART}, {"FIX_C0", "1", "0|1", RESTART}, {"FIX_ZERO", "1", "0|1", RESTART}, {"BIND_SKIP", "1", "0|1", RESTART}, {"SHADER_MEMO", "1", "0|1", RESTART}, {"PIPE_MEMO", "1", "0|1", RESTART}, {"TEX_MEMO", "1", "0|1", RESTART}, {"VB_USED", "1", "0|1", RESTART}, {"DESC_TEMPLATE", "1", "0|1", RESTART}, {"PUSH_SKIP", "1", "0|1", RESTART}, {"SPLIT_DRAWS", "64", "0..1000", RESTART}, {"SPLIT_US", "1000", "0..20000", RESTART}, // Vulkan {"VK_VALIDATION", "0", "0|1", RESTART}, {"VK_DYN_VERTEX", "", "0|1", RESTART}, {"VK_DYN_A2C", "", "0|1", RESTART}, {"VK_SUBGROUP_PIN", "1", "0|1", RESTART}, // diagnostics {"TRACE", "", NULL, RESTART}, {"FLIGHT", "0", "0|1", RESTART}, {"FLIGHT_SPIKE_MS", "100", "0..60000", RESTART}, {"WB_FENCE_LIMIT", "65536", "0..1073741824", RESTART}, {"WB_MERGE", "1", "0|1", RESTART}, {"WB_EXACT", "1", "0|1", RESTART}, {"PASS_CRUMBS", "", "0|1", RESTART}, // vk_crumbs.c pass marks; unset: VK_AMD_buffer_marker only {"INPUT_RECORD", "", NULL, RESTART}, }; static int is_known(const char *key) { for (size_t i = 0; i < sizeof known / sizeof *known; i++) if (!strcmp(known[i], key)) return 1; return !strncmp(key, "CHEAT_", 6); // cheats.c and the cheat patch sets (tools/cheats.py) } static void clear(void) { for (int i = 0; i < n_entries; i++) free(entries[i].key), free(entries[i].value); n_entries = 0; } static char *trim(char *s) { while (*s == ' ' || *s == '\t') s++; char *e = s + strlen(s); while (e > s && (e[-1] == ' ' || e[-1] == '\t' || e[-1] == '\r')) *--e = 0; return s; } // The upper-case key without the prefix, in place. static char *norm_key(char *key) { for (char *p = key; *p; p++) *p = (char)toupper((unsigned char)*p); return strncmp(key, PREFIX, PREFIX_LEN) ? key : key + PREFIX_LEN; } static void add(char *key, char *value, int line) { key = norm_key(key); size_t vl = strlen(value); if (vl >= 2 && value[0] == '"' && value[vl - 1] == '"') value[vl - 1] = 0, value++; if (!*key) return; if (!is_known(key)) rt_log("cfg: totk.ini line %d: unknown key '%s'", line, key); if (!*value) return; // "key =" leaves the setting at its default for (int i = 0; i < n_entries; i++) if (!strcmp(entries[i].key, key)) { // a repeated key: the later line wins free(entries[i].value); entries[i].value = strdup(value); return; } if (n_entries == CFG_MAX_KEYS) return; entries[n_entries++] = (CfgEntry){strdup(key), strdup(value), 0, 0}; } // 0.1.9 had the Xbox prompts as a pad layout: pad_layout = xbox_prompts is pad_layout = nintendo with prompts = xbox. // Read as those two, which the menu shows and the next cfg_save writes; a prompts line of the file's own wins. The // value still works from the environment (input_pad_layout_pick, input_prompts_pick). static void migrate(void) { CfgEntry *layout = NULL, *prompts = NULL; for (int i = 0; i < n_entries; i++) { if (!strcmp(entries[i].key, "PAD_LAYOUT")) layout = &entries[i]; if (!strcmp(entries[i].key, "PROMPTS")) prompts = &entries[i]; } if (!layout || strcasecmp(layout->value, "xbox_prompts")) return; free(layout->value); layout->value = strdup("nintendo"); layout->dirty = 1; if (!prompts && n_entries < CFG_MAX_KEYS) entries[n_entries++] = (CfgEntry){strdup("PROMPTS"), strdup("xbox"), 1, 0}; rt_log("cfg: totk.ini pad_layout = xbox_prompts is read as pad_layout = nintendo and prompts = %s", prompts ? prompts->value : "xbox"); } int cfg_load(const char *path) { clear(); uint64_t size = 0; uint8_t *data = plat_file_read(path, &size); if (!data) return 0; char *p = (char *)data; if (size >= 3 && !memcmp(p, "\xEF\xBB\xBF", 3)) p += 3; // editors add a BOM for (int line = 1; *p; line++) { char *eol = strchr(p, '\n'); if (eol) *eol = 0; char *s = trim(p); char *eq = strchr(s, '='); if (*s && *s != '#' && *s != ';' && *s != '[') { if (!eq) rt_log("cfg: totk.ini line %d: no '=' in '%s'", line, s); else { *eq = 0; add(trim(s), trim(eq + 1), line); } } if (!eol) break; p = eol + 1; } free(data); migrate(); return 1; } // ---- device profiles: defaults for a known machine, below the environment and totk.ini and above the table, so any // key the player sets wins and resetting a key in the menu returns to the profile's value. TOTK_DEVICE_PROFILE = auto // (default: the Steam Deck when the firmware says so), desktop (none) or deck. // // The Steam Deck (LCD Jupiter, OLED Galileo; 1280x800, 16:10, 4 Zen 2 cores, RDNA 2 with 8 CUs sharing 16 GB): the game // at the screen's 1280x800 with its own 5/6 scene (the aspect patch gives the camera its 16:10, resolution.c, and the // romfs-patches-1280x800 overlay the HUD's); // fullscreen and vsync, as gamescope composes Game Mode; 40 fps, which divides 40 Hz (and 120 Hz on the OLED). // Starting points to replace with measured ones (docs/tester-guide.md, Steam Deck). static const struct { const char *key, *value; } DECK[] = { {"FULLSCREEN", "1"}, {"WINDOW_SIZE", "1280x800"}, {"OUTPUT_RES", "1280x800"}, {"RENDER_SCALE", "game"}, {"VK_PRESENT", "fifo"}, {"FPS_LIMIT", "40"}, }; static int profile = -1; // 0 none, 1 Steam Deck static int read_line(const char *path, char *out, size_t n) { FILE *f = fopen(path, "r"); if (!f) return 0; int ok = fgets(out, (int)n, f) != NULL; fclose(f); out[ok ? strcspn(out, "\r\n") : 0] = 0; return ok; } // The Steam Deck: Valve's board, product Jupiter or Galileo (/sys/class/dmi/id); SteamDeck=1, which Steam sets on it. static int on_steam_deck(void) { const char *env = getenv("SteamDeck"); if (env && !strcmp(env, "1")) return 1; char vendor[64], product[64]; return read_line("/sys/class/dmi/id/board_vendor", vendor, sizeof vendor) && !strcmp(vendor, "Valve") && read_line("/sys/class/dmi/id/product_name", product, sizeof product) && (!strcmp(product, "Jupiter") || !strcmp(product, "Galileo")); } static const char *raw_get(const char *name); static int profile_of(void) { int p = __atomic_load_n(&profile, __ATOMIC_ACQUIRE); if (p >= 0) return p; const char *set = raw_get(PREFIX "DEVICE_PROFILE"); if (set && !strcmp(set, "deck")) p = 1; else if (set && *set && strcmp(set, "auto")) p = 0; else p = on_steam_deck(); __atomic_store_n(&profile, p, __ATOMIC_RELEASE); return p; } const char *cfg_device_profile(void) { return profile_of() ? "deck" : "desktop"; } static const char *raw_get(const char *name) { const char *e = getenv(name); if (e || strncmp(name, PREFIX, PREFIX_LEN)) return e; int n = __atomic_load_n(&n_entries, __ATOMIC_ACQUIRE); for (int i = 0; i < n; i++) if (!strcasecmp(entries[i].key, name + PREFIX_LEN)) return __atomic_load_n(&entries[i].value, __ATOMIC_ACQUIRE); return NULL; } // ---- the screen's shape (desktop): a primary display of another shape than 16:9 gives output_res a default of that // shape while neither the environment nor totk.ini sets it, so the first start fills the screen instead of a 16:9 // picture between black bars. The sizes are the ones the installer lays the HUD out for (installer/steps.py // ASPECT_SIZES, ui/menu.cpp row_output_res): of the display's shape within 1%, the smallest at least 1080 high that fits // the display, else the largest that fits. A 16:9 display, an unknown one or one of another shape keeps 1920x1080. // Xbox and keyboard prompts have a folder for each of these sizes (romfs-patches-xbox-x, -xbox-label-x, // -pc-x, installer/steps.py Prepare): they keep 1920x1080 only when the one for the display's size is missing, // as in a source build without make_romfs_patches.py --prompts