diff --git a/CMakeLists.txt b/CMakeLists.txt index decdc6dd..fc82a0a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,10 @@ set(CMAKE_C_STANDARD 23) set(CMAKE_C_STANDARD_REQUIRED ON) add_compile_options(-Wall -Wextra) +if (MSVC) + add_compile_definitions(_USE_MATH_DEFINES) +endif() + set(PLATFORM "" CACHE STRING "Platform backend") # Build metadata @@ -43,7 +47,13 @@ if(PLATFORM STREQUAL "glfw") # miniaudio target_include_directories(butterscotch PUBLIC vendor/miniaudio) - if(MINGW) + if (MSVC) + find_package(glfw3 REQUIRED) + set(GLFW3_LIBRARIES glfw3) + + target_include_directories(butterscotch PRIVATE ${GLFW3_INCLUDE_DIRS}) + target_link_libraries(butterscotch ${GLFW3_LIBRARIES} glad opengl32 gdi32 winmm) + elseif(MINGW) find_package(glfw3 REQUIRED) set(GLFW3_LIBRARIES glfw3) diff --git a/src/glfw/main.c b/src/glfw/main.c index dc19d07d..7654cb7d 100644 --- a/src/glfw/main.c +++ b/src/glfw/main.c @@ -1,9 +1,9 @@ #include "data_win.h" #include "vm.h" +#include "options.h" #include #include -#include #include #include #include @@ -65,54 +65,57 @@ typedef struct { const char* playbackInputsPath; } CommandLineArgs; -static void parseCommandLineArgs(CommandLineArgs* args, int argc, char* argv[]) { +static void parseCommandLineArgs(CommandLineArgs* args, int argc, const char* argv[]) { memset(args, 0, sizeof(CommandLineArgs)); - static struct option longOptions[] = { - {"screenshot", required_argument, nullptr, 's'}, - {"screenshot-at-frame", required_argument, nullptr, 'f'}, - {"headless", no_argument, nullptr, 'h'}, - {"print-rooms", no_argument, nullptr, 'r'}, - {"print-declared-functions", no_argument, nullptr, 'p'}, - {"trace-variable-reads", required_argument, nullptr, 'R'}, - {"trace-variable-writes", required_argument, nullptr, 'W'}, - {"trace-function-calls", required_argument, nullptr, 'c'}, - {"trace-alarms", required_argument, nullptr, 'a'}, - {"trace-instance-lifecycles", required_argument, nullptr, 'l'}, - {"trace-events", required_argument, nullptr, 'e'}, - {"trace-event-inherited", no_argument, nullptr, 'E'}, - {"trace-tiles", required_argument, nullptr, 'T'}, - {"trace-opcodes", required_argument, nullptr, 'o'}, - {"trace-stack", required_argument, nullptr, 'S'}, - {"trace-frames", no_argument, nullptr, 'k'}, - {"exit-at-frame", required_argument, nullptr, 'x'}, - {"dump-frame", required_argument, nullptr, 'd'}, - {"dump-frame-json", required_argument, nullptr, 'j'}, - {"dump-frame-json-file", required_argument, nullptr, 'J'}, - {"speed", required_argument, nullptr, 'M'}, - {"seed", required_argument, nullptr, 'Z'}, - {"debug", no_argument, nullptr, 'D'}, - {"disassemble", required_argument, nullptr, 'A'}, - {"record-inputs", required_argument, nullptr, 'I'}, - {"playback-inputs", required_argument, nullptr, 'P'}, - {nullptr, 0, nullptr, 0 } + static struct Option options[] = { + {'s', OPTION_TYPE_VALUE, "screenshot"}, + {'f', OPTION_TYPE_VALUE, "screenshot-at-frame"}, + {'h', OPTION_TYPE_FLAG, "headless"}, + {'r', OPTION_TYPE_FLAG, "print-rooms"}, + {'p', OPTION_TYPE_FLAG, "print-declared-functions"}, + {'R', OPTION_TYPE_VALUE, "trace-variable-reads"}, + {'W', OPTION_TYPE_VALUE, "trace-variable-writes"}, + {'c', OPTION_TYPE_VALUE, "trace-function-calls"}, + {'a', OPTION_TYPE_VALUE, "trace-alarms"}, + {'l', OPTION_TYPE_VALUE, "trace-instance-lifecycles"}, + {'e', OPTION_TYPE_VALUE, "trace-events"}, + {'E', OPTION_TYPE_FLAG, "trace-event-inherited"}, + {'T', OPTION_TYPE_VALUE, "trace-tiles"}, + {'o', OPTION_TYPE_VALUE, "trace-opcodes"}, + {'S', OPTION_TYPE_VALUE, "trace-stack"}, + {'k', OPTION_TYPE_FLAG, "trace-frames"}, + {'x', OPTION_TYPE_VALUE, "exit-at-frame"}, + {'d', OPTION_TYPE_VALUE, "dump-frame"}, + {'j', OPTION_TYPE_VALUE, "dump-frame-json"}, + {'J', OPTION_TYPE_VALUE, "dump-frame-json-file"}, + {'M', OPTION_TYPE_VALUE, "speed"}, + {'Z', OPTION_TYPE_VALUE, "seed"}, + {'D', OPTION_TYPE_FLAG, "debug"}, + {'A', OPTION_TYPE_VALUE, "disassemble"}, + {'I', OPTION_TYPE_VALUE, "record-inputs"}, + {'P', OPTION_TYPE_VALUE, "playback-inputs"}, + OPTION_SENTINEL }; args->screenshotFrames = nullptr; args->exitAtFrame = -1; args->speedMultiplier = 1.0; - int opt; - while ((opt = getopt_long(argc, argv, "", longOptions, nullptr)) != -1) { - switch (opt) { + Options optionsParser; + Options_init(&optionsParser, options, argv); + + OptionsStatus opt; + while (!(opt = Options_getValue(&optionsParser)).done) { + switch (opt.shortName) { case 's': - args->screenshotPattern = optarg; + args->screenshotPattern = opt.value; break; case 'f': { char* endPtr; - long frame = strtol(optarg, &endPtr, 10); + long frame = strtol(opt.value, &endPtr, 10); if (*endPtr != '\0' || 0 > frame) { - fprintf(stderr, "Error: Invalid frame number '%s'\n", optarg); + fprintf(stderr, "Error: Invalid frame number '%s'\n", opt.value); exit(1); } @@ -129,37 +132,37 @@ static void parseCommandLineArgs(CommandLineArgs* args, int argc, char* argv[]) args->printDeclaredFunctions = true; break; case 'R': - shput(args->varReadsToBeTraced, optarg, true); + shput(args->varReadsToBeTraced, opt.value, true); break; case 'W': - shput(args->varWritesToBeTraced, optarg, true); + shput(args->varWritesToBeTraced, opt.value, true); break; case 'c': - shput(args->functionCallsToBeTraced, optarg, true); + shput(args->functionCallsToBeTraced, opt.value, true); break; case 'a': - shput(args->alarmsToBeTraced, optarg, true); + shput(args->alarmsToBeTraced, opt.value, true); break; case 'l': - shput(args->instanceLifecyclesToBeTraced, optarg, true); + shput(args->instanceLifecyclesToBeTraced, opt.value, true); break; case 'e': - shput(args->eventsToBeTraced, optarg, true); + shput(args->eventsToBeTraced, opt.value, true); break; case 'o': - shput(args->opcodesToBeTraced, optarg, true); + shput(args->opcodesToBeTraced, opt.value, true); break; case 'S': - shput(args->stackToBeTraced, optarg, true); + shput(args->stackToBeTraced, opt.value, true); break; case 'k': args->traceFrames = true; break; case 'x': { char* endPtr; - long frame = strtol(optarg, &endPtr, 10); + long frame = strtol(opt.value, &endPtr, 10); if (*endPtr != '\0' || 0 > frame) { - fprintf(stderr, "Error: Invalid frame number '%s' for --exit-at-frame\n", optarg); + fprintf(stderr, "Error: Invalid frame number '%s' for --exit-at-frame\n", opt.value); exit(1); } args->exitAtFrame = (int) frame; @@ -167,9 +170,9 @@ static void parseCommandLineArgs(CommandLineArgs* args, int argc, char* argv[]) } case 'd': { char* endPtr; - long frame = strtol(optarg, &endPtr, 10); + long frame = strtol(opt.value, &endPtr, 10); if (*endPtr != '\0' || 0 > frame) { - fprintf(stderr, "Error: Invalid frame number '%s' for --dump-frame\n", optarg); + fprintf(stderr, "Error: Invalid frame number '%s' for --dump-frame\n", opt.value); exit(1); } hmput(args->dumpFrames, (int) frame, true); @@ -177,22 +180,22 @@ static void parseCommandLineArgs(CommandLineArgs* args, int argc, char* argv[]) } case 'j': { char* endPtr; - long frame = strtol(optarg, &endPtr, 10); + long frame = strtol(opt.value, &endPtr, 10); if (*endPtr != '\0' || 0 > frame) { - fprintf(stderr, "Error: Invalid frame number '%s' for --dump-frame-json\n", optarg); + fprintf(stderr, "Error: Invalid frame number '%s' for --dump-frame-json\n", opt.value); exit(1); } hmput(args->dumpJsonFrames, (int) frame, true); break; } case 'J': - args->dumpJsonFilePattern = optarg; + args->dumpJsonFilePattern = opt.value; break; case 'M': { char* endPtr; - double speed = strtod(optarg, &endPtr); + double speed = strtod(opt.value, &endPtr); if (*endPtr != '\0' || speed <= 0.0) { - fprintf(stderr, "Error: Invalid speed multiplier '%s' for --speed (must be > 0)\n", optarg); + fprintf(stderr, "Error: Invalid speed multiplier '%s' for --speed (must be > 0)\n", opt.value); exit(1); } args->speedMultiplier = speed; @@ -202,19 +205,19 @@ static void parseCommandLineArgs(CommandLineArgs* args, int argc, char* argv[]) args->debug = true; break; case 'A': - shput(args->disassemble, optarg, true); + shput(args->disassemble, opt.value, true); break; case 'T': - shput(args->tilesToBeTraced, optarg, true); + shput(args->tilesToBeTraced, opt.value, true); break; case 'E': args->traceEventInherited = true; break; case 'Z': { char* endPtr; - long seedVal = strtol(optarg, &endPtr, 10); + long seedVal = strtol(opt.value, &endPtr, 10); if (*endPtr != '\0') { - fprintf(stderr, "Error: Invalid seed value '%s' for --seed\n", optarg); + fprintf(stderr, "Error: Invalid seed value '%s' for --seed\n", opt.value); exit(1); } args->seed = (int) seedVal; @@ -222,10 +225,10 @@ static void parseCommandLineArgs(CommandLineArgs* args, int argc, char* argv[]) break; } case 'I': - args->recordInputsPath = optarg; + args->recordInputsPath = opt.value; break; case 'P': - args->playbackInputsPath = optarg; + args->playbackInputsPath = opt.value; break; default: fprintf(stderr, "Usage: %s [--headless] [--screenshot=PATTERN] [--screenshot-at-frame=N ...] \n", argv[0]); @@ -233,12 +236,12 @@ static void parseCommandLineArgs(CommandLineArgs* args, int argc, char* argv[]) } } - if (optind >= argc) { + if (optionsParser.index >= argc || opt.error) { fprintf(stderr, "Usage: %s [--headless] [--screenshot=PATTERN] [--screenshot-at-frame=N ...] \n", argv[0]); exit(1); } - args->dataWinPath = argv[optind]; + args->dataWinPath = argv[optionsParser.index]; if (hmlen(args->screenshotFrames) > 0 && args->screenshotPattern == nullptr) { fprintf(stderr, "Error: --screenshot-at-frame requires --screenshot to be set\n"); diff --git a/src/options.c b/src/options.c new file mode 100644 index 00000000..c534c342 --- /dev/null +++ b/src/options.c @@ -0,0 +1,84 @@ +#include "options.h" + +#include +#include + +#define OPTION_STATUS_DONE ((OptionsStatus){ true, false, 0, nullptr }) +#define OPTION_STATUS_ERROR ((OptionsStatus){ true, true, 0, nullptr }) +#define OPTION_STATUS_FLAG(k) ((OptionsStatus){ false, false, k, nullptr }) +#define OPTION_STATUS_VALUE(k, v) ((OptionsStatus){ false, false, k, v }) + +void Options_init(Options *options, const struct Option *optionsList, const char **argv) { + options->list = optionsList; + options->argv = argv; + options->index = 1; +} + +// No flag bundle atm +OptionsStatus Options_getValue(Options* options) { + const char *query = options->argv[options->index]; + const Option *option = nullptr; + const char *value = nullptr; + + if (query == nullptr) { + return OPTION_STATUS_DONE; + } + + size_t queryLen = strlen(query); + value = strchr(query, '='); + if (value != nullptr) { + if (value[1] == '\0') { + value = nullptr; + } else { + queryLen = value - query; + value++; + } + } + + // Eh... using shortName as sentinel? + for (int i = 0; options->list[i].shortName != 0; i++) { + if (query[0] == '-' && query[1] == '-' && query[2]) { + if (strncmp(options->list[i].longName, query + 2, queryLen - 2) == 0) { + option = &options->list[i]; + break; + } + } else if (query[0] == '-' && query[1]) { + if (query[2] != '\0' && query[2] != '=') { + fprintf(stderr, "Error: invalid option '%s'\n", query); + return OPTION_STATUS_ERROR; + } else if (options->list[i].shortName == query[1]) { + option = &options->list[i]; + break; + } + } + } + + if (option == nullptr) { + if (query[0] == '-') { + fprintf(stderr, "Error: unknown option '%.*s'\n", (int)queryLen, query); + return OPTION_STATUS_ERROR; + } + return OPTION_STATUS_DONE; + } + options->index++; + + switch (option->type) { + case OPTION_TYPE_VALUE: + if (value == nullptr) { + value = options->argv[options->index++]; + } + + if (value == nullptr) { + fprintf(stderr, "Error: missing argument for option '%.*s'\n", (int)queryLen, query); + return OPTION_STATUS_ERROR; + } + return OPTION_STATUS_VALUE(option->shortName, value); + case OPTION_TYPE_FLAG: + if (value != nullptr) { + fprintf(stderr, "Error: flag option '%.*s' does not take an argument\n", (int)queryLen, query); + return OPTION_STATUS_ERROR; + } + + return OPTION_STATUS_FLAG(option->shortName); + } +} diff --git a/src/options.h b/src/options.h new file mode 100644 index 00000000..24d5d3ef --- /dev/null +++ b/src/options.h @@ -0,0 +1,30 @@ +#pragma once + +typedef enum OptionType { + OPTION_TYPE_FLAG, + OPTION_TYPE_VALUE, +} OptionType; + +typedef struct Option { + char shortName; + OptionType type; + const char* longName; +} Option; + +typedef struct Options { + const struct Option* list; + const char **argv; + int index; +} Options; + +typedef struct OptionsStatus { + bool done; + bool error; + char shortName; + const char *value; +} OptionsStatus; + +#define OPTION_SENTINEL ((Option){ 0, 0, nullptr }) + +void Options_init(Options *options, const struct Option *optionsList, const char *argv[]); +OptionsStatus Options_getValue(Options* options);