Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
4aafbcc
Add log system
Purplesmaug96 Jul 15, 2026
b6ea831
Make the vLog* funcs internal
Purplesmaug96 Jul 15, 2026
649db2c
Add file logging
Purplesmaug96 Jul 15, 2026
dd5e47e
Fix
Purplesmaug96 Jul 15, 2026
5237a79
Add vLog function variants and update utils.h to use log
Purplesmaug96 Jul 15, 2026
3ebd225
Replace most calls of fprintf(stderr, ...) with Log_log* functions
Purplesmaug96 Jul 15, 2026
927fd6c
Replace printf with Log_log
Purplesmaug96 Jul 15, 2026
40325ea
Fix some things
Purplesmaug96 Jul 15, 2026
0a2dfe4
Fix
Purplesmaug96 Jul 15, 2026
a6153ca
Add log arguments to desktop
Purplesmaug96 Jul 15, 2026
24a7fe7
Fix some things and remove debug logs
Purplesmaug96 Jul 15, 2026
d98e1dd
Fix some things
Purplesmaug96 Jul 16, 2026
848211e
Fix some more things
Purplesmaug96 Jul 16, 2026
9a9cae5
Add support for temporarily changing log file
Purplesmaug96 Jul 16, 2026
2a126e2
Add support for adding custom files to log to
Purplesmaug96 Jul 16, 2026
4bd7788
Fix some things
Purplesmaug96 Jul 16, 2026
6789f2e
Fix some things
Purplesmaug96 Jul 16, 2026
1308c5b
Fix
Purplesmaug96 Jul 16, 2026
97ca355
Fix some things
Purplesmaug96 Jul 16, 2026
19ff1f9
Fix
Purplesmaug96 Jul 16, 2026
79b68ff
Fix more
Purplesmaug96 Jul 16, 2026
dbaee1d
Add va_copy compatibility macro for msvc
Purplesmaug96 Jul 16, 2026
a96314f
Remove msvc va_copy define requirement
Purplesmaug96 Jul 16, 2026
94ed5fa
Fix some indentation issues
Purplesmaug96 Jul 16, 2026
eda431b
Fix
Purplesmaug96 Jul 16, 2026
912e154
Reduce redundancy
Purplesmaug96 Jul 16, 2026
78da838
No out variable needed
Purplesmaug96 Jul 16, 2026
a9a97cc
Change Log_setFile and Log_resetFile
Purplesmaug96 Jul 16, 2026
92a6f20
Fix some indentation issues
Purplesmaug96 Jul 16, 2026
1568d41
Add missing log.h in ps3_textures.c
Purplesmaug96 Jul 16, 2026
bda338b
Add log.c to web-meta
Purplesmaug96 Jul 16, 2026
82c76e4
Fix tests hopefully
Purplesmaug96 Jul 16, 2026
08d928d
Add logDebug functions
Purplesmaug96 Jul 16, 2026
0928730
Use the logDebug functions
Purplesmaug96 Jul 16, 2026
6a6c170
Add "Debug: " prefix to logDebug
Purplesmaug96 Jul 16, 2026
4b7735f
Rename log functions to what uniq said
Purplesmaug96 Jul 16, 2026
6aeda60
Add warning and error prefixes as well
Purplesmaug96 Jul 16, 2026
a0a667f
Use isatty
Purplesmaug96 Jul 16, 2026
a7eb85b
Do what uniq said i think
Purplesmaug96 Jul 16, 2026
b121198
Explicit cast
Purplesmaug96 Jul 16, 2026
8427e68
Hopefully fix web meta
Purplesmaug96 Jul 16, 2026
3a74c1a
Hopefully fix
Purplesmaug96 Jul 16, 2026
db6ea5f
Add compatability va_copy define in main.c
Purplesmaug96 Jul 16, 2026
e41d955
Do what uniq said (at least for desktop) i think
Purplesmaug96 Jul 16, 2026
a2caa32
Implement file logging in desktop
Purplesmaug96 Jul 16, 2026
cdbde34
Fix other platforms
Purplesmaug96 Jul 16, 2026
a3ef066
I forgot to give args to the va_copy compat macro
Purplesmaug96 Jul 16, 2026
6d3d69c
Fix other platforms hopefully
Purplesmaug96 Jul 16, 2026
ec9bb6b
Unconditionally va_copy
Purplesmaug96 Jul 16, 2026
d3d323c
Move va_end out of conditional
Purplesmaug96 Jul 16, 2026
3cc82f2
Maybe fix tests
Purplesmaug96 Jul 17, 2026
b2cbbd8
Maybe fix them
Purplesmaug96 Jul 17, 2026
1ba1991
Remove unused macro
Purplesmaug96 Jul 17, 2026
cd4108e
Do what uniq said for desktop i think
Purplesmaug96 Jul 17, 2026
85d724c
Fix other backends hopefully
Purplesmaug96 Jul 17, 2026
b5e8195
Hopefully fix compile errs
Purplesmaug96 Jul 17, 2026
d3a7fe4
Fix tests hopefully
Purplesmaug96 Jul 17, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ CMakeUserPresets.json
.devcontainer/
compat/config.mk
/.vs
*.log
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ if(PLATFORM STREQUAL "web-meta")
set(SOURCES
src/data_win.c
src/binary_reader.c
src/log.c
)
else()
file(GLOB SOURCES src/*.c)
Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<a href="https://discord.gg/2gQR7t3WJR"><img src="https://img.shields.io/discord/1406856655920168971?color=5865F2&logo=discord&logoColor=white&label=discord"></a>
</p>

> [!IMPORTANT]
> [!IMPORTANT]
> Butterscotch is still VERY early in development and it is NOT that good yet.

When you create a game in GameMaker: Studio and export it, GameMaker: Studio exports the game code as bytecode instead of native compiled code, and that bytecode is compatible with any other GameMaker: Studio runner (also known as YoYo runner), as long as they have matching GameMaker: Studio versions. This is similar to how Java applications work.
Expand Down Expand Up @@ -52,7 +52,7 @@ However, that doesn't mean that a game that uses a compatible version WILL run!

Of course, there are exceptions that break game compatibility altogether:

* Games compiled with YYC, because they use native code instead of bytecode.
* Games compiled with YYC, because they use native code instead of bytecode.
* Games compiled with the new [GMRT](https://github.com/YoYoGames/GMRT-Beta/tree/main), because they use native code instead of bytecode.

## Supported Platforms
Expand Down Expand Up @@ -152,7 +152,10 @@ The desktop target has a lot of nifty CLI parameters that you can use to trace a
--profile-opcodes - Rank which GML opcodes were executed the most
--lazy-textures - Load textures into VRAM on first use, improving startup times
--load-type <type> - Specify how data.win is loaded, per-chunk or all at once
```
--disable-file-log - Disable logging to a file
--log-file <filename> - File to log to
--disable-log-colours - Disable colours for warning, error, and debug logs
--disable-log-colors - Same as --disable-log-colours, but different spelling

## Debug Features

Expand All @@ -172,7 +175,7 @@ Performance is pretty good on any modern computer, but when running on low end t

## Then why not have a transpiler?

The issue with a transpiler is that, if you try transpiling the game in the "naive" way, that is, emitting VM calls like it was the original bytecode, you won't get any
The issue with a transpiler is that, if you try transpiling the game in the "naive" way, that is, emitting VM calls like it was the original bytecode, you won't get any
*improvement* from it, you would need to create a *good* transpiler that actually transpiles it into *good* code, and that's way harder.

Having a transpiler also have other disadvantages:
Expand Down
29 changes: 28 additions & 1 deletion src/android/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
#include "gl/gl_renderer.h"
#include "stb_ds.h"

#include "log.h"

#define LOG_TAG "Butterscotch"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
Expand All @@ -40,6 +42,31 @@ static float gNormalizedCursorY = 0.0f;
// We don't need to worry about game changes because the profiler will be automatically disabled then
static int32_t gProfilerStartedAtFrame = 0;

void platformLog(const logType type, const char *format, va_list va) {
const char* prefix = "";
switch (type) {
case LOG_TYPE_NORMAL:
break;
case LOG_TYPE_WARNING:
prefix = "Warning: ";
break;
case LOG_TYPE_ERROR:
prefix = "Error: ";
break;
case LOG_TYPE_DEBUG:
prefix = "Debug: ";
break;
}

const int newFormatSize = strlen(format) + strlen(prefix) + 1;
const char* newFormat = (char*)safeMalloc(newFormatSize);
snprintf(newFormat, newFormatSize, "%s%s", prefix, format);

__android_log_vprint(type == LOG_TYPE_NORMAL ? ANDROID_LOG_INFO : (type == LOG_TYPE_WARNING ? ANDROID_LOG_WARN : ANDROID_LOG_ERROR), LOG_TAG, newFormat, va);

free(newFormat);
}

// Android has no platformGetWindowSize like the desktop, so we cache the EGL surface size the host
// passes into stepAndDraw and expose it through the getWindowSize hook below.
static int32_t gWindowW = 0;
Expand Down Expand Up @@ -633,7 +660,7 @@ static bool performGameChange(const char* workingDirectory, char* launchParamete
}

if (dataWinFilename == nullptr) {
fprintf(stderr, "Runner: Launch parameters '%s' did not contain a '-game <file>' entry! Shutting down...\n", launchParameters);
logError("Runner: Launch parameters '%s' did not contain a '-game <file>' entry! Shutting down...\n", launchParameters);
repeat(arrlen(newArguments), i) {
free(newArguments[i]);
}
Expand Down
38 changes: 19 additions & 19 deletions src/audio/miniaudio/ma_audio_system.c
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,14 @@ static void maInit(AudioSystem* audio, DataWin* dataWin, FileSystem* fileSystem)
deviceConfig.pUserData = &ma->engine;
ma_result deviceResult = ma_device_init(NULL, &deviceConfig, &ma->device);
if (deviceResult != MA_SUCCESS) {
fprintf(stderr, "Audio: Failed to initialize playback device (error %d)\n", deviceResult);
logError("Audio: Failed to initialize playback device (error %d)\n", deviceResult);
return;
}
ma_engine_config config = ma_engine_config_init();
config.pDevice = &ma->device;
ma_result result = ma_engine_init(&config, &ma->engine);
if (result != MA_SUCCESS) {
fprintf(stderr, "Audio: Failed to initialize miniaudio engine (error %d)\n", result);
logError("Audio: Failed to initialize miniaudio engine (error %d)\n", result);
ma_device_uninit(&ma->device);
return;
}
Expand All @@ -130,7 +130,7 @@ static void maInit(AudioSystem* audio, DataWin* dataWin, FileSystem* fileSystem)
ma->listenerGains[i] = 1.0f;
}

fprintf(stderr, "Audio: miniaudio engine initialized\n");
logInfo("Audio: miniaudio engine initialized\n");
}

static void maDestroy(AudioSystem* audio) {
Expand Down Expand Up @@ -215,7 +215,7 @@ static int32_t maPlaySound(AudioSystem* audio, int32_t soundIndex, int32_t prior
if (isStream) {
int32_t streamSlot = soundIndex - AUDIO_STREAM_INDEX_BASE;
if (0 > streamSlot || streamSlot >= MAX_AUDIO_STREAMS || !ma->streams[streamSlot].active) {
fprintf(stderr, "Audio: Invalid stream index %d\n", soundIndex);
logWarn("Audio: Invalid stream index %d\n", soundIndex);
return -1;
}
AudioStreamEntry* stream = &ma->streams[streamSlot];
Expand All @@ -225,15 +225,15 @@ static int32_t maPlaySound(AudioSystem* audio, int32_t soundIndex, int32_t prior
} else {
DataWin* dw = ma->base.audioGroups[0]; // Audio Group 0 should always be data.win
if (0 > soundIndex || (uint32_t) soundIndex >= dw->sond.count) {
fprintf(stderr, "Audio: Invalid sound index %d\n", soundIndex);
logWarn("Audio: Invalid sound index %d\n", soundIndex);
return -1;
}
sound = &dw->sond.sounds[soundIndex];
}

SoundInstance* slot = findFreeSlot(ma);
if (slot == nullptr) {
fprintf(stderr, "Audio: No free sound slots for sound %d\n", soundIndex);
logWarn("Audio: No free sound slots for sound %d\n", soundIndex);
return -1;
}

Expand All @@ -244,7 +244,7 @@ static int32_t maPlaySound(AudioSystem* audio, int32_t soundIndex, int32_t prior
// Stream audio: load from file path stored in stream entry
result = ma_sound_init_from_file(&ma->engine, streamPath, MA_SOUND_FLAG_ASYNC, &ma->listenerGroups[0], nullptr, &slot->maSound);
if (result != MA_SUCCESS) {
fprintf(stderr, "Audio: Failed to load stream file '%s' (error %d)\n", streamPath, result);
logWarn("Audio: Failed to load stream file '%s' (error %d)\n", streamPath, result);
return -1;
}
slot->ownsDecoder = false;
Expand All @@ -257,7 +257,7 @@ static int32_t maPlaySound(AudioSystem* audio, int32_t soundIndex, int32_t prior
if (inAudo) {
// Embedded audio: decode from AUDO chunk memory
if (0 > sound->audioFile || (uint32_t) sound->audioFile >= ma->base.audioGroups[sound->audioGroup]->audo.count) {
fprintf(stderr, "Audio: Invalid audio file index %d for sound '%s'\n", sound->audioFile, sound->name);
logWarn("Audio: Invalid audio file index %d for sound '%s'\n", sound->audioFile, sound->name);
return -1;
}

Expand All @@ -266,28 +266,28 @@ static int32_t maPlaySound(AudioSystem* audio, int32_t soundIndex, int32_t prior
ma_decoder_config decoderConfig = ma_decoder_config_init_default();
result = ma_decoder_init_memory(entry->data, entry->dataSize, &decoderConfig, &slot->decoder);
if (result != MA_SUCCESS) {
fprintf(stderr, "Audio: Failed to init decoder for '%s' (error %d)\n", sound->name, result);
logWarn("Audio: Failed to init decoder for '%s' (error %d)\n", sound->name, result);
return -1;
}
slot->ownsDecoder = true;

result = ma_sound_init_from_data_source(&ma->engine, &slot->decoder, 0, &ma->listenerGroups[0], &slot->maSound);
if (result != MA_SUCCESS) {
fprintf(stderr, "Audio: Failed to init sound from decoder for '%s' (error %d)\n", sound->name, result);
logWarn("Audio: Failed to init sound from decoder for '%s' (error %d)\n", sound->name, result);
ma_decoder_uninit(&slot->decoder);
return -1;
}
} else {
// External audio: load from file
char* path = resolveExternalPath(ma, sound);
if (path == nullptr) {
fprintf(stderr, "Audio: Could not resolve path for sound '%s'\n", sound->name);
logWarn("Audio: Could not resolve path for sound '%s'\n", sound->name);
return -1;
}

result = ma_sound_init_from_file(&ma->engine, path, MA_SOUND_FLAG_ASYNC, &ma->listenerGroups[0], nullptr, &slot->maSound);
if (result != MA_SUCCESS) {
fprintf(stderr, "Audio: Failed to load file for '%s' at '%s' (error %d)\n", sound->name, path, result);
logWarn("Audio: Failed to load file for '%s' at '%s' (error %d)\n", sound->name, path, result);
free(path);
return -1;
}
Expand Down Expand Up @@ -741,7 +741,7 @@ static void maGroupLoad(AudioSystem* audio, int32_t groupIndex) {
// The original runner does not care if the file doesn't exist (this may happen if someone uses "audio_group_load" on a non-existent group)
FileSystem* fileSystem = ((MaAudioSystem*)audio)->fileSystem;
if (!fileSystem->vtable->fileExists(fileSystem, buf)) {
fprintf(stderr, "Audio: Wanted to load Audio Group %d, but Audio Group %d does not exist in the file system!\n", groupIndex, groupIndex);
logWarn("Audio: Wanted to load Audio Group %d, but Audio Group %d does not exist in the file system!\n", groupIndex, groupIndex);
free(buf);
DataWin* dw = (DataWin *)safeCalloc(1, sizeof(DataWin));
arrput(audio->audioGroups, dw);
Expand All @@ -754,7 +754,7 @@ static void maGroupLoad(AudioSystem* audio, int32_t groupIndex) {
arrput(audio->audioGroups, audioGroup);
free(buf);
} else {
fprintf(stderr, "Audio: Wanted to load Audio Group %d, but Audio Group %d does not exist in the AGPR!\n", groupIndex, groupIndex);
logWarn("Audio: Wanted to load Audio Group %d, but Audio Group %d does not exist in the AGPR!\n", groupIndex, groupIndex);
}
}

Expand All @@ -777,13 +777,13 @@ static int32_t maCreateStream(AudioSystem* audio, const char* filename) {
}

if (0 > freeSlot) {
fprintf(stderr, "Audio: No free stream slots for '%s'\n", filename);
logWarn("Audio: No free stream slots for '%s'\n", filename);
return -1;
}

char* resolved = ma->fileSystem->vtable->resolvePath(ma->fileSystem, filename);
if (resolved == nullptr) {
fprintf(stderr, "Audio: Could not resolve path for stream '%s'\n", filename);
logWarn("Audio: Could not resolve path for stream '%s'\n", filename);
return -1;
}

Expand All @@ -793,7 +793,7 @@ static int32_t maCreateStream(AudioSystem* audio, const char* filename) {
ma->streams[freeSlot].initialPitch = 1.0f;

int32_t streamIndex = AUDIO_STREAM_INDEX_BASE + freeSlot;
fprintf(stderr, "Audio: Created stream %d for '%s' -> '%s'\n", streamIndex, filename, resolved);
logInfo("Audio: Created stream %d for '%s' -> '%s'\n", streamIndex, filename, resolved);
return streamIndex;
}

Expand All @@ -802,7 +802,7 @@ static bool maDestroyStream(AudioSystem* audio, int32_t streamIndex) {

int32_t slotIndex = streamIndex - AUDIO_STREAM_INDEX_BASE;
if (0 > slotIndex || slotIndex >= MAX_AUDIO_STREAMS) {
fprintf(stderr, "Audio: Invalid stream index %d for destroy\n", streamIndex);
logWarn("Audio: Invalid stream index %d for destroy\n", streamIndex);
return false;
}

Expand All @@ -825,7 +825,7 @@ static bool maDestroyStream(AudioSystem* audio, int32_t streamIndex) {
free(entry->filePath);
entry->filePath = nullptr;
entry->active = false;
fprintf(stderr, "Audio: Destroyed stream %d\n", streamIndex);
logInfo("Audio: Destroyed stream %d\n", streamIndex);
return true;
}

Expand Down
Loading
Loading