Skip to content
Open
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
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
build/
build_ps2/
build_ps3/
build_*/
.gitignore
.cache
CMakeUserPresets.json
.devcontainer/
vcpkg_installed/
.vs/
14 changes: 11 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ project(butterscotch C)

set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
add_compile_options(-Wall -Wextra)
if(NOT MSVC)
add_compile_options(-Wall -Wextra)
endif()

set(PLATFORM "" CACHE STRING "Platform backend")
set(AUDIO_BACKEND "" CACHE STRING "Audio backend")
Expand Down Expand Up @@ -204,14 +206,20 @@ if(PLATFORM STREQUAL "glfw")
target_include_directories(butterscotch PUBLIC vendor/stb/vorbis)

if(MINGW)
find_package(glfw3 REQUIRED)
set(GLFW3_LIBRARIES glfw3)
find_package(glfw3 CONFIG REQUIRED)
set(GLFW3_LIBRARIES glfw)

# Target Windows 7+ (avoids referencing newer APIs like MapViewOfFileNuma2 that MinGW import libs don't provide)
# https://github.com/mirror/mingw-w64/blob/master/mingw-w64-headers/include/sdkddkver.h
target_compile_definitions(butterscotch PRIVATE _WIN32_WINNT=0x0601 NTDDI_VERSION=0x06010000 WIN32_LEAN_AND_MEAN)
target_link_libraries(butterscotch ${GLFW3_LIBRARIES} ${AUDIO_LIBRARIES} glad m opengl32 gdi32 winmm bz2)
target_link_options(butterscotch PRIVATE -static)
elseif(MSVC)
find_package(glfw3 CONFIG REQUIRED)
set(GLFW3_LIBRARIES glfw)
find_package(BZip2 REQUIRED)
find_package(unofficial-getopt-win32 CONFIG REQUIRED)
target_link_libraries(butterscotch ${GLFW3_LIBRARIES} ${AUDIO_LIBRARIES} glad opengl32 gdi32 winmm BZip2::BZip2 unofficial::getopt-win32::getopt)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Haiku")
find_package(PkgConfig REQUIRED)
pkg_check_modules(GLFW3 REQUIRED glfw3)
Expand Down
15 changes: 15 additions & 0 deletions CMakePresets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"version": 2,
"configurePresets": [
{
"name": "vcpkg",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"PLATFORM": "glfw",
"ENABLE_ASAN": false,
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
}
}
],
}
9 changes: 7 additions & 2 deletions src/audio/miniaudio/ma_audio_system.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
// which enables miniaudio's built-in OGG Vorbis decoding support.
#include "stb_vorbis.c"

#ifdef _MSC_VER // Workaround for miniaudio compilation error on MSVC
#define MA_DISABLE_WASAPI
#endif
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"

