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
15 changes: 15 additions & 0 deletions src/rvalue.h
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,21 @@ static inline int32_t RValue_toInt32(RValue val) {
}
}

// A GML colour lives in an RValue as a double, and it is quite happily 32-bit wide: DELTARUNE
// Chapter 5 stores 4294967295 (0xFFFFFFFF) in the platforming floor's tint. Casting that through
// a signed int32 saturates to INT32_MIN (0x80000000), whose three colour bytes are all zero -- the
// white wall behind the floor came out black, and merge_color(c_white, 4294967295, 1) returned 0.
// Take the bits, not the sign: values inside the int32 range behave exactly as before, and negative
// ones keep their bit pattern.
static inline uint32_t RValue_toColour(RValue val) {
if (val.type == RVALUE_REAL) {
double d = (double) val.real;
if (d >= 0.0) return (d >= 4294967295.0) ? 0xFFFFFFFFu : (uint32_t) d;
return (uint32_t) (int32_t) RValue_toInt32(val);
}
return (uint32_t) RValue_toInt32(val);
}

static inline int64_t RValue_toInt64(RValue val) {
switch (val.type) {
case RVALUE_REAL: return (int64_t) val.real;
Expand Down
9 changes: 6 additions & 3 deletions src/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,12 @@ static inline int32_t Color_lerp(int32_t color1, int32_t color2, float blending)
int32_t r1 = BGR_R(color1), g1 = BGR_G(color1), b1 = BGR_B(color1);
int32_t r2 = BGR_R(color2), g2 = BGR_G(color2), b2 = BGR_B(color2);
float inv = 1.0f - blending;
int32_t r = (int32_t)((float) r2 * blending + (float) r1 * inv) & 0xFF;
int32_t g = (int32_t)((float) g2 * blending + (float) g1 * inv) & 0xFF;
int32_t b = (int32_t)((float) b2 * blending + (float) b1 * inv) & 0xFF;
// Rounded, not truncated: merge_color(c_black, c_white, 0.5) is 0x808080 in GameMaker, while
// truncation gave 0x7F7F7F -- every half-and-half blend came out one step dark per channel.
// (Not to be confused with vertex alpha in floatToUnormByte, where the runtime does truncate.)
int32_t r = (int32_t)((float) r2 * blending + (float) r1 * inv + 0.5f) & 0xFF;
int32_t g = (int32_t)((float) g2 * blending + (float) g1 * inv + 0.5f) & 0xFF;
int32_t b = (int32_t)((float) b2 * blending + (float) b1 * inv + 0.5f) & 0xFF;
return r | (g << 8) | (b << 16);
}

Expand Down
34 changes: 34 additions & 0 deletions src/vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,28 @@ static void handlePush(VMContext* ctx, uint32_t instr, const uint8_t* extraData,
scope = resolveInstanceStackTop(ctx);
}

// Built-in variables do NOT live in selfVars: argument[] is the call frame, not a
// member. Falling through would call getOrInsertUndefined, find nothing, materialise
// a FRESH EMPTY array in that slot and drill into it -- so argument[0][i] came back
// undefined while argument[0] (which goes through resolveVariableRead) was correct.
// Read the real value and hand out a weak ref to it instead.
if (varDef->builtinVarId >= 0) {
Instance* builtinInst = (scope == INSTANCE_OTHER && ctx->otherInstance != nullptr)
? (Instance*) ctx->otherInstance
: (scope == INSTANCE_GLOBAL) ? ctx->globalScopeInstance
: (scope >= 0) ? VM_findInstanceByTarget(ctx, scope)
: (Instance*) ctx->currentInstance;
RValue builtinVal = VMBuiltins_getVariable(ctx, builtinInst, varDef->builtinVarId, varDef->name, firstIndex);
// Only a borrowed array can become a weak ref: freeing an owned one here would
// leave the stack pointing at released memory. Anything else keeps the old path,
// so this cannot turn a working case into an abort.
if (builtinVal.type == RVALUE_ARRAY && builtinVal.array != nullptr && !builtinVal.ownsReference) {
stackPush(ctx, RValue_makeArrayWeak(builtinVal.array));
break;
}
RValue_free(&builtinVal);
}

// Resolve the slot for this scope.
RValue* slot = nullptr;
switch (scope) {
Expand Down Expand Up @@ -1276,6 +1298,18 @@ static void handlePushBltn(VMContext* ctx, uint32_t instr, const uint8_t* extraD
logError("VM: PushBltn ARRAYPUSHAF: no instance for scope %d varID=%d\n", scope, varDef->varID);
abort();
}
// Same trap as in handlePush: a built-in has no selfVars slot, and materialising one there
// would replace the real value with an empty array. PushBltn only ever names built-ins, but
// the index of the first step is NOT on the stack here -- this opcode form carries no array
// index, so read the variable whole (arrayIndex -1) and hand out a weak ref to it.
if (varDef->builtinVarId >= 0) {
RValue builtinVal = VMBuiltins_getVariable(ctx, inst, varDef->builtinVarId, varDef->name, -1);
if (builtinVal.type == RVALUE_ARRAY && builtinVal.array != nullptr && !builtinVal.ownsReference) {
stackPush(ctx, RValue_makeArrayWeak(builtinVal.array));
return;
}
RValue_free(&builtinVal);
}
RValue* slot = IntRValueHashMap_getOrInsertUndefined(&inst->selfVars, varDef->varID);
pushTopLevelArrayRef(ctx, slot, varType == VARTYPE_ARRAYPOPAF);
return;
Expand Down
Loading