From b999fec59ee85702d7814b8625271174be717bfc Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Thu, 16 Jul 2026 12:02:30 +0100 Subject: [PATCH 1/5] vm: only check for modulo by zero for integers Fixes Deltarune Ch5 crashing when entering the garden of hopes and dreams --- src/vm.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/vm.c b/src/vm.c index c7085f574..afd6c0855 100644 --- a/src/vm.c +++ b/src/vm.c @@ -1550,9 +1550,14 @@ static void handleDiv(VMContext* ctx, uint32_t instr) { static void handleRem(VMContext* ctx, uint32_t instr) { RValue b = stackPop(ctx); RValue a = stackPop(ctx); - int64_t divisor = RValue_toInt64(b); - requireMessageFormatted(__FILE__, __LINE__, divisor != 0, "VM: [%s] DoRem :: Divide by zero", ctx->currentCodeName); - int64_t result = RValue_toInt64(a) / divisor; + uint8_t type1 = instrType1(instr); + uint8_t type2 = instrType2(instr); + GMLReal divisor = RValue_toReal(b); + // Ditto + if ((type1 == GML_TYPE_INT32 || type1 == GML_TYPE_INT64) && (type2 == GML_TYPE_INT32 || type2 == GML_TYPE_INT64)) { + requireMessageFormatted(__FILE__, __LINE__, divisor != 0.0, "VM: [%s] DoRem :: Divide by zero", ctx->currentCodeName); + } + GMLReal result = GMLReal_fmod(RValue_toReal(a), divisor); RValue_free(&a); RValue_free(&b); stackPushTyped(ctx, RValue_makeInt64(result), instrType2(instr)); From c348702253eeeed7bd6a875a139ae596eaed949b Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Thu, 16 Jul 2026 12:08:27 +0100 Subject: [PATCH 2/5] tile_set_mirror, tile_set_flip, tile_set_rotate --- src/vm_builtins.c | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/vm_builtins.c b/src/vm_builtins.c index ad61091e4..51ab215af 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -14086,14 +14086,14 @@ static RValue builtin_tile_get_index(MAYBE_UNUSED VMContext* ctx, RValue* args, // (see GameMaker-HTML5 Function_Layers.js) static RValue builtin_tile_get_mirror(MAYBE_UNUSED VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t argCount) { if (1 > argCount) return RValue_makeBool(false); - return RValue_makeBool((RValue_toInt32(args[0]) & (1 << 28)) != 0); + return RValue_makeBool((RValue_toInt32(args[0]) & TILEMIRROR_MASK) != 0); } // tile_get_flip(tiledata): returns whether the vertical-flip bit is set on a raw tile cell value. // (see GameMaker-HTML5 Function_Layers.js) static RValue builtin_tile_get_flip(MAYBE_UNUSED VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t argCount) { if (1 > argCount) return RValue_makeBool(false); - return RValue_makeBool((RValue_toInt32(args[0]) & (1 << 29)) != 0); + return RValue_makeBool((RValue_toInt32(args[0]) & TILEFLIP_MASK) != 0); } // tile_get_rotate(tiledata): returns whether the 90-degree-rotate bit is set on a raw tile cell value. @@ -14110,6 +14110,42 @@ static RValue builtin_tile_set_empty(MAYBE_UNUSED VMContext* ctx, RValue* args, return RValue_makeReal((GMLReal) (RValue_toInt32(args[0]) & ~TILEINDEX_SHIFTEDMASK)); } +// tile_set_mirror(tiledata): sets the horizontal-mirror bit on a raw tile cell value. +// (see GameMaker-HTML5 Function_Layers.js) +static RValue builtin_tile_set_mirror(MAYBE_UNUSED VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t argCount) { + if (2 > argCount) return RValue_makeReal(-1.0); + int32_t cell = RValue_toInt32(args[0]); + if (RValue_toBool(args[1])) + cell |= TILEMIRROR_MASK; + else + cell &= ~TILEMIRROR_MASK; + return RValue_makeReal((GMLReal) cell); +} + +// tile_set_flip(tiledata): sets the vertical-flip bit on a raw tile cell value. +// (see GameMaker-HTML5 Function_Layers.js) +static RValue builtin_tile_set_flip(MAYBE_UNUSED VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t argCount) { + if (2 > argCount) return RValue_makeReal(-1.0); + int32_t cell = RValue_toInt32(args[0]); + if (RValue_toBool(args[1])) + cell |= TILEFLIP_MASK; + else + cell &= ~TILEFLIP_MASK; + return RValue_makeReal((GMLReal) cell); +} + +// tile_set_rotate(tiledata): sets the 90-degree-rotate bit on a raw tile cell value. +// (see GameMaker-HTML5 Function_Layers.js) +static RValue builtin_tile_set_rotate(MAYBE_UNUSED VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t argCount) { + if (2 > argCount) return RValue_makeReal(-1.0); + int32_t cell = RValue_toInt32(args[0]); + if (RValue_toBool(args[1])) + cell |= TILEROTATE_MASK; + else + cell &= ~TILEROTATE_MASK; + return RValue_makeReal((GMLReal) cell); +} + static RValue builtin_layer_get_all(VMContext* ctx, MAYBE_UNUSED RValue* args, MAYBE_UNUSED int32_t argCount) { Runner* runner = ctx->runner; size_t count = arrlenu(runner->runtimeLayers); @@ -17079,6 +17115,9 @@ void VMBuiltins_registerAll(VMContext* ctx) { VM_registerBuiltin(ctx, "tile_get_flip", builtin_tile_get_flip); VM_registerBuiltin(ctx, "tile_get_rotate", builtin_tile_get_rotate); VM_registerBuiltin(ctx, "tile_set_empty", builtin_tile_set_empty); + VM_registerBuiltin(ctx, "tile_set_mirror", builtin_tile_set_mirror); + VM_registerBuiltin(ctx, "tile_set_flip", builtin_tile_set_flip); + VM_registerBuiltin(ctx, "tile_set_rotate", builtin_tile_set_rotate); VM_registerBuiltin(ctx, "tilemap_set", builtin_tilemap_set); VM_registerBuiltin(ctx, "tilemap_set_at_pixel", builtin_tilemap_set_at_pixel); #endif From 1bb2409ddc971b8464e65e29d81a712a3d322644 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Thu, 16 Jul 2026 12:34:08 +0100 Subject: [PATCH 3/5] why are we doing requireNotNull here again? fixes segfaults in some ch5 rooms --- src/vm_builtins.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vm_builtins.c b/src/vm_builtins.c index 51ab215af..d33f0e357 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -13536,9 +13536,8 @@ static RValue builtin_layer_tile_visible(VMContext* ctx, RValue* args, MAYBE_UNU } static bool isValidLayerSpriteElement(RuntimeLayerElement* element) { - bool isValid = element != nullptr && element->type == RuntimeLayerElementType_Sprite; - requireNotNull(element->spriteElement); // If this crashes then something went DEEPLY wrong - return isValid; + return element != nullptr && element->spriteElement != nullptr && \ + element->type == RuntimeLayerElementType_Sprite; } static RValue builtin_layer_sprite_get_id(VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t argCount) { From a485eff0c1baa3cd5d9855d2a50f09bb1861291f Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Fri, 17 Jul 2026 11:35:29 +0100 Subject: [PATCH 4/5] draw_path fixes lines in ch2 cyber world background --- src/vm_builtins.c | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/vm_builtins.c b/src/vm_builtins.c index d33f0e357..647c634bb 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -14373,6 +14373,46 @@ static GamePath* getPath(Runner* runner, int32_t pathIdx) { return &runner->dataWin->path.paths[pathIdx]; } +// draw_path(index, x, y, absolute) - draws a path outline using the current draw color/alpha +static RValue builtin_draw_path(VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t argCount) { + Runner* runner = ctx->runner; + if (runner->renderer == nullptr) return RValue_makeUndefined(); + + int32_t index = RValue_toInt32(args[0]); + GMLReal x = RValue_toReal(args[1]); + GMLReal y = RValue_toReal(args[2]); + bool absolute = RValue_toBool(args[3]); + + GamePath* path = getPath(runner, index); + if (path == nullptr) return RValue_makeUndefined(); + + PathPositionResult origin = GamePath_getPosition(path, 0.0f); + + float xoff = absolute ? 0.0f : x - origin.x; + float yoff = absolute ? 0.0f : y - origin.y; + + if (runner->applyOffsetForPrimitives) { + xoff += 1.0f; + yoff += 1.0f; + } + + int maxsteps = (int) roundf(path->length / 4.0f); + if (maxsteps <= 0) return RValue_makeUndefined(); + + PathPositionResult prev = GamePath_getPosition(path, 0.0f); + for (int i = 1; i <= maxsteps; i++) { + float t = (float) i / (float) maxsteps; + PathPositionResult cur = GamePath_getPosition(path, t); + runner->renderer->vtable->drawLine(runner->renderer, + xoff + prev.x, yoff + prev.y, + xoff + cur.x, yoff + cur.y, + 1.0f, runner->renderer->drawColor, runner->renderer->drawAlpha); + prev = cur; + } + + return RValue_makeUndefined(); +} + // path_add() - create a new empty path, return its index static RValue builtin_path_add(VMContext* ctx, MAYBE_UNUSED RValue* args, MAYBE_UNUSED int32_t argCount) { Runner* runner = ctx->runner; @@ -16922,6 +16962,7 @@ void VMBuiltins_registerAll(VMContext* ctx) { VM_registerBuiltin(ctx, "draw_ellipse_color", builtin_draw_ellipse_color); VM_registerBuiltin(ctx, "draw_set_circle_precision", builtin_draw_set_circle_precision); VM_registerBuiltin(ctx, "draw_get_circle_precision", builtin_draw_get_circle_precision); + VM_registerBuiltin(ctx, "draw_path", builtin_draw_path); VM_registerBuiltin(ctx, "draw_set_colour", builtin_draw_set_colour); VM_registerBuiltin(ctx, "draw_get_colour", builtin_draw_get_colour); VM_registerBuiltin(ctx, "draw_get_color", builtin_draw_get_color); From b450b4184bb4d056458d0b345d4a31c650361d7b Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Fri, 17 Jul 2026 14:01:13 +0100 Subject: [PATCH 5/5] date_current_datetime --- src/vm_builtins.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/vm_builtins.c b/src/vm_builtins.c index 647c634bb..2c25ab4a2 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -7866,6 +7866,12 @@ static RValue builtin_window_get_cursor(VMContext* ctx, MAYBE_UNUSED RValue* arg return RValue_makeReal(runner->currentCursor); } +// ===[ Date Functions ]=== +static RValue builtin_date_current_datetime(VMContext* ctx, MAYBE_UNUSED RValue* args, MAYBE_UNUSED int32_t argCount) { + time_t now = time(NULL); + return RValue_makeReal((GMLReal)now / (GMLReal)86400 + (GMLReal)25569); +} + // ===[ Game State Functions ]=== static RValue builtin_game_restart(VMContext* ctx, MAYBE_UNUSED RValue* args, MAYBE_UNUSED int32_t argCount) { ctx->runner->pendingRoom = ROOM_RESTARTGAME; @@ -16793,6 +16799,9 @@ void VMBuiltins_registerAll(VMContext* ctx) { VM_registerBuiltin(ctx, "window_set_cursor", builtin_window_set_cursor); VM_registerBuiltin(ctx, "window_get_cursor", builtin_window_get_cursor); + // Date + VM_registerBuiltin(ctx, "date_current_datetime", builtin_date_current_datetime); + // Game VM_registerBuiltin(ctx, "game_restart", builtin_game_restart); VM_registerBuiltin(ctx, "game_end", builtin_game_end);