Expand Down Expand Up @@ -607,8 +610,10 @@ static void maSetChannelCount(MAYBE_UNUSED AudioSystem* audio, MAYBE_UNUSED int3
static void maGroupLoad(AudioSystem* audio, int32_t groupIndex) {
if (groupIndex > 0) {
int sz = snprintf(nullptr, 0, "audiogroup%d.dat", groupIndex);
char buf[sz + 1];
snprintf(buf, sizeof(buf), "audiogroup%d.dat", groupIndex);
size_t bufSize = (size_t)sz + 1;
char *buf = malloc(bufSize);
snprintf(buf, bufSize, "audiogroup%d.dat", groupIndex);
free(buf);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you free buf here right before it gets used the very next line, you need to free it later

DataWin *audioGroup = DataWin_parse(((MaAudioSystem*)audio)->fileSystem->vtable->resolvePath(((MaAudioSystem*)audio)->fileSystem, buf),
(DataWinParserOptions) {
.parseAudo = true,
Expand Down
18 changes: 18 additions & 0 deletions src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
#define nullptr NULL
#endif

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

#if (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) || defined(__BIG_ENDIAN__)
#define IS_BIG_ENDIAN
#endif
Expand All @@ -22,3 +26,17 @@
#define MAYBE_UNUSED
#endif
#endif

#if defined(__GNUC__) || defined(__clang__)
#define ALIGN(x) __attribute__((aligned(x)));
#else
#define ALIGN(x)
#endif

#if defined(__GNUC__) || defined(__clang__)
#define NOINLINE __attribute__((noinline))
#elif defined(_MSC_VER) && _MSC_VER >= 1400 // VS2005 or later
#define NOINLINE __declspec(noinline)
#else
#define NOINLINE
#endif
3 changes: 3 additions & 0 deletions src/overlay_file_system.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
#include <direct.h>
#define overlayMkdir(path) _mkdir(path)
#define overlayRmdir(path) _rmdir(path)
#ifndef S_ISDIR
#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
#endif
#else
#include <unistd.h>
#define overlayMkdir(path) mkdir((path), 0777)
Expand Down
2 changes: 2 additions & 0 deletions src/profiler.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#ifndef _MSC_VER
#include <sys/time.h>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move the sys/time.h header to right below time.h a little further down.

change it from

#else
#include <time.h>
#endif

to

#else
#include <time.h>
#ifndef CLOCK_MONOTONIC
#include <sys/time.h>
#endif
#endif

#endif

#include "utils.h"
#include "stb_ds.h"
Expand Down
2 changes: 1 addition & 1 deletion src/rvalue.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ struct RValue {
uint8_t gmlStackType; // GML data type from the instruction that pushed this value
#endif
uint8_t assetRefType; // For RVALUE_ASSETREF: Indicates the asset type (AssetRefType)
} __attribute__((aligned(8)));
} ALIGN(8);

// Helper to initialize .gmlStackType only on BC17+ builds
#if IS_BC17_OR_HIGHER_ENABLED
Expand Down
122 changes: 64 additions & 58 deletions src/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,30 @@
#include "common.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <stdint.h>
#include <math.h>

#include "real_type.h"

#if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 202311L)) \
|| defined(__GNUC__) || defined(__clang__) || defined(__TINYC__)
// The "typeof((typeof(x))0" is used to remove the "const" from the typeof
#define TYPEOF(x) typeof((typeof(x))0)
#else
#define TYPEOF(x) unsigned long long
#endif

#define forEach(type, item, array, count) \
Comment thread
SergioFLS marked this conversation as resolved.
for (typeof(count) item##_i_ = 0; item##_i_ < (count); item##_i_++) \
for (type* item = &(array)[item##_i_]; item; item = NULL)
for (TYPEOF(count) item##_i_ = 0; item##_i_ < (long long)(count); item##_i_++) \
Copy link
Copy Markdown
Contributor

@Un1q32 Un1q32 May 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why cast count to long long here?

for (type* item = &(array)[item##_i_]; item; item = 0)

#define forEachIndexed(type, item, index, array, count) \
for (typeof(count) index = 0; index < (count); index++) \
for (type* item = &(array)[index]; item; item = NULL)

// The "typeof((typeof(n))0" is used to remove the "const" from the typeof
for (TYPEOF(count) index = 0; index < (long long)(count); index++) \
for (type* item = &(array)[index]; item; item = 0)

#define repeat(n, it) for (typeof((typeof(n))0) it = 0; it < (n); it++)
#define repeat(n, it) for (TYPEOF(n) it = 0; it < (long long)(n); it++)

#define require(condition) \
do { \
Expand All @@ -45,52 +52,49 @@ abort(); \
} \
} while (0)

#define requireNotNull(ptr) ({ \
typeof(ptr) _val = (ptr); \
if (_val == NULL) { \
fprintf(stderr, "%s:%d: requireNotNull failed: '%s'\n", __FILE__, __LINE__, #ptr); \
abort(); \
} \
_val; \
})
static inline void* requireNotNullFunction(void* ptr, char* file, int line, char* name) {
if (ptr == nullptr) {
fprintf(stderr, "%s:%d: requireNotNull failed: '%s'\n", file, line, name);
abort();
}
return ptr;
}
#define requireNotNull(ptr) requireNotNullFunction((void*)ptr, __FILE__, __LINE__, #ptr)

#define requireNotNullMessage(ptr, msg) ({ \
typeof(ptr) _val = (ptr); \
if (_val == NULL) { \
fprintf(stderr, "%s:%d: requireNotNull failed: %s\n", __FILE__, __LINE__, (msg)); \
abort(); \
} \
_val; \
})
#define requireNotNullMessage(ptr, msg) requireNotNullFunction((void*)ptr, __FILE__, __LINE__, msg)

// Safe allocation macros - check for nullptr and abort with file/line info
#define safeMalloc(size) ({ \
void* _ptr = malloc(size); \
if (_ptr == nullptr) { \
fprintf(stderr, "FATAL: malloc(%zu) failed at %s:%d\n", (size_t)(size), __FILE__, __LINE__); \
abort(); \
} \
_ptr; \
})

#define safeCalloc(count, size) ({ \
void* _ptr = calloc(count, size); \
if (_ptr == nullptr) { \
fprintf(stderr, "FATAL: calloc(%zu, %zu) failed at %s:%d\n", (size_t)(count), (size_t)(size), __FILE__, __LINE__); \
abort(); \
} \
_ptr; \
})

#define safeRealloc(ptr, size) ({ \
void* _ptr = realloc(ptr, size); \
if (_ptr == nullptr) { \
fprintf(stderr, "FATAL: realloc(%zu) failed at %s:%d\n", (size_t)(size), __FILE__, __LINE__); \
abort(); \
} \
_ptr; \
})
static inline void* safeMallocFunction(size_t size, char* file, int line) {
void* _ptr = malloc(size);
if (_ptr == nullptr) {
fprintf(stderr, "FATAL: malloc(%zu) failed at %s:%d\n", size, file, line);
abort();
}
return _ptr;
}
#define safeMalloc(size) safeMallocFunction(size, __FILE__, __LINE__)

