SDL_Libretro 0.1.0
SDL3-power libretro frontend.
Loading...
Searching...
No Matches
SDL_libretro_options.h
Go to the documentation of this file.
1
6#if defined(SDL_LIBRETRO_IMPLEMENTATION) && !defined(SDL_LIBRETRO_OPTIONS_IMPL_ONCE)
7#define SDL_LIBRETRO_OPTIONS_IMPL_ONCE
8
9static char* SDL_Libretro_Strdup(const char* s) {
10 if (!s) return SDL_strdup("");
11 return SDL_strdup(s);
12}
13
18static bool SDL_Libretro_IsOptionValueValid(const SDL_LibretroOption* opt, const char* value) {
19 if (!opt || !value) return false;
20 if (opt->valuesCount == 0) return true;
21 for (unsigned v = 0; v < opt->valuesCount; v++) {
22 if (opt->values[v].value && SDL_strcmp(opt->values[v].value, value) == 0) {
23 return true;
24 }
25 }
26 return false;
27}
28
33static void SDL_Libretro_FreeOptionValues(SDL_LibretroOptionValue* values, unsigned count) {
34 if (!values) return;
35 for (unsigned v = 0; v < count; v++) {
36 SDL_free((void*)values[v].value);
37 SDL_free((void*)values[v].label);
38 }
39 SDL_free(values);
40}
41
51static bool SDL_Libretro_CopyOptionValues(const struct retro_core_option_value* values, unsigned valuesMax, SDL_LibretroOptionValue** outValues, unsigned* outCount) {
52 *outValues = NULL;
53 *outCount = 0;
54 if (!values) return true;
55
56 // Count how many values there are available for the option.
57 unsigned count = 0;
58 while (count < valuesMax && values[count].value) count++;
59 if (count == 0) return true;
60
62 if (!copy) return false;
63 for (unsigned v = 0; v < count; v++) {
64 copy[v].value = SDL_strdup(values[v].value);
65 copy[v].label = values[v].label ? SDL_strdup(values[v].label) : NULL;
66 if (!copy[v].value || (values[v].label && !copy[v].label)) {
67 SDL_Libretro_FreeOptionValues(copy, v + 1);
68 return false;
69 }
70 }
71 *outValues = copy;
72 *outCount = count;
73 return true;
74}
75
76static void SDL_Libretro_InitCoreOption(SDL_Libretro* lr, const char* key, const char* defaultValue, const char* desc, const struct retro_core_option_value* values, unsigned valuesMax, const char* info, const char* categoryKey) {
77 if (!lr || !key) return;
78
80 bool reregistered = slot != NULL;
81
82 // Grow array if needed
83 if (!reregistered && lr->core.optionCount >= lr->core.optionCapacity) {
84 unsigned newCap = lr->core.optionCapacity ? lr->core.optionCapacity * 2 : 16;
85 SDL_LibretroOption* newOpts = (SDL_LibretroOption*)SDL_realloc(lr->core.options, newCap * sizeof(SDL_LibretroOption));
86 if (!newOpts) return;
87 lr->core.options = newOpts;
88 lr->core.optionCapacity = newCap;
89 }
90
91 // Copy everything up front, so running out of memory leaves any existing
92 // option untouched. Strings are deep-copied and owned by the context.
93 SDL_LibretroOption copy = {0};
94 copy.key = reregistered ? NULL : SDL_Libretro_Strdup(key);
95 copy.defaultValue = SDL_Libretro_Strdup(defaultValue);
96 copy.desc = SDL_Libretro_Strdup(desc);
97 copy.info = SDL_Libretro_Strdup(info);
98 copy.category = SDL_Libretro_Strdup(categoryKey);
99 bool ok = (reregistered || copy.key) && copy.defaultValue && copy.desc && copy.info && copy.category;
100 ok = ok && SDL_Libretro_CopyOptionValues(values, valuesMax, &copy.values, &copy.valuesCount);
101 copy.valuesCapacity = copy.valuesCount;
102
103 // Pick the value: a re-registered option keeps its current value while
104 // it's still valid. Otherwise use the saved value, since cores may register
105 // options after the saved core config was loaded (in retro_init() or
106 // retro_load_game()), falling back to the default.
107 const char* current = reregistered ? slot->value : NULL;
108 bool keepCurrent = current && SDL_Libretro_IsOptionValueValid(&copy, current);
109 if (ok && !keepCurrent) {
110 const char* saved = SDL_Libretro_GetSavedCoreOption(lr, key);
111 copy.value = SDL_strdup((saved && SDL_Libretro_IsOptionValueValid(&copy, saved)) ? saved : copy.defaultValue);
112 ok = copy.value != NULL;
113 }
114
115 if (!ok) {
116 SDL_free((void*)copy.key);
117 SDL_free((void*)copy.value);
118 SDL_free((void*)copy.defaultValue);
119 SDL_free((void*)copy.desc);
120 SDL_free((void*)copy.info);
121 SDL_free((void*)copy.category);
122 SDL_Libretro_FreeOptionValues(copy.values, copy.valuesCount);
123 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[SDL_Libretro] Out of memory registering option '%s'", key);
124 return;
125 }
126
127 if (!reregistered) {
128 // The core hasn't read a new option yet, so it isn't flagged as updated.
129 copy.visible = true;
130 lr->core.options[lr->core.optionCount++] = copy;
131 return;
132 }
133
134 // Re-registration: the core may have changed the values, default, or text.
135 SDL_free((void*)slot->defaultValue);
136 SDL_free((void*)slot->desc);
137 SDL_free((void*)slot->info);
138 SDL_free((void*)slot->category);
139 SDL_Libretro_FreeOptionValues(slot->values, slot->valuesCount);
140 slot->defaultValue = copy.defaultValue;
141 slot->desc = copy.desc;
142 slot->info = copy.info;
143 slot->category = copy.category;
144 slot->values = copy.values;
145 slot->valuesCount = copy.valuesCount;
146 slot->valuesCapacity = copy.valuesCapacity;
147 if (!keepCurrent) {
148 SDL_free((void*)slot->value);
149 slot->value = copy.value;
150 lr->core.optionsDirtyCore = true;
151 }
152 lr->core.optionsDirtyApp = true;
153}
154
155static void SDL_Libretro_InitCoreOptionCategory(SDL_Libretro* lr, const char* key, const char* desc, const char* info) {
156 if (!lr || !key || key[0] == '\0') return;
157
158 // Re-registration: the core may have changed the text.
159 for (unsigned i = 0; i < lr->core.optionCategoriesCount; i++) {
160 SDL_LibretroCategory* existing = &lr->core.optionCategories[i];
161 if (existing->key && SDL_strcmp(existing->key, key) == 0) {
162 // Copy first, so running out of memory leaves the category untouched.
163 char* newDesc = SDL_Libretro_Strdup(desc);
164 char* newInfo = SDL_Libretro_Strdup(info);
165 if (!newDesc || !newInfo) {
166 SDL_free(newDesc);
167 SDL_free(newInfo);
168 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[SDL_Libretro] Out of memory registering option category '%s'", key);
169 return;
170 }
171 SDL_free((void*)existing->desc);
172 SDL_free((void*)existing->info);
173 existing->desc = newDesc;
174 existing->info = newInfo;
175 lr->core.optionsDirtyApp = true;
176 return;
177 }
178 }
179
180 // Grow array if needed
181 if (lr->core.optionCategoriesCount >= lr->core.optionCategoriesCapacity) {
182 unsigned newCap = lr->core.optionCategoriesCapacity ? lr->core.optionCategoriesCapacity * 2 : 8;
183 SDL_LibretroCategory* newCats = (SDL_LibretroCategory*)SDL_realloc(lr->core.optionCategories, newCap * sizeof(SDL_LibretroCategory));
184 if (!newCats) return;
185 lr->core.optionCategories = newCats;
186 lr->core.optionCategoriesCapacity = newCap;
187 }
188
189 // Set the category; strings are deep-copied and owned by the context.
190 SDL_LibretroCategory* cat = &lr->core.optionCategories[lr->core.optionCategoriesCount];
191 cat->key = SDL_Libretro_Strdup(key);
192 cat->desc = SDL_Libretro_Strdup(desc);
193 cat->info = SDL_Libretro_Strdup(info);
194 if (!cat->key || !cat->desc || !cat->info) {
195 SDL_free((void*)cat->key);
196 SDL_free((void*)cat->desc);
197 SDL_free((void*)cat->info);
198 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[SDL_Libretro] Out of memory registering option category '%s'", key);
199 return;
200 }
201
202 lr->core.optionCategoriesCount++;
203}
204
205static void SDL_Libretro_FreeCoreOptions(SDL_Libretro* lr) {
206 if (!lr) return;
207
208 // Options
209 if (lr->core.options) {
210 for (unsigned i = 0; i < lr->core.optionCount; i++) {
211 SDL_LibretroOption* opt = &lr->core.options[i];
212 SDL_free((void*)opt->key);
213 SDL_free((void*)opt->value);
214 SDL_free((void*)opt->defaultValue);
215 SDL_free((void*)opt->desc);
216 SDL_free((void*)opt->info);
217 SDL_free((void*)opt->category);
218 SDL_Libretro_FreeOptionValues(opt->values, opt->valuesCount);
219 }
220 SDL_free(lr->core.options);
221 lr->core.options = NULL;
222 }
223 lr->core.optionCount = 0;
224 lr->core.optionCapacity = 0;
225
226 // Categories
227 if (lr->core.optionCategories) {
228 for (unsigned i = 0; i < lr->core.optionCategoriesCount; i++) {
229 SDL_free((void*)lr->core.optionCategories[i].key);
230 SDL_free((void*)lr->core.optionCategories[i].desc);
231 SDL_free((void*)lr->core.optionCategories[i].info);
232 }
233 SDL_free(lr->core.optionCategories);
234 lr->core.optionCategories = NULL;
235 }
236 lr->core.optionCategoriesCount = 0;
237 lr->core.optionCategoriesCapacity = 0;
238}
239
248unsigned SDL_Libretro_GetOptionCount(const SDL_Libretro* lr) {
249 return lr ? lr->core.optionCount : 0;
250}
251
265const SDL_LibretroOption* SDL_Libretro_GetOption(const SDL_Libretro* lr, const char* key) {
266 if (!lr || !key) return NULL;
267 for (unsigned i = 0; i < lr->core.optionCount; i++) {
268 if (lr->core.options[i].key && SDL_strcmp(lr->core.options[i].key, key) == 0) {
269 return &lr->core.options[i];
270 }
271 }
272 return NULL;
273}
274
287const SDL_LibretroOption* SDL_Libretro_GetOptionByIndex(const SDL_Libretro* lr, unsigned index) {
288 if (!lr || index >= lr->core.optionCount) return NULL;
289 return &lr->core.options[index];
290}
291
306bool SDL_Libretro_SetOptionValue(SDL_Libretro* lr, const char* key, const char* value) {
307 if (!lr || !key || !value) {
308 SDL_SetError("[SDL_Libretro] Invalid SetOptionValue arguments");
309 return false;
310 }
312 if (!opt) {
313 return SDL_SetError("[SDL_Libretro] No such option '%s'", key);
314 }
315
316 // Reject values the core never offered, so GET_VARIABLE only ever hands the
317 // core a value it declared. Options with no declared values accept anything.
318 if (!SDL_Libretro_IsOptionValueValid(opt, value)) {
319 return SDL_SetError("[SDL_Libretro] '%s' is not a valid value for option '%s'", value, key);
320 }
321
322 // Allocate before freeing so a failed allocation never leaves opt->value
323 // NULL (which GET_VARIABLE would then hand to the core).
324 char* dup = SDL_strdup(value);
325 if (!dup) {
326 return SDL_SetError("[SDL_Libretro] Out of memory setting option '%s'", key);
327 }
328 SDL_free((void*)opt->value);
329 opt->value = dup;
330 lr->core.optionsDirtyCore = true;
331 lr->core.optionsDirtyApp = true;
332 return true;
333}
334
347const char* SDL_Libretro_GetOptionValue(SDL_Libretro* lr, const char* key) {
348 const SDL_LibretroOption* opt = SDL_Libretro_GetOption(lr, key);
349 if (!opt) return NULL;
350 return opt->value;
351}
352
365const char* SDL_Libretro_GetOptionValueLabel(SDL_Libretro* lr, const char* key) {
366 const SDL_LibretroOption* opt = SDL_Libretro_GetOption(lr, key);
367 if (!opt || !opt->value) return NULL;
368 for (unsigned v = 0; v < opt->valuesCount; v++) {
369 if (opt->values[v].value && SDL_strcmp(opt->values[v].value, opt->value) == 0) {
370 return opt->values[v].label ? opt->values[v].label : opt->values[v].value;
371 }
372 }
373 return opt->value;
374}
375
388bool SDL_Libretro_CycleOptionValue(SDL_Libretro* lr, const char* key, int direction) {
389 if (direction == 0) {
390 return SDL_InvalidParamError("direction");
391 }
393 if (!opt) {
394 return SDL_SetError("[SDL_Libretro] No such option '%s'", key);
395 }
396 if (opt->valuesCount == 0) {
397 return SDL_SetError("[SDL_Libretro] Option '%s' has no declared values to cycle", key);
398 }
399
400 // Find the current value's index; default to 0 if it isn't in the list.
401 unsigned current = 0;
402 for (unsigned v = 0; v < opt->valuesCount; v++) {
403 if (opt->values[v].value && opt->value && SDL_strcmp(opt->values[v].value, opt->value) == 0) {
404 current = v;
405 break;
406 }
407 }
408
409 unsigned count = opt->valuesCount;
410 unsigned step = (direction > 0) ? 1 : count - 1;
411 unsigned next = (current + step) % count;
412 return SDL_Libretro_SetOptionValue(lr, key, opt->values[next].value);
413}
414
424bool SDL_Libretro_ResetOption(SDL_Libretro* lr, const char* key) {
426 if (!opt) {
427 return SDL_InvalidParamError("opt");
428 }
429 char* dupicateValue = SDL_Libretro_Strdup(opt->defaultValue);
430 if (!dupicateValue) {
431 return SDL_SetError("[SDL_Libretro] Out of memory resetting option '%s'", key);
432 }
433 SDL_free((void*)opt->value);
434 opt->value = dupicateValue;
435 lr->core.optionsDirtyCore = true;
436 lr->core.optionsDirtyApp = true;
437 return true;
438}
439
447void SDL_Libretro_ResetAllOptions(SDL_Libretro* lr) {
448 if (!lr) return;
449 for (unsigned i = 0; i < lr->core.optionCount; i++) {
450 SDL_LibretroOption* opt = &lr->core.options[i];
451 char* dupicateValue = SDL_Libretro_Strdup(opt->defaultValue);
452 if (!dupicateValue) continue; // keep the previous value rather than nulling it
453 SDL_free((void*)opt->value);
454 opt->value = dupicateValue;
455 }
456 lr->core.optionsDirtyCore = true;
457 lr->core.optionsDirtyApp = true;
458}
459
469bool SDL_Libretro_AreOptionsDirty(SDL_Libretro* lr) {
470 if (!lr) return false;
471 bool dirty = lr->core.optionsDirtyApp;
472 lr->core.optionsDirtyApp = false;
473 return dirty;
474}
475
490 if (!lr || !lr->core.optionsUpdateDisplayCallback) return false;
491 return lr->core.optionsUpdateDisplayCallback();
492}
493
502unsigned SDL_Libretro_GetCategoryCount(const SDL_Libretro* lr) {
503 return lr ? lr->core.optionCategoriesCount : 0;
504}
505
515const SDL_LibretroCategory* SDL_Libretro_GetCategory(const SDL_Libretro* lr, const char* key) {
516 if (!lr || !key) return NULL;
517 for (unsigned i = 0; i < lr->core.optionCategoriesCount; i++) {
518 if (lr->core.optionCategories[i].key && SDL_strcmp(lr->core.optionCategories[i].key, key) == 0) {
519 return &lr->core.optionCategories[i];
520 }
521 }
522 return NULL;
523}
524
534const SDL_LibretroCategory* SDL_Libretro_GetCategoryByIndex(const SDL_Libretro* lr, unsigned index) {
535 if (!lr || index >= lr->core.optionCategoriesCount) return NULL;
536 return &lr->core.optionCategories[index];
537}
538
539#endif /* SDL_LIBRETRO_OPTIONS_IMPL_ONCE */
bool SDL_Libretro_CycleOptionValue(SDL_Libretro *lr, const char *key, int direction)
Advance a core option to the next (+1) or previous (-1) value.
bool SDL_Libretro_UpdateOptionVisibility(SDL_Libretro *lr)
Ask the core to refresh which of its options should be displayed.
bool SDL_Libretro_AreOptionsDirty(SDL_Libretro *lr)
Retrieves whether the options have been changed since the last time they were checked.
const SDL_LibretroCategory * SDL_Libretro_GetCategoryByIndex(const SDL_Libretro *lr, unsigned index)
Retrieve an option category by index.
const SDL_LibretroCategory * SDL_Libretro_GetCategory(const SDL_Libretro *lr, const char *key)
Retrieve an option category by key.
unsigned SDL_Libretro_GetCategoryCount(const SDL_Libretro *lr)
Get the number of option categories registered by the core.
const char * SDL_Libretro_GetOptionValueLabel(SDL_Libretro *lr, const char *key)
Get the human-readable label for a core option's current value.
const SDL_LibretroOption * SDL_Libretro_GetOptionByIndex(const SDL_Libretro *lr, unsigned index)
Retrieve a core option by its registration index.
const char * SDL_Libretro_GetOptionValue(SDL_Libretro *lr, const char *key)
Get a core option's current value.
bool SDL_Libretro_ResetOption(SDL_Libretro *lr, const char *key)
Reset a core option to its default value.
bool SDL_Libretro_SetOptionValue(SDL_Libretro *lr, const char *key, const char *value)
Set a core option's value; fails if the value isn't one the core declared.
void SDL_Libretro_ResetAllOptions(SDL_Libretro *lr)
Reset every core option to its default value.
unsigned SDL_Libretro_GetOptionCount(const SDL_Libretro *lr)
Get the number of core options that have been set.
const SDL_LibretroOption * SDL_Libretro_GetOption(const SDL_Libretro *lr, const char *key)
Retrieve details about a given core option.
A group of related core options.
const char * key
Unique identifier referenced by SDL_LibretroOption::category.
const char * desc
Human-readable name.
const char * info
Description / help text; may be empty.
A core option and its current state.
SDL_LibretroOptionValue * values
The selectable values; dynamically allocated.
unsigned valuesCapacity
Allocated capacity of the values array.
const char * desc
Human-readable name.
const char * value
The current value.
const char * category
Key of the category it belongs to; empty if none.
unsigned valuesCount
The number of populated entries in values.
const char * key
Unique identifier the core queries.
const char * defaultValue
The default value.
const char * info
Description / help text; may be empty.
One selectable value for a core option.
const char * value
The value the core stores.
const char * label
Human-readable name; may be NULL, in which case display value itself.