mirror of https://github.com/mgba-emu/mgba.git
382 lines
9.9 KiB
C
382 lines
9.9 KiB
C
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
#include <mgba/feature/commandline.h>
|
|
|
|
#include <mgba/core/cheats.h>
|
|
#include <mgba/core/config.h>
|
|
#include <mgba/core/core.h>
|
|
#include <mgba/core/version.h>
|
|
#include <mgba-util/string.h>
|
|
#include <mgba-util/vfs.h>
|
|
|
|
#ifdef USE_GDB_STUB
|
|
#include <mgba/internal/debugger/gdb-stub.h>
|
|
#endif
|
|
#ifdef USE_EDITLINE
|
|
#include <mgba/internal/debugger/cli-el-backend.h>
|
|
#endif
|
|
|
|
#include <fcntl.h>
|
|
#ifdef _MSC_VER
|
|
#include <mgba-util/platform/windows/getopt.h>
|
|
#else
|
|
#include <getopt.h>
|
|
#endif
|
|
|
|
#define GRAPHICS_OPTIONS "12345678f"
|
|
#define GRAPHICS_USAGE \
|
|
"Graphics options:\n" \
|
|
" -1, -2, -3, -4, -5, -6, -7, -8 Scale viewport by 1-8 times\n" \
|
|
" -f, --fullscreen Start full-screen\n" \
|
|
" --scale X Scale viewport by X times"
|
|
|
|
static const struct option _options[] = {
|
|
{ "bios", required_argument, 0, 'b' },
|
|
{ "cheats", required_argument, 0, 'c' },
|
|
{ "frameskip", required_argument, 0, 's' },
|
|
#ifdef USE_EDITLINE
|
|
{ "debug", no_argument, 0, 'd' },
|
|
#endif
|
|
#ifdef USE_GDB_STUB
|
|
{ "gdb", no_argument, 0, 'g' },
|
|
#endif
|
|
{ "help", no_argument, 0, 'h' },
|
|
{ "log-level", required_argument, 0, 'l' },
|
|
{ "savestate", required_argument, 0, 't' },
|
|
{ "patch", required_argument, 0, 'p' },
|
|
{ "version", no_argument, 0, '\0' },
|
|
{ 0, 0, 0, 0 }
|
|
};
|
|
|
|
static const struct mOption _graphicsLongOpts[] = {
|
|
{ "fullscreen", false, 'f' },
|
|
{ "scale", true, '\0' },
|
|
{ 0, 0, 0 }
|
|
};
|
|
|
|
static bool _parseGraphicsArg(struct mSubParser* parser, int option, const char* arg);
|
|
static bool _parseLongGraphicsArg(struct mSubParser* parser, const char* option, const char* arg);
|
|
static void _applyGraphicsArgs(struct mSubParser* parser, struct mCoreConfig* config);
|
|
|
|
static void _tableInsert(struct Table* table, const char* pair) {
|
|
char* eq = strchr(pair, '=');
|
|
if (eq) {
|
|
char option[128] = "";
|
|
strncpy(option, pair, eq - pair);
|
|
option[sizeof(option) - 1] = '\0';
|
|
HashTableInsert(table, option, strdup(&eq[1]));
|
|
} else {
|
|
HashTableInsert(table, pair, strdup("1"));
|
|
}
|
|
}
|
|
|
|
static void _tableApply(const char* key, void* value, void* user) {
|
|
struct mCoreConfig* config = user;
|
|
mCoreConfigSetOverrideValue(config, key, value);
|
|
}
|
|
|
|
bool mArgumentsParse(struct mArguments* args, int argc, char* const* argv, struct mSubParser* subparsers, int nSubparsers) {
|
|
int ch;
|
|
char options[128] =
|
|
"b:c:C:hl:p:s:t:"
|
|
#ifdef USE_EDITLINE
|
|
"d"
|
|
#endif
|
|
#ifdef USE_GDB_STUB
|
|
"g"
|
|
#endif
|
|
;
|
|
|
|
struct option longOptions[128] = {0};
|
|
memcpy(longOptions, _options, sizeof(_options));
|
|
|
|
memset(args, 0, sizeof(*args));
|
|
args->frameskip = -1;
|
|
args->logLevel = INT_MIN;
|
|
HashTableInit(&args->configOverrides, 0, free);
|
|
int lastLongOpt;
|
|
|
|
int i, j;
|
|
for (i = 0; _options[i].name; ++i); // Seek to end
|
|
lastLongOpt = i;
|
|
|
|
for (i = 0; i < nSubparsers; ++i) {
|
|
if (subparsers[i].extraOptions) {
|
|
strncat(options, subparsers[i].extraOptions, sizeof(options) - strlen(options) - 1);
|
|
}
|
|
if (subparsers[i].longOptions) {
|
|
for (j = 0; subparsers[i].longOptions[j].name; ++j) {
|
|
longOptions[lastLongOpt].name = subparsers[i].longOptions[j].name;
|
|
longOptions[lastLongOpt].has_arg = subparsers[i].longOptions[j].arg ? required_argument : no_argument;
|
|
longOptions[lastLongOpt].flag = NULL;
|
|
longOptions[lastLongOpt].val = subparsers[i].longOptions[j].shortEquiv;
|
|
++lastLongOpt;
|
|
}
|
|
}
|
|
}
|
|
bool ok = false;
|
|
int index = 0;
|
|
while ((ch = getopt_long(argc, argv, options, longOptions, &index)) != -1) {
|
|
const struct option* opt = &longOptions[index];
|
|
switch (ch) {
|
|
case '\0':
|
|
if (strcmp(opt->name, "version") == 0) {
|
|
args->showVersion = true;
|
|
} else {
|
|
for (i = 0; i < nSubparsers; ++i) {
|
|
if (subparsers[i].parseLong) {
|
|
ok = subparsers[i].parseLong(&subparsers[i], opt->name, optarg) || ok;
|
|
}
|
|
}
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
case 'b':
|
|
args->bios = strdup(optarg);
|
|
break;
|
|
case 'c':
|
|
args->cheatsFile = strdup(optarg);
|
|
break;
|
|
case 'C':
|
|
_tableInsert(&args->configOverrides, optarg);
|
|
break;
|
|
#ifdef USE_EDITLINE
|
|
case 'd':
|
|
args->debugAtStart = true;
|
|
args->debugCli = true;
|
|
break;
|
|
#endif
|
|
#ifdef USE_GDB_STUB
|
|
case 'g':
|
|
args->debugAtStart = true;
|
|
args->debugGdb = true;
|
|
break;
|
|
#endif
|
|
case 'h':
|
|
args->showHelp = true;
|
|
break;
|
|
case 'l':
|
|
args->logLevel = atoi(optarg);
|
|
break;
|
|
case 'p':
|
|
args->patch = strdup(optarg);
|
|
break;
|
|
case 's':
|
|
args->frameskip = atoi(optarg);
|
|
break;
|
|
case 't':
|
|
args->savestate = strdup(optarg);
|
|
break;
|
|
default:
|
|
for (i = 0; i < nSubparsers; ++i) {
|
|
if (subparsers[i].parse) {
|
|
ok = subparsers[i].parse(&subparsers[i], ch, optarg) || ok;
|
|
}
|
|
}
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
argc -= optind;
|
|
argv += optind;
|
|
if (argc > 1) {
|
|
return false;
|
|
} else if (argc == 1) {
|
|
args->fname = strdup(argv[0]);
|
|
} else {
|
|
args->fname = NULL;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void mArgumentsApply(const struct mArguments* args, struct mSubParser* subparsers, int nSubparsers, struct mCoreConfig* config) {
|
|
if (args->frameskip >= 0) {
|
|
mCoreConfigSetOverrideIntValue(config, "frameskip", args->frameskip);
|
|
}
|
|
if (args->logLevel > INT_MIN) {
|
|
mCoreConfigSetOverrideIntValue(config, "logLevel", args->logLevel);
|
|
}
|
|
if (args->bios) {
|
|
mCoreConfigSetOverrideValue(config, "bios", args->bios);
|
|
mCoreConfigSetOverrideIntValue(config, "useBios", true);
|
|
}
|
|
HashTableEnumerate(&args->configOverrides, _tableApply, config);
|
|
int i;
|
|
for (i = 0; i < nSubparsers; ++i) {
|
|
if (subparsers[i].apply) {
|
|
subparsers[i].apply(&subparsers[i], config);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool mArgumentsApplyDebugger(const struct mArguments* args, struct mCore* core, struct mDebugger* debugger) {
|
|
bool hasDebugger = false;
|
|
|
|
#ifdef USE_EDITLINE
|
|
if (args->debugCli) {
|
|
struct mDebuggerModule* module = mDebuggerCreateModule(DEBUGGER_CLI, core);
|
|
if (module) {
|
|
struct CLIDebugger* cliDebugger = (struct CLIDebugger*) module;
|
|
CLIDebuggerAttachBackend(cliDebugger, CLIDebuggerEditLineBackendCreate());
|
|
mDebuggerAttachModule(debugger, module);
|
|
hasDebugger = true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_GDB_STUB
|
|
if (args->debugGdb) {
|
|
struct mDebuggerModule* module = mDebuggerCreateModule(DEBUGGER_GDB, core);
|
|
if (module) {
|
|
mDebuggerAttachModule(debugger, module);
|
|
hasDebugger = true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return hasDebugger;
|
|
}
|
|
|
|
void mArgumentsApplyFileLoads(const struct mArguments* args, struct mCore* core) {
|
|
if (args->patch) {
|
|
struct VFile* patch = VFileOpen(args->patch, O_RDONLY);
|
|
if (patch) {
|
|
core->loadPatch(core, patch);
|
|
patch->close(patch);
|
|
}
|
|
} else {
|
|
mCoreAutoloadPatch(core);
|
|
}
|
|
|
|
struct mCheatDevice* device = NULL;
|
|
if (args->cheatsFile && (device = core->cheatDevice(core))) {
|
|
struct VFile* vf = VFileOpen(args->cheatsFile, O_RDONLY);
|
|
if (vf) {
|
|
mCheatDeviceClear(device);
|
|
mCheatParseFile(device, vf);
|
|
vf->close(vf);
|
|
}
|
|
} else {
|
|
mCoreAutoloadCheats(core);
|
|
}
|
|
}
|
|
|
|
void mArgumentsDeinit(struct mArguments* args) {
|
|
free(args->fname);
|
|
args->fname = 0;
|
|
|
|
free(args->patch);
|
|
args->patch = 0;
|
|
|
|
free(args->savestate);
|
|
args->savestate = 0;
|
|
|
|
free(args->cheatsFile);
|
|
args->cheatsFile = 0;
|
|
|
|
free(args->bios);
|
|
args->bios = 0;
|
|
|
|
HashTableDeinit(&args->configOverrides);
|
|
}
|
|
|
|
void mSubParserGraphicsInit(struct mSubParser* parser, struct mGraphicsOpts* opts) {
|
|
parser->usage = GRAPHICS_USAGE;
|
|
parser->opts = opts;
|
|
parser->parse = _parseGraphicsArg;
|
|
parser->parseLong = _parseLongGraphicsArg;
|
|
parser->apply = _applyGraphicsArgs;
|
|
parser->extraOptions = GRAPHICS_OPTIONS;
|
|
parser->longOptions = _graphicsLongOpts;
|
|
opts->multiplier = 0;
|
|
opts->fullscreen = false;
|
|
}
|
|
|
|
bool _parseGraphicsArg(struct mSubParser* parser, int option, const char* arg) {
|
|
UNUSED(arg);
|
|
struct mGraphicsOpts* graphicsOpts = parser->opts;
|
|
switch (option) {
|
|
case 'f':
|
|
graphicsOpts->fullscreen = true;
|
|
return true;
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
if (graphicsOpts->multiplier) {
|
|
return false;
|
|
}
|
|
graphicsOpts->multiplier = option - '0';
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool _parseLongGraphicsArg(struct mSubParser* parser, const char* option, const char* arg) {
|
|
struct mGraphicsOpts* graphicsOpts = parser->opts;
|
|
if (strcmp(option, "scale") == 0) {
|
|
if (graphicsOpts->multiplier) {
|
|
return false;
|
|
}
|
|
graphicsOpts->multiplier = atoi(arg);
|
|
return graphicsOpts->multiplier != 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void _applyGraphicsArgs(struct mSubParser* parser, struct mCoreConfig* config) {
|
|
struct mGraphicsOpts* graphicsOpts = parser->opts;
|
|
if (graphicsOpts->fullscreen) {
|
|
mCoreConfigSetOverrideIntValue(config, "fullscreen", graphicsOpts->fullscreen);
|
|
}
|
|
}
|
|
|
|
void usage(const char* arg0, const char* prologue, const char* epilogue, const struct mSubParser* subparsers, int nSubparsers) {
|
|
printf("usage: %s [option ...] file\n", arg0);
|
|
if (prologue) {
|
|
puts(prologue);
|
|
}
|
|
puts("\nGeneric options:\n"
|
|
" -b, --bios FILE GBA BIOS file to use\n"
|
|
" -c, --cheats FILE Apply cheat codes from a file\n"
|
|
" -C, --config OPTION=VALUE Override config value\n"
|
|
#ifdef USE_EDITLINE
|
|
" -d, --debug Use command-line debugger\n"
|
|
#endif
|
|
#ifdef USE_GDB_STUB
|
|
" -g, --gdb Start GDB session (default port 2345)\n"
|
|
#endif
|
|
" -l, --log-level N Log level mask\n"
|
|
" -t, --savestate FILE Load savestate when starting\n"
|
|
" -p, --patch FILE Apply a specified patch file when running\n"
|
|
" -s, --frameskip N Skip every N frames\n"
|
|
" --version Print version and exit"
|
|
);
|
|
int i;
|
|
for (i = 0; i < nSubparsers; ++i) {
|
|
if (subparsers[i].usage) {
|
|
puts("");
|
|
puts(subparsers[i].usage);
|
|
}
|
|
}
|
|
if (epilogue) {
|
|
puts(epilogue);
|
|
}
|
|
}
|
|
|
|
void version(const char* arg0) {
|
|
printf("%s %s (%s)\n", arg0, projectVersion, gitCommit);
|
|
}
|