Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
125 changes: 64 additions & 61 deletions src/glfw/main.c
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#include "data_win.h"
#include "vm.h"
#include "options.h"

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Expand Down Expand Up @@ -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);
}

Expand All @@ -129,70 +132,70 @@ 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;
break;
}
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);
break;
}
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;
Expand All @@ -202,43 +205,43 @@ 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;
args->hasSeed = true;
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 ...] <path to data.win or game.unx>\n", argv[0]);
exit(1);
}
}

if (optind >= argc) {
if (optionsParser.index >= argc || opt.error) {
fprintf(stderr, "Usage: %s [--headless] [--screenshot=PATTERN] [--screenshot-at-frame=N ...] <path to data.win or game.unx>\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");
Expand Down
84 changes: 84 additions & 0 deletions src/options.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#include "options.h"

#include <string.h>
#include <stdio.h>

#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);
}
}
30 changes: 30 additions & 0 deletions src/options.h
Original file line number Diff line number Diff line change
@@ -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);