ref: 12bb1f82bc08e34febad33844dfdd1f30dfbf5e9
dir: /config.c/
#include "config.h"
#include "types.h"
#include <stdio.h>
#include <string.h>
#include <SDL.h>
#include "features.h"
enum {
kKeyMod_ScanCode = 0x200,
kKeyMod_Alt = 0x400,
kKeyMod_Shift = 0x800,
kKeyMod_Ctrl = 0x1000,
};
Config g_config;
#define REMAP_SDL_KEYCODE(key) ((key) & SDLK_SCANCODE_MASK ? kKeyMod_ScanCode : 0) | (key) & (kKeyMod_ScanCode - 1)
#define _(x) REMAP_SDL_KEYCODE(x)
#define S(x) REMAP_SDL_KEYCODE(x) | kKeyMod_Shift
#define A(x) REMAP_SDL_KEYCODE(x) | kKeyMod_Alt
#define C(x) REMAP_SDL_KEYCODE(x) | kKeyMod_Ctrl
#define N 0
static const uint16 kDefaultKbdControls[kKeys_Total] = {
// Controls
_(SDLK_UP), _(SDLK_DOWN), _(SDLK_LEFT), _(SDLK_RIGHT), _(SDLK_RSHIFT), _(SDLK_RETURN), _(SDLK_x), _(SDLK_z), _(SDLK_s), _(SDLK_a), _(SDLK_c), _(SDLK_v),
// LoadState
_(SDLK_F1), _(SDLK_F2), _(SDLK_F3), _(SDLK_F4), _(SDLK_F5), _(SDLK_F6), _(SDLK_F7), _(SDLK_F8), _(SDLK_F9), _(SDLK_F10), N, N, N, N, N, N, N, N, N, N,
// SaveState
S(SDLK_F1), S(SDLK_F2), S(SDLK_F3), S(SDLK_F4), S(SDLK_F5), S(SDLK_F6), S(SDLK_F7), S(SDLK_F8), S(SDLK_F9), S(SDLK_F10), N, N, N, N, N, N, N, N, N, N,
// Replay State
C(SDLK_F1), C(SDLK_F2), C(SDLK_F3), C(SDLK_F4), C(SDLK_F5), C(SDLK_F6), C(SDLK_F7), C(SDLK_F8), C(SDLK_F9), C(SDLK_F10), N, N, N, N, N, N, N, N, N, N,
// Load Ref State
_(SDLK_1), _(SDLK_2), _(SDLK_3), _(SDLK_4), _(SDLK_5), _(SDLK_6), _(SDLK_7), _(SDLK_8), _(SDLK_9), _(SDLK_0), _(SDLK_MINUS), _(SDLK_EQUALS), _(SDLK_BACKSPACE), N, N, N, N, N, N, N,
// Replay Ref State
C(SDLK_1), C(SDLK_2), C(SDLK_3), C(SDLK_4), C(SDLK_5), C(SDLK_6), C(SDLK_7), C(SDLK_8), C(SDLK_9), C(SDLK_0), C(SDLK_MINUS), C(SDLK_EQUALS), C(SDLK_BACKSPACE), N, N, N, N, N, N, N,
// CheatLife, CheatKeys, CheatEquipment, CheatWalkThroughWalls
_(SDLK_w), _(SDLK_o), S(SDLK_w), C(SDLK_e),
// ClearKeyLog, StopReplay, Fullscreen, Reset, Pause, PauseDimmed, Turbo, ReplayTurbo, WindowBigger, WindowSmaller, DisplayPerf, ToggleRenderer
_(SDLK_k), _(SDLK_l), A(SDLK_RETURN), C(SDLK_r), S(SDLK_p), _(SDLK_p), _(SDLK_TAB), _(SDLK_t), N, N, _(SDLK_f), _(SDLK_r),
};
#undef _
#undef A
#undef C
#undef S
#undef N
typedef struct KeyNameId {
const char *name;
uint16 id, size;
} KeyNameId;
#define M(n) {#n, kKeys_##n, kKeys_##n##_Last - kKeys_##n + 1}
#define S(n) {#n, kKeys_##n, 1}
static const KeyNameId kKeyNameId[] = {
M(Controls), M(Load), M(Save), M(Replay), M(LoadRef), M(ReplayRef),
S(CheatLife), S(CheatKeys), S(CheatEquipment), S(CheatWalkThroughWalls),
S(ClearKeyLog), S(StopReplay), S(Fullscreen), S(Reset),
S(Pause), S(PauseDimmed), S(Turbo), S(ReplayTurbo), S(WindowBigger), S(WindowSmaller), S(VolumeUp), S(VolumeDown), S(DisplayPerf), S(ToggleRenderer),
};
#undef S
#undef M
typedef struct KeyMapHashEnt {
uint16 key, cmd, next;
} KeyMapHashEnt;
static uint16 keymap_hash_first[255];
static KeyMapHashEnt *keymap_hash;
static int keymap_hash_size;
static bool has_keynameid[countof(kKeyNameId)];
bool KeyMapHash_Add(uint16 key, uint16 cmd) {
if ((keymap_hash_size & 0xff) == 0) {
if (keymap_hash_size > 10000)
Die("Too many keys");
keymap_hash = realloc(keymap_hash, sizeof(KeyMapHashEnt) * (keymap_hash_size + 256));
}
int i = keymap_hash_size++;
KeyMapHashEnt *ent = &keymap_hash[i];
ent->key = key;
ent->cmd = cmd;
ent->next = 0;
int j = (uint32)key % 255;
uint16 *cur = &keymap_hash_first[j];
while (*cur) {
KeyMapHashEnt *ent = &keymap_hash[*cur - 1];
if (ent->key == key)
return false;
cur = &ent->next;
}
*cur = i + 1;
return true;
}
static int KeyMapHash_Find(uint16 key) {
int i = keymap_hash_first[key % 255];
while (i) {
KeyMapHashEnt *ent = &keymap_hash[i - 1];
if (ent->key == key)
return ent->cmd;
i = ent->next;
}
return -1;
}
int FindCmdForSdlKey(SDL_Keycode code, SDL_Keymod mod) {
if (code & ~(SDLK_SCANCODE_MASK | 0x1ff))
return -1;
int key = mod & KMOD_ALT ? kKeyMod_Alt : 0;
key |= mod & KMOD_CTRL ? kKeyMod_Ctrl : 0;
key |= mod & KMOD_SHIFT ? kKeyMod_Shift : 0;
key |= REMAP_SDL_KEYCODE(code);
return KeyMapHash_Find(key);
}
static char *NextDelim(char **s, int sep) {
char *r = *s;
if (r) {
while (r[0] == ' ' || r[0] == '\t')
r++;
char *t = strchr(r, sep);
*s = t ? (*t++ = 0, t) : NULL;
}
return r;
}
static inline int ToLower(int a) {
return a + (a >= 'A' && a <= 'Z') * 32;
}
static bool StringEqualsNoCase(const char *a, const char *b) {
for (;;) {
int aa = ToLower(*a++), bb = ToLower(*b++);
if (aa != bb)
return false;
if (aa == 0)
return true;
}
}
static bool StringStartsWithNoCase(const char *a, const char *b) {
for (;;) {
int aa = ToLower(*a++), bb = ToLower(*b++);
if (bb == 0)
return true;
if (aa != bb)
return false;
}
}
static void ParseKeyArray(char *value, int cmd, int size) {
char *s;
int i = 0;
for (; i < size && (s = NextDelim(&value, ',')) != NULL; i++, cmd++) {
if (*s == 0)
continue;
int key_with_mod = 0;
for (;;) {
if (StringStartsWithNoCase(s, "Shift+")) {
key_with_mod |= kKeyMod_Shift, s += 6;
} else if (StringStartsWithNoCase(s, "Ctrl+")) {
key_with_mod |= kKeyMod_Ctrl, s += 5;
} else if (StringStartsWithNoCase(s, "Alt+")) {
key_with_mod |= kKeyMod_Alt, s += 4;
} else {
break;
}
}
SDL_Keycode key = SDL_GetKeyFromName(s);
if (key == SDLK_UNKNOWN) {
fprintf(stderr, "Unknown key: '%s'\n", s);
continue;
}
if (!KeyMapHash_Add(key_with_mod | REMAP_SDL_KEYCODE(key), cmd))
fprintf(stderr, "Duplicate key: '%s'\n", s);
}
}
static void RegisterDefaultKeys() {
for (int i = 0; i < countof(kKeyNameId); i++) {
if (!has_keynameid[i]) {
int size = kKeyNameId[i].size, k = kKeyNameId[i].id;
for (int j = 0; j < size; j++, k++)
KeyMapHash_Add(kDefaultKbdControls[k], k);
}
}
}
static int GetIniSection(const char *s) {
if (StringEqualsNoCase(s, "[KeyMap]"))
return 0;
if (StringEqualsNoCase(s, "[Graphics]"))
return 1;
if (StringEqualsNoCase(s, "[Sound]"))
return 2;
if (StringEqualsNoCase(s, "[General]"))
return 3;
if (StringEqualsNoCase(s, "[Features]"))
return 4;
return -1;
}
static bool ParseBool(const char *value, bool *result) {
if (StringEqualsNoCase(value, "0") || StringEqualsNoCase(value, "false")) {
*result = false;
return true;
} else if (StringEqualsNoCase(value, "1") || StringEqualsNoCase(value, "true")) {
*result = true;
return true;
}
return false;
}
static bool ParseBoolBit(const char *value, uint32 *data, uint32 mask) {
bool tmp;
if (!ParseBool(value, &tmp))
return false;
*data = *data & ~mask | (tmp ? mask : 0);
return true;
}
static bool HandleIniConfig(int section, const char *key, char *value) {
if (section == 0) {
for (int i = 0; i < countof(kKeyNameId); i++) {
if (StringEqualsNoCase(key, kKeyNameId[i].name)) {
has_keynameid[i] = true;
ParseKeyArray(value, kKeyNameId[i].id, kKeyNameId[i].size);
return true;
}
}
} else if (section == 1) {
if (StringEqualsNoCase(key, "WindowSize")) {
char *s;
if (StringEqualsNoCase(value, "Auto")){
g_config.window_width = 0;
g_config.window_height = 0;
return true;
}
while ((s = NextDelim(&value, 'x')) != NULL) {
if(g_config.window_width == 0) {
g_config.window_width = atoi(s);
} else {
g_config.window_height = atoi(s);
return true;
}
}
} else if (StringEqualsNoCase(key, "EnhancedMode7")) {
return ParseBool(value, &g_config.enhanced_mode7);
} else if (StringEqualsNoCase(key, "NewRenderer")) {
return ParseBool(value, &g_config.new_renderer);
} else if (StringEqualsNoCase(key, "IgnoreAspectRatio")) {
return ParseBool(value, &g_config.ignore_aspect_ratio);
} else if (StringEqualsNoCase(key, "Fullscreen")) {
g_config.fullscreen = (uint8)strtol(value, (char**)NULL, 10);
return true;
} else if (StringEqualsNoCase(key, "WindowScale")) {
g_config.window_scale = (uint8)strtol(value, (char**)NULL, 10);
return true;
} else if (StringEqualsNoCase(key, "NoSpriteLimits")) {
return ParseBool(value, &g_config.no_sprite_limits);
} else if (StringEqualsNoCase(key, "LinkGraphics")) {
g_config.link_graphics = value;
return true;
}
} else if (section == 2) {
if (StringEqualsNoCase(key, "EnableAudio")) {
return ParseBool(value, &g_config.enable_audio);
} else if (StringEqualsNoCase(key, "AudioFreq")) {
g_config.audio_freq = (uint16)strtol(value, (char**)NULL, 10);
return true;
} else if (StringEqualsNoCase(key, "AudioChannels")) {
g_config.audio_channels = (uint8)strtol(value, (char**)NULL, 10);
return true;
} else if (StringEqualsNoCase(key, "AudioSamples")) {
g_config.audio_samples = (uint16)strtol(value, (char**)NULL, 10);
return true;
} else if (StringEqualsNoCase(key, "EnableMSU")) {
return ParseBool(value, &g_config.enable_msu);
}
} else if (section == 3) {
if (StringEqualsNoCase(key, "Autosave")) {
g_config.autosave = (bool)strtol(value, (char**)NULL, 10);
return true;
} else if (StringEqualsNoCase(key, "ExtendedAspectRatio")) {
const char* s;
int h = 224;
bool nospr = false, novis = false;
// todo: make it not depend on the order
while ((s = NextDelim(&value, ',')) != NULL) {
if (strcmp(s, "extend_y") == 0)
h = 240, g_config.extend_y = true;
else if (strcmp(s, "16:9") == 0)
g_config.extended_aspect_ratio = (h * 16 / 9 - 256) / 2;
else if (strcmp(s, "16:10") == 0)
g_config.extended_aspect_ratio = (h * 16 / 10 - 256) / 2;
else if (strcmp(s, "18:9") == 0)
g_config.extended_aspect_ratio = (h * 18 / 9 - 256) / 2;
else if (strcmp(s, "4:3") == 0)
g_config.extended_aspect_ratio = 0;
else if (strcmp(s, "unchanged_sprites") == 0)
nospr = true;
else if (strcmp(s, "no_visual_fixes") == 0)
novis = true;
else
return false;
}
if (g_config.extended_aspect_ratio && !nospr)
g_config.features0 |= kFeatures0_ExtendScreen64;
if (g_config.extended_aspect_ratio && !novis)
g_config.features0 |= kFeatures0_WidescreenVisualFixes;
return true;
} else if (StringEqualsNoCase(key, "DisplayPerfInTitle")) {
return ParseBool(value, &g_config.display_perf_title);
} else if (StringEqualsNoCase(key, "DisableFrameDelay")) {
return ParseBool(value, &g_config.disable_frame_delay);
}
} else if (section == 4) {
if (StringEqualsNoCase(key, "ItemSwitchLR")) {
return ParseBoolBit(value, &g_config.features0, kFeatures0_SwitchLR);
} else if (StringEqualsNoCase(key, "TurnWhileDashing")) {
return ParseBoolBit(value, &g_config.features0, kFeatures0_TurnWhileDashing);
} else if (StringEqualsNoCase(key, "MirrorToDarkworld")) {
return ParseBoolBit(value, &g_config.features0, kFeatures0_MirrorToDarkworld);
} else if (StringEqualsNoCase(key, "CollectItemsWithSword")) {
return ParseBoolBit(value, &g_config.features0, kFeatures0_CollectItemsWithSword);
} else if (StringEqualsNoCase(key, "BreakPotsWithSword")) {
return ParseBoolBit(value, &g_config.features0, kFeatures0_BreakPotsWithSword);
} else if (StringEqualsNoCase(key, "DisableLowHealthBeep")) {
return ParseBoolBit(value, &g_config.features0, kFeatures0_DisableLowHealthBeep);
} else if (StringEqualsNoCase(key, "SkipIntroOnKeypress")) {
return ParseBoolBit(value, &g_config.features0, kFeatures0_SkipIntroOnKeypress);
} else if (StringEqualsNoCase(key, "ShowMaxItemsInYellow")) {
return ParseBoolBit(value, &g_config.features0, kFeatures0_ShowMaxItemsInYellow);
} else if (StringEqualsNoCase(key, "MoreActiveBombs")) {
return ParseBoolBit(value, &g_config.features0, kFeatures0_MoreActiveBombs);
} else if (StringEqualsNoCase(key, "CarryMoreRupees")) {
return ParseBoolBit(value, &g_config.features0, kFeatures0_CarryMoreRupees);
} else if (StringEqualsNoCase(key, "MiscBugFixes")) {
return ParseBoolBit(value, &g_config.features0, kFeatures0_MiscBugFixes);
} else if (StringEqualsNoCase(key, "GameChangingBugFixes")) {
return ParseBoolBit(value, &g_config.features0, kFeatures0_GameChangingBugFixes);
} else if (StringEqualsNoCase(key, "CancelBirdTravel")) {
return ParseBoolBit(value, &g_config.features0, kFeatures0_CancelBirdTravel);
}
}
return false;
}
uint8 *ReadFile(const char *name, size_t *length) {
FILE *f = fopen(name, "rb");
if (f == NULL)
return NULL;
fseek(f, 0, SEEK_END);
size_t size = ftell(f);
rewind(f);
uint8 *buffer = (uint8 *)malloc(size + 1);
if (!buffer) Die("malloc failed");
// Always zero terminate so this function can be used also for strings.
buffer[size] = 0;
if (fread(buffer, 1, size, f) != size)
Die("fread failed");
fclose(f);
if (length) *length = size;
return buffer;
}
void ParseConfigFile() {
uint8 *file = ReadFile("zelda3.user.ini", NULL);
if (!file) {
file = ReadFile("zelda3.ini", NULL);
if (!file)
return;
}
fprintf(stderr, "Loading zelda3.ini\n");
int section = -2;
int lineno = 1;
char *p, *next_p = (char*)file;
for (; (p = next_p) != NULL; lineno++) {
// find end of line
char *eol = strchr(p, '\n');
next_p = eol ? eol + 1 : NULL;
eol = eol ? eol : p + strlen(p);
// strip comments
char *comment = memchr(p, '#', eol - p);
eol = (comment != 0) ? comment : eol;
// strip trailing whitespace
while (eol > p && (eol[-1] == '\r' || eol[-1] == ' ' || eol[-1] == '\t'))
eol--;
*eol = 0;
if (p == eol)
continue; // empty line
// strip leading whitespace
while (p[0] == ' ' || p[0] == '\t')
p++;
if (*p == '[') {
section = GetIniSection(p);
if (section < 0)
fprintf(stderr, "zelda3.ini:%d: Invalid .ini section %s\n", lineno, p);
} else if (section == -2) {
fprintf(stderr, "zelda3.ini:%d: Expecting [section]\n", lineno);
} else {
char *equals = memchr(p, '=', eol - p);
if (equals == NULL) {
fprintf(stderr, "zelda3.ini:%d: Expecting 'key=value'\n", lineno);
} else {
char *kr = equals;
while (kr > p && (kr[-1] == ' ' || kr[-1] == '\t'))
kr--;
*kr = 0;
char *v = equals + 1;
while (v[0] == ' ' || v[0] == '\t')
v++;
if (section >= 0 && !HandleIniConfig(section, p, v))
fprintf(stderr, "zelda3.ini:%d: Can't parse '%s'\n", lineno, p);
}
}
}
g_config.memory_buffer = file;
}
void AfterConfigParse() {
RegisterDefaultKeys();
}