/* Copyright 2025 flyinghead This file is part of Flycast. Flycast is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. Flycast is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Flycast. If not, see . */ #include "cfg/cfg.h" #include "stdclass.h" #include #include namespace config { static void usage(const char *exe) { fprintf(stderr, "Usage: %s [option]... []\n", exe); fprintf(stderr, "Options:\n"); fprintf(stderr, "-config section:key=value,... set a transient config value.\n"); fprintf(stderr, " Transient config values won't be saved to emu.cfg.\n"); fprintf(stderr, "-help display this help\n"); } static void parseConfigOption(const std::string& str) { char inQuote = '\0'; std::string section, key, value; int step = 0; // section, key, value for (char c : str) { if (inQuote != '\0' && c == inQuote) { inQuote = false; step = 0; setTransient(section, key, value); DEBUG_LOG(COMMON, "-config [%s] %s = %s", section.c_str(), key.c_str(), value.c_str()); section.clear(); key.clear(); value.clear(); continue; } switch (c) { case ':': switch (step) { case 0: section = trim_trailing_ws(section); if (section.empty()) { WARN_LOG(COMMON, "Invalid -config option '%s'. Format is: -config section:key=value,...", str.c_str()); return; } step = 1; break; case 1: key += c; break; case 2: value += c; break; } break; case '=': switch (step) { case 0: WARN_LOG(COMMON, "Invalid -config option '%s'. Format is: -config section:key=value,...", str.c_str()); return; case 1: key = trim_trailing_ws(key); if (key.empty()) { WARN_LOG(COMMON, "Invalid -config option '%s'. Format is: -config section:key=value,...", str.c_str()); return; } step = 2; break; case 2: value += c; break; } break; case '\'': case '"': switch (step) { case 0: section += c; break; case 1: key += c; break; case 2: if (inQuote == '\0') { inQuote = c; value.clear(); } else value += c; break; } break; case ',': switch (step) { case 0: // ignore consecutive commas break; case 1: key += c; break; case 2: if (inQuote != '\0') { value += c; } else { step = 0; setTransient(section, key, value); DEBUG_LOG(COMMON, "-config [%s] %s = %s", section.c_str(), key.c_str(), value.c_str()); section.clear(); key.clear(); value.clear(); } break; } break; case ' ': switch (step) { case 0: // Ignore leading spaces. Inner spaces are kept (game ids such as "INITIAL D Ver.2") // and trailing ones are trimmed at the ':' and '=' separators if (!section.empty()) section += c; break; case 1: if (!key.empty()) key += c; break; case 2: value += c; break; } break; default: switch (step) { case 0: section += c; break; case 1: key += c; break; case 2: value += c; break; } } } if (step == 2) { setTransient(section, key, value); DEBUG_LOG(COMMON, "-config [%s] %s = %s", section.c_str(), key.c_str(), value.c_str()); } } void parseCommandLine(int argc, const char * const argv[]) { settings.content.path.clear(); const char *exe = argv[0]; for (int i = 1; i < argc; i++) { if (!strcmp(argv[i], "-help") || !strcmp(argv[i], "--help")) { usage(exe); exit(0); } if (!strcmp(argv[i], "-config") || !strcmp(argv[i], "--config")) { if (i < argc - 1) parseConfigOption(argv[++i]); continue; } // macOS if (!strncmp(argv[i], "-NSDocumentRevisions", 20)) { i++; continue; } if (argv[i][0] == '-') { WARN_LOG(COMMON, "Ignoring unknown command line option '%s'", argv[i]); continue; } std::string extension = get_file_extension(argv[i]); if (extension == "cdi" || extension == "chd" || extension == "gdi"|| extension == "cue") { INFO_LOG(COMMON, "Using '%s' as CD image", argv[i]); settings.content.path = argv[i]; } else if (extension == "elf") { INFO_LOG(COMMON, "Using '%s' as reios elf file", argv[i]); setTransient("config", "bios.UseReios", "yes"); settings.content.path = argv[i]; } else { INFO_LOG(COMMON, "Using '%s' as rom", argv[i]); settings.content.path = argv[i]; } if (i < argc - 1) WARN_LOG(COMMON, "Rest of command line ignored: '%s'...", argv[i + 1]); break; } } } // namespace config