static inline void* safeCallocFunction(size_t count, size_t size, char* file, int line) {
void* _ptr = calloc(count, size);
if (_ptr == nullptr) {
fprintf(stderr, "FATAL: calloc(%zu, %zu) failed at %s:%d\n", count, size, file, line);
abort();
}
return _ptr;
}
#define safeCalloc(count, size) safeCallocFunction(count, size, __FILE__, __LINE__)

static inline void* safeReallocFunction(void* ptr, size_t size, char* file, int line) {
void* _ptr = realloc(ptr, size);
if (_ptr == nullptr) {
fprintf(stderr, "FATAL: realloc(%zu) failed at %s:%d\n", size, file, line);
abort();
}
return _ptr;
}
#define safeRealloc(ptr, size) safeReallocFunction(ptr, size, __FILE__, __LINE__)

#if defined(PLATFORM_PS2)
#define safeMemalign(alignment, size) ({ \
void* _ptr = memalign(alignment, size); \
if (_ptr == nullptr) { \
Expand All @@ -99,15 +103,17 @@ _val; \
} \
_ptr; \
})

#define safeStrdup(str) ({ \
char* _ptr = strdup(str); \
if (_ptr == nullptr) { \
fprintf(stderr, "FATAL: strdup() failed at %s:%d\n", __FILE__, __LINE__); \
abort(); \
} \
_ptr; \
})
#endif

static inline char* safeStrdupFunction(const char* str, char* file, int line) {
char* _ptr = strdup(str);
if (_ptr == nullptr) {
fprintf(stderr, "FATAL: strdup() failed at %s:%d\n", file, line);
abort();
}
return _ptr;
}
#define safeStrdup(str) safeStrdupFunction(str, __FILE__, __LINE__)

// Truncates to 6 decimal places, matching the HTML5 runner's ClampFloat
static inline GMLReal clampFloat(GMLReal f) {
Expand Down
6 changes: 3 additions & 3 deletions src/vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -848,7 +848,7 @@ static inline void writeIntoSlot(RValue* dest, RValue val) {
}

// Force out-of-line so the OP_POP fast path in executeLoop doesn't inline this, because we already have an "optimized" version for common writes
__attribute__((noinline))
NOINLINE
static void resolveVariableWrite(VMContext* ctx, int32_t instanceType, uint32_t varRef, RValue val) {
Variable* varDef = resolveVarDef(ctx, varRef);

Expand Down Expand Up @@ -1499,7 +1499,7 @@ static void handlePopz(VMContext* ctx) {
RValue_free(&val);
}

__attribute__((noinline))
NOINLINE
static void handleAddString(VMContext* ctx, RValue a, RValue b, uint8_t resultType) {
if (a.type == RVALUE_STRING && b.type == RVALUE_STRING) {
// String concatenation
Expand Down Expand Up @@ -1540,7 +1540,7 @@ static void handleAddString(VMContext* ctx, RValue a, RValue b, uint8_t resultTy
}
}

__attribute__((noinline))
NOINLINE
static void handleMulString(VMContext* ctx, RValue a, RValue b, uint8_t resultType) {
// a.type == RVALUE_STRING; b is the repetition count.
int count = RValue_toInt32(b);
Expand Down
18 changes: 12 additions & 6 deletions src/vm.h
Original file line number Diff line number Diff line change
Expand Up @@ -333,13 +333,19 @@ static inline bool VM_shouldTraceVariable(StringBooleanEntry* traceMap, const ch
if (shgeti(traceMap, "*") != -1) return true;
if (shgeti(traceMap, scopeName) != -1) return true;
if (altScopeName != nullptr && shgeti(traceMap, altScopeName) != -1) return true;
char formatted[strlen(scopeName) + 1 + strlen(varName) + 1];
snprintf(formatted, sizeof(formatted), "%s.%s", scopeName, varName);
if (shgeti(traceMap, formatted) != -1) return true;
size_t formattedLen = strlen(scopeName) + 1 + strlen(varName) + 1;
char *formatted = malloc(formattedLen);
snprintf(formatted, formattedLen, "%s.%s", scopeName, varName);
bool match = shgeti(traceMap, formatted) != -1;
free(formatted);
if (match) return true;
if (altScopeName != nullptr) {
char altFormatted[strlen(altScopeName) + 1 + strlen(varName) + 1];
snprintf(altFormatted, sizeof(altFormatted), "%s.%s", altScopeName, varName);
if (shgeti(traceMap, altFormatted) != -1) return true;
size_t altFormattedLen = strlen(altScopeName) + 1 + strlen(varName) + 1;
char *altFormatted = malloc(altFormattedLen);
snprintf(altFormatted, altFormattedLen, "%s.%s", altScopeName, varName);
bool altMatch = shgeti(traceMap, altFormatted) != -1;
free(altFormatted);
if (altMatch) return true;
}
return false;
}
Expand Down
Loading
Loading