Summary
ini_write_string() silently persists an empty string whenever the third argument is not already an RVALUE_STRING. Native GameMaker converts any value to a string, so GML like ini_write_string("S", "K", 123) must persist K="123", but Butterscotch writes K="".
Environment
- Butterscotch built from
main (relevant code: src/vm_builtins.c)
- Android frontend
- Game: DELTARUNE (GMS 2.3 / WAD 17 bytecode) with the gm3dr/DeltaruneChinese community patch installed
Root cause
src/vm_builtins.c, builtin_ini_write_string:
const char* value = (args[2].type == RVALUE_STRING ? args[2].string : "");
Any non-string third argument is silently discarded and replaced with "". The adjacent builtin_ini_write_real already does the right thing:
char* valueStr = RValue_toString(args[2]);
Minimal repro
ini_open("test.ini");
ini_write_string("TEST", "VALUE", 123);
ini_close();
Resulting file on disk (wrong):
Native GameMaker writes VALUE="123".
Real-world failure this caused
Deltarune's Chinese localization patch implements the in-game name translation toggle like this:
function scr_change_language() {
global.names = (global.names + 1) % 3; // number: 0/1/2
ossafe_ini_open("true_config.ini");
ini_write_string("L10N_ZH", "NAMES", global.names); // numeric value
ossafe_ini_close();
ossafe_savedata_save();
scr_84_init_localization(); // immediately reads ini_read_real("L10N_ZH", "NAMES", 0)
}
Instrumentation confirmed the whole chain fires on Android: virtual Z key down -> keyboard_check(90)=true -> input_pressed[4]=1 -> button1_p()=1 -> scr_change_language() runs, and true_config.ini is rewritten at that exact moment - but with:
scr_84_init_localization() then reads "" back as real 0, so the toggle instantly reverts and the option appears to do nothing.
Suggested fix
static RValue builtin_ini_write_string(VMContext* ctx, RValue* args, int32_t argCount) {
Runner* runner = ctx->runner;
if (3 > argCount || runner->currentIni == nullptr) return RValue_makeUndefined();
const char* section = (args[0].type == RVALUE_STRING ? args[0].string : "");
const char* key = (args[1].type == RVALUE_STRING ? args[1].string : "");
char* value = RValue_toString(args[2]);
Ini_setString(runner->currentIni, section, key, value);
runner->currentIniDirty = true;
free(value);
return RValue_makeUndefined();
}
Happy to open a PR with this fix if that helps.
Summary
ini_write_string()silently persists an empty string whenever the third argument is not already anRVALUE_STRING. Native GameMaker converts any value to a string, so GML likeini_write_string("S", "K", 123)must persistK="123", but Butterscotch writesK="".Environment
main(relevant code:src/vm_builtins.c)Root cause
src/vm_builtins.c,builtin_ini_write_string:Any non-string third argument is silently discarded and replaced with
"". The adjacentbuiltin_ini_write_realalready does the right thing:Minimal repro
Resulting file on disk (wrong):
Native GameMaker writes
VALUE="123".Real-world failure this caused
Deltarune's Chinese localization patch implements the in-game name translation toggle like this:
Instrumentation confirmed the whole chain fires on Android: virtual Z key down ->
keyboard_check(90)=true->input_pressed[4]=1->button1_p()=1->scr_change_language()runs, andtrue_config.iniis rewritten at that exact moment - but with:scr_84_init_localization()then reads""back as real0, so the toggle instantly reverts and the option appears to do nothing.Suggested fix
Happy to open a PR with this fix if that helps.