From c6e666c4589073cf217f63152707ec7c91ea1537 Mon Sep 17 00:00:00 2001 From: AloXado320 Date: Fri, 19 Jun 2026 19:34:05 -0500 Subject: [PATCH 1/3] Implement more ds_map functions, filename_name and made json_decode more accurate --- src/vm_builtins.c | 270 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 221 insertions(+), 49 deletions(-) diff --git a/src/vm_builtins.c b/src/vm_builtins.c index 723c4f18..8dbdb11f 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -4136,7 +4136,14 @@ static RValue builtin_ds_map_create(VMContext* ctx, MAYBE_UNUSED RValue* args, M return RValue_makeReal((GMLReal) dsMapCreate(runner)); } -static RValue builtin_ds_map_add(VMContext* ctx, RValue* args, int32_t argCount) { +static RValue makeMapListContainer(VMContext* ctx, int32_t id, int32_t type) { + Instance* container = Runner_createStruct(ctx->runner); + VM_structSetAndFreeVal(ctx, container, "ObjType", RValue_makeInt32(type), -1); + VM_structSetAndFreeVal(ctx, container, "Object", RValue_makeInt32(id), -1); + return RValue_makeStructAndIncRef(container); +} + +static RValue dsMapAddCommon(VMContext* ctx, RValue* args, int32_t argCount, bool wrapAsContainer, int32_t containerType) { if (3 > argCount) return RValue_makeUndefined(); Runner* runner = ctx->runner; int32_t id = RValue_toInt32(args[0]); @@ -4145,18 +4152,82 @@ static RValue builtin_ds_map_add(VMContext* ctx, RValue* args, int32_t argCount) char* key = RValue_toString(args[1]); - // Only add if key doesn't exist - bool exists = shgeti(*mapPtr, key) != -1; + RValue valueToStore; + if (wrapAsContainer) { + // Wrap the value (ds_map or ds_list ID) in a container struct + int32_t containedId = RValue_toInt32(args[2]); + valueToStore = makeMapListContainer(ctx, containedId, containerType); + } else { + // Store the value directly + valueToStore = RValue_makeIndependent(args[2]); + } - if (exists) { - free(key); // Key already exists, we didn't insert it + // Check if key exists + ptrdiff_t existingIdx = shgeti(*mapPtr, key); + if (existingIdx != -1) { + // Key already exists - replace the value + RValue_free(&(*mapPtr)[existingIdx].value); + (*mapPtr)[existingIdx].value = valueToStore; + free(key); // The key is already stored in the map } else { - shput(*mapPtr, key, RValue_makeIndependent(args[2])); + shput(*mapPtr, key, valueToStore); } return RValue_makeUndefined(); } +static RValue builtin_ds_map_add(VMContext* ctx, RValue* args, int32_t argCount) { + return dsMapAddCommon(ctx, args, argCount, false, 0); +} + +static RValue builtin_ds_map_add_map(VMContext* ctx, RValue* args, int32_t argCount) { + return dsMapAddCommon(ctx, args, argCount, true, 1); // Map type +} + +static RValue builtin_ds_map_add_list(VMContext* ctx, RValue* args, int32_t argCount) { + return dsMapAddCommon(ctx, args, argCount, true, 2); // List type +} + +static RValue builtin_ds_map_is_map(VMContext* ctx, RValue* args, int32_t argCount) { + if (2 > argCount) return RValue_makeBool(false); + Runner* runner = ctx->runner; + int32_t id = RValue_toInt32(args[0]); + DsMapEntry** mapPtr = dsMapGet(runner, id); + if (mapPtr == nullptr) return RValue_makeBool(false); + + ptrdiff_t idx = getValueIndexInMap(mapPtr, args[1]); + if (0 > idx) return RValue_makeBool(false); + + RValue val = (*mapPtr)[idx].value; + if (val.type == RVALUE_STRUCT && val.structInst != NULL) { + RValue objType = VM_structGetVariableByVarName(ctx, val.structInst, "ObjType", -1); + if (objType.type != RVALUE_UNDEFINED) { + return RValue_makeBool(RValue_toInt32(objType) == 1); // Map type + } + } + return RValue_makeBool(false); +} + +static RValue builtin_ds_map_is_list(VMContext* ctx, RValue* args, int32_t argCount) { + if (2 > argCount) return RValue_makeBool(false); + Runner* runner = ctx->runner; + int32_t id = RValue_toInt32(args[0]); + DsMapEntry** mapPtr = dsMapGet(runner, id); + if (mapPtr == nullptr) return RValue_makeBool(false); + + ptrdiff_t idx = getValueIndexInMap(mapPtr, args[1]); + if (0 > idx) return RValue_makeBool(false); + + RValue val = (*mapPtr)[idx].value; + if (val.type == RVALUE_STRUCT && val.structInst != NULL) { + RValue objType = VM_structGetVariableByVarName(ctx, val.structInst, "ObjType", -1); + if (objType.type != RVALUE_UNDEFINED) { + return RValue_makeBool(RValue_toInt32(objType) == 2); // List type + } + } + return RValue_makeBool(false); +} + static RValue builtin_ds_map_clear(VMContext* ctx, RValue* args, int32_t argCount) { if (1 > argCount) return RValue_makeUndefined(); Runner* runner = ctx->runner; @@ -4248,6 +4319,17 @@ static RValue builtin_ds_map_find_value(VMContext* ctx, RValue* args, int32_t ar if (0 > idx) return RValue_makeUndefined(); RValue val = (*mapPtr)[idx].value; + + if (val.type == RVALUE_STRUCT && val.structInst != NULL) { + RValue objectVal = VM_structGetVariableByVarName(ctx, val.structInst, "Object", -1); + if (objectVal.type != RVALUE_UNDEFINED) { + RValue result = RValue_makeIndependent(objectVal); + RValue_free(&objectVal); + return result; + } + RValue_free(&objectVal); + } + if (val.type == RVALUE_STRING && val.string != nullptr) { return RValue_makeOwnedString(safeStrdup(val.string)); } @@ -9021,36 +9103,6 @@ static RValue builtin_buffer_async_group_end(MAYBE_UNUSED VMContext* ctx, MAYBE_ return RValue_makeReal((GMLReal) requestId); } -// filename_change_ext(fname, newext): changes the extension of fname to newext -// (see GameMaker-HTML5 Function_File.js for reference) -static RValue builtin_filename_change_ext(MAYBE_UNUSED VMContext* ctx, MAYBE_UNUSED RValue* args, MAYBE_UNUSED int32_t argCount) { - if (2 > argCount) return RValue_makeUndefined(); - - char* fname = RValue_toString(args[0]); - char* newext = RValue_toString(args[1]); // includes the ., example: ".gmk" - - char *last = strrchr(fname, '.'); - - if (last != nullptr && last != 0) { - long index = last - fname; - char* new_name = safeMalloc(index + strlen(newext) + 1); - memcpy(new_name, fname, (size_t) index); - memcpy(new_name + index, newext, (size_t) strlen(newext)); - new_name[index + strlen(newext)] = '\0'; - RValue result = RValue_makeOwnedString(new_name); - - free(fname); - free(newext); - - return result; - } - - free(newext); - - // If there isn't a dot, we return the original string as is - return RValue_makeOwnedString(fname); -} - static RValue builtin_buffer_base64_encode(MAYBE_UNUSED VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t argCount) { Runner* runner = ctx->runner; if (3 > argCount) return RValue_makeOwnedString(safeStrdup("")); @@ -9222,6 +9274,60 @@ static RValue builtin_md5_file(MAYBE_UNUSED VMContext* ctx, RValue* args, MAYBE_ return RValue_makeOwnedString(convertToHexString(digest, 16)); } +// filename_change_ext(fname, newext): changes the extension of fname to newext +// (see GameMaker-HTML5 Function_File.js for reference) +static RValue builtin_filename_change_ext(MAYBE_UNUSED VMContext* ctx, MAYBE_UNUSED RValue* args, MAYBE_UNUSED int32_t argCount) { + if (2 > argCount) return RValue_makeUndefined(); + + char* fname = RValue_toString(args[0]); + char* newext = RValue_toString(args[1]); // includes the ., example: ".gmk" + + char *last = strrchr(fname, '.'); + + if (last != nullptr && last != 0) { + long index = last - fname; + char* new_name = safeMalloc(index + strlen(newext) + 1); + memcpy(new_name, fname, (size_t) index); + memcpy(new_name + index, newext, (size_t) strlen(newext)); + new_name[index + strlen(newext)] = '\0'; + RValue result = RValue_makeOwnedString(new_name); + + free(fname); + free(newext); + + return result; + } + + free(newext); + + // If there isn't a dot, we return the original string as is + return RValue_makeOwnedString(fname); +} + +// filename_name(fname): returns the name part of the indicated file, with the extension but without the path +// (see GameMaker-HTML5 Function_File.js for reference) +static RValue builtin_filename_name(MAYBE_UNUSED VMContext* ctx, RValue* args, int32_t argCount) { + if (1 > argCount) return RValue_makeOwnedString(safeStrdup("")); + + char* fname = RValue_toString(args[0]); + if (fname == NULL) return RValue_makeOwnedString(safeStrdup("")); + + char* lastBackslash = strrchr(fname, '\\'); + char* lastSlash = strrchr(fname, '/'); + char* last = lastBackslash > lastSlash ? lastBackslash : lastSlash; + + char* result; + if (last != NULL) { + result = safeStrdup(last + 1); + } else { + result = fname; + fname = NULL; + } + + free(fname); + return RValue_makeOwnedString(result); +} + // buffer_get_surface(buffer, surface, offset) -> bool // Reads RGBA8 pixels from the surface into the buffer at the given offset. static RValue builtin_buffer_get_surface(VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t argCount) { @@ -14560,7 +14666,58 @@ static RValue builtin_json_encode(VMContext* ctx, RValue* args, int32_t argCount return RValue_makeOwnedString(result); } -// json_decode +// Recursively decode a JSON value into a GML value +static RValue jsonDecodeValue(VMContext* ctx, JsonValue* json) { + if (json == NULL) return RValue_makeUndefined(); + + switch (json->type) { + case JSON_NULL: + return RValue_makeUndefined(); + case JSON_BOOL: + return RValue_makeBool(json->boolValue); + case JSON_NUMBER: + return RValue_makeReal((GMLReal)json->numberValue); + case JSON_STRING: + return RValue_makeOwnedString(safeStrdup(json->stringValue ? json->stringValue : "")); + case JSON_ARRAY: { + // For arrays, create a ds_list (matches HTML5 - _json_decode_array) + int32_t listId = dsListCreate(ctx->runner); + DsList* list = dsListGet(ctx->runner, listId); + if (list != NULL) { + int len = JsonReader_arrayLength(json); + for (int i = 0; i < len; i++) { + JsonValue* item = JsonReader_getArrayElement(json, i); + RValue val = jsonDecodeValue(ctx, item); + arrput(list->items, val); + } + } + return RValue_makeReal((GMLReal)listId); + } + case JSON_OBJECT: { + // For arrays, create a ds_map (matches HTML5 - _json_decode_object) + int32_t mapId = dsMapCreate(ctx->runner); + DsMapEntry** mapPtr = dsMapGet(ctx->runner, mapId); + if (mapPtr != NULL) { + int len = JsonReader_objectLength(json); + for (int i = 0; i < len; i++) { + const char* key = JsonReader_getJsonKeyByIndex(json, i); + JsonValue* valJson = JsonReader_getJsonValueByIndex(json, i); + RValue val = jsonDecodeValue(ctx, valJson); + + // Store in the map + char* keyCopy = safeStrdup(key); + RValue storedVal = RValue_makeIndependent(val); + RValue_free(&val); + shput(*mapPtr, keyCopy, storedVal); + } + } + return RValue_makeReal((GMLReal)mapId); + } + default: + return RValue_makeUndefined(); + } +} + static RValue builtin_json_decode(VMContext* ctx, RValue* args, int32_t argCount) { if (1 > argCount) { fprintf(stderr, "[json_decode] Expected at least 1 argument\n"); @@ -14568,24 +14725,32 @@ static RValue builtin_json_decode(VMContext* ctx, RValue* args, int32_t argCount } Runner* runner = ctx->runner; - int32_t mapIndex = dsMapCreate(runner); - DsMapEntry **mapPtr = dsMapGet(runner, mapIndex); const char* content = args[0].string; + JsonValue* json = JsonReader_parse(content); - // While the docs say "An invalid DS Map handle (-1) is returned in case the JSON could not be decoded.", when looking at the GameMaker-HTML5 source code it actually wraps in a "default" block when it fails to be parsed + // While the docs say "An invalid ds_map handle (-1) is returned in case the JSON could not be decoded", + // when looking at the GameMaker-HTML5 source code it actually wraps in a "default" block when it fails to be parsed if (json == nullptr) { - shput(*mapPtr, "default", RValue_makeIndependent(args[0])); - } else { - repeat(JsonReader_objectLength(json), i) { - const char *key = safeStrdup(JsonReader_getJsonKeyByIndex(json, i)); - RValue val = RValue_makeOwnedString(safeStrdup(JsonReader_getString(JsonReader_getJsonValueByIndex(json, i)))); - shput(*mapPtr, key, val); + int32_t mapIndex = dsMapCreate(runner); + DsMapEntry** mapPtr = dsMapGet(runner, mapIndex); + if (mapPtr != NULL) { + shput(*mapPtr, safeStrdup("default"), RValue_makeIndependent(args[0])); } + return RValue_makeReal((GMLReal)mapIndex); + } + + // Recursively decode the JSON + RValue result = jsonDecodeValue(ctx, json); + JsonReader_free(json); - JsonReader_free(json); + // result should be a ds_map ID (from the top-level object) + if (result.type == RVALUE_REAL || result.type == RVALUE_INT32) { + int32_t mapId = RValue_toInt32(result); + DsMapEntry** mapPtr = dsMapGet(runner, mapId); + return result; } - return RValue_makeReal(mapIndex); + return RValue_makeReal((GMLReal)dsMapCreate(runner)); } static RValue builtin_object_exists(VMContext* ctx, RValue* args, int32_t argCount) { @@ -15557,6 +15722,10 @@ void VMBuiltins_registerAll(VMContext* ctx) { VM_registerBuiltin(ctx, "ds_map_create", builtin_ds_map_create); VM_registerBuiltin(ctx, "ds_map_delete", builtin_ds_map_delete); VM_registerBuiltin(ctx, "ds_map_add", builtin_ds_map_add); + VM_registerBuiltin(ctx, "ds_map_add_map", builtin_ds_map_add_map); + VM_registerBuiltin(ctx, "ds_map_add_list", builtin_ds_map_add_list); + VM_registerBuiltin(ctx, "ds_map_is_map", builtin_ds_map_is_map); + VM_registerBuiltin(ctx, "ds_map_is_list", builtin_ds_map_is_list); VM_registerBuiltin(ctx, "ds_map_clear", builtin_ds_map_clear); VM_registerBuiltin(ctx, "ds_map_set", builtin_ds_map_set); VM_registerBuiltin(ctx, "ds_map_set_pre", builtin_ds_map_set_pre); @@ -15891,7 +16060,6 @@ void VMBuiltins_registerAll(VMContext* ctx) { VM_registerBuiltin(ctx, "buffer_save_async", builtin_buffer_save_async); VM_registerBuiltin(ctx, "buffer_async_group_begin", builtin_buffer_async_group_begin); VM_registerBuiltin(ctx, "buffer_async_group_end", builtin_buffer_async_group_end); - VM_registerBuiltin(ctx, "filename_change_ext", builtin_filename_change_ext); VM_registerBuiltin(ctx, "buffer_base64_encode", builtin_buffer_base64_encode); VM_registerBuiltin(ctx, "buffer_base64_decode", builtin_buffer_base64_decode); VM_registerBuiltin(ctx, "base64_encode", builtin_base64_encode); @@ -15902,6 +16070,10 @@ void VMBuiltins_registerAll(VMContext* ctx) { VM_registerBuiltin(ctx, "sha1_file", builtin_sha1_file); VM_registerBuiltin(ctx, "md5_file", builtin_md5_file); + // Filename + VM_registerBuiltin(ctx, "filename_change_ext", builtin_filename_change_ext); + VM_registerBuiltin(ctx, "filename_name", builtin_filename_name); + // PSN VM_registerBuiltin(ctx, "psn_init", builtin_psn_init); VM_registerBuiltin(ctx, "psn_init_np_libs", builtin_psn_init_np_libs); From 09ab15f7ab7ba5818b623eaa57b5f234cf5c2eb2 Mon Sep 17 00:00:00 2001 From: AloXado320 Date: Fri, 19 Jun 2026 19:53:55 -0500 Subject: [PATCH 2/3] oops, debug leftover --- src/vm_builtins.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vm_builtins.c b/src/vm_builtins.c index 8dbdb11f..3f5b3984 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -14745,8 +14745,6 @@ static RValue builtin_json_decode(VMContext* ctx, RValue* args, int32_t argCount // result should be a ds_map ID (from the top-level object) if (result.type == RVALUE_REAL || result.type == RVALUE_INT32) { - int32_t mapId = RValue_toInt32(result); - DsMapEntry** mapPtr = dsMapGet(runner, mapId); return result; } From 984897483b17e6a8ddc79f09496522187a821940 Mon Sep 17 00:00:00 2001 From: AloXado320 Date: Fri, 19 Jun 2026 23:18:08 -0500 Subject: [PATCH 3/3] Use proper ds type defines, null to nullptr --- src/vm_builtins.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/vm_builtins.c b/src/vm_builtins.c index 3f5b3984..2b40a7e1 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -4181,11 +4181,11 @@ static RValue builtin_ds_map_add(VMContext* ctx, RValue* args, int32_t argCount) } static RValue builtin_ds_map_add_map(VMContext* ctx, RValue* args, int32_t argCount) { - return dsMapAddCommon(ctx, args, argCount, true, 1); // Map type + return dsMapAddCommon(ctx, args, argCount, true, DS_TYPE_MAP); } static RValue builtin_ds_map_add_list(VMContext* ctx, RValue* args, int32_t argCount) { - return dsMapAddCommon(ctx, args, argCount, true, 2); // List type + return dsMapAddCommon(ctx, args, argCount, true, DS_TYPE_LIST); } static RValue builtin_ds_map_is_map(VMContext* ctx, RValue* args, int32_t argCount) { @@ -4199,10 +4199,10 @@ static RValue builtin_ds_map_is_map(VMContext* ctx, RValue* args, int32_t argCou if (0 > idx) return RValue_makeBool(false); RValue val = (*mapPtr)[idx].value; - if (val.type == RVALUE_STRUCT && val.structInst != NULL) { + if (val.type == RVALUE_STRUCT && val.structInst != nullptr) { RValue objType = VM_structGetVariableByVarName(ctx, val.structInst, "ObjType", -1); if (objType.type != RVALUE_UNDEFINED) { - return RValue_makeBool(RValue_toInt32(objType) == 1); // Map type + return RValue_makeBool(RValue_toInt32(objType) == DS_TYPE_MAP); } } return RValue_makeBool(false); @@ -4219,10 +4219,10 @@ static RValue builtin_ds_map_is_list(VMContext* ctx, RValue* args, int32_t argCo if (0 > idx) return RValue_makeBool(false); RValue val = (*mapPtr)[idx].value; - if (val.type == RVALUE_STRUCT && val.structInst != NULL) { + if (val.type == RVALUE_STRUCT && val.structInst != nullptr) { RValue objType = VM_structGetVariableByVarName(ctx, val.structInst, "ObjType", -1); if (objType.type != RVALUE_UNDEFINED) { - return RValue_makeBool(RValue_toInt32(objType) == 2); // List type + return RValue_makeBool(RValue_toInt32(objType) == DS_TYPE_LIST); } } return RValue_makeBool(false); @@ -4320,7 +4320,7 @@ static RValue builtin_ds_map_find_value(VMContext* ctx, RValue* args, int32_t ar if (0 > idx) return RValue_makeUndefined(); RValue val = (*mapPtr)[idx].value; - if (val.type == RVALUE_STRUCT && val.structInst != NULL) { + if (val.type == RVALUE_STRUCT && val.structInst != nullptr) { RValue objectVal = VM_structGetVariableByVarName(ctx, val.structInst, "Object", -1); if (objectVal.type != RVALUE_UNDEFINED) { RValue result = RValue_makeIndependent(objectVal); @@ -9310,18 +9310,18 @@ static RValue builtin_filename_name(MAYBE_UNUSED VMContext* ctx, RValue* args, i if (1 > argCount) return RValue_makeOwnedString(safeStrdup("")); char* fname = RValue_toString(args[0]); - if (fname == NULL) return RValue_makeOwnedString(safeStrdup("")); + if (fname == nullptr) return RValue_makeOwnedString(safeStrdup("")); char* lastBackslash = strrchr(fname, '\\'); char* lastSlash = strrchr(fname, '/'); char* last = lastBackslash > lastSlash ? lastBackslash : lastSlash; char* result; - if (last != NULL) { + if (last != nullptr) { result = safeStrdup(last + 1); } else { result = fname; - fname = NULL; + fname = nullptr; } free(fname); @@ -14668,7 +14668,7 @@ static RValue builtin_json_encode(VMContext* ctx, RValue* args, int32_t argCount // Recursively decode a JSON value into a GML value static RValue jsonDecodeValue(VMContext* ctx, JsonValue* json) { - if (json == NULL) return RValue_makeUndefined(); + if (json == nullptr) return RValue_makeUndefined(); switch (json->type) { case JSON_NULL: @@ -14683,7 +14683,7 @@ static RValue jsonDecodeValue(VMContext* ctx, JsonValue* json) { // For arrays, create a ds_list (matches HTML5 - _json_decode_array) int32_t listId = dsListCreate(ctx->runner); DsList* list = dsListGet(ctx->runner, listId); - if (list != NULL) { + if (list != nullptr) { int len = JsonReader_arrayLength(json); for (int i = 0; i < len; i++) { JsonValue* item = JsonReader_getArrayElement(json, i); @@ -14697,7 +14697,7 @@ static RValue jsonDecodeValue(VMContext* ctx, JsonValue* json) { // For arrays, create a ds_map (matches HTML5 - _json_decode_object) int32_t mapId = dsMapCreate(ctx->runner); DsMapEntry** mapPtr = dsMapGet(ctx->runner, mapId); - if (mapPtr != NULL) { + if (mapPtr != nullptr) { int len = JsonReader_objectLength(json); for (int i = 0; i < len; i++) { const char* key = JsonReader_getJsonKeyByIndex(json, i); @@ -14733,7 +14733,7 @@ static RValue builtin_json_decode(VMContext* ctx, RValue* args, int32_t argCount if (json == nullptr) { int32_t mapIndex = dsMapCreate(runner); DsMapEntry** mapPtr = dsMapGet(runner, mapIndex); - if (mapPtr != NULL) { + if (mapPtr != nullptr) { shput(*mapPtr, safeStrdup("default"), RValue_makeIndependent(args[0])); } return RValue_makeReal((GMLReal)mapIndex);