From f37d0687337e31dcfb96974810a572acba571416 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 3 Feb 2023 02:20:47 -0800 Subject: [PATCH 01/47] GBA SIO: Minor code modernization --- src/gba/sio.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gba/sio.c b/src/gba/sio.c index d4df7e8da..9c47335dc 100644 --- a/src/gba/sio.c +++ b/src/gba/sio.c @@ -184,13 +184,13 @@ void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value) { switch (sio->mode) { case SIO_NORMAL_8: case SIO_NORMAL_32: - value |= 0x0004; + value = GBASIONormalFillSi(value); if ((value & 0x0081) == 0x0081) { - if (value & 0x4000) { + if (GBASIONormalIsIrq(value)) { // TODO: Test this on hardware to see if this is correct GBARaiseIRQ(sio->p, GBA_IRQ_SIO, 0); } - value &= ~0x0080; + value = GBASIONormalClearStart(value); } break; case SIO_MULTI: From 5164b888d895f2f91bb40290f6b2ef0e35b89571 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 31 Jan 2023 17:22:29 -0800 Subject: [PATCH 02/47] Scripting: Allow Lua to pass nested tables to the scripting subsystem --- include/mgba/script/types.h | 3 ++- src/script/engines/lua.c | 25 +++++++++++++++---- src/script/test/lua.c | 50 ++++++++++++++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index ed8ae02e3..0be1c41e3 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -35,7 +35,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_C_PTR void* #define mSCRIPT_TYPE_C_CPTR const void* #define mSCRIPT_TYPE_C_LIST struct mScriptList* -#define mSCRIPT_TYPE_C_TABLE Table* +#define mSCRIPT_TYPE_C_TABLE struct Table* #define mSCRIPT_TYPE_C_WRAPPER struct mScriptValue* #define mSCRIPT_TYPE_C_WEAKREF uint32_t #define mSCRIPT_TYPE_C_S(STRUCT) struct STRUCT* @@ -120,6 +120,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_CMP_STR(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_STR, TYPE) #define mSCRIPT_TYPE_CMP_CHARP(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_CHARP, TYPE) #define mSCRIPT_TYPE_CMP_LIST(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_LIST, TYPE) +#define mSCRIPT_TYPE_CMP_TABLE(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_TABLE, TYPE) #define mSCRIPT_TYPE_CMP_PTR(TYPE) ((TYPE)->base >= mSCRIPT_TYPE_OPAQUE) #define mSCRIPT_TYPE_CMP_WRAPPER(TYPE) (true) #define mSCRIPT_TYPE_CMP_S(STRUCT) mSCRIPT_TYPE_MS_S(STRUCT)->name == _mSCRIPT_FIELD_NAME diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index bac841f1b..95ac70eba 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -538,11 +538,13 @@ struct mScriptValue* _luaCoerceFunction(struct mScriptEngineContextLua* luaConte return value; } -struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext) { +struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext, struct Table* markedObjects) { struct mScriptValue* table = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); bool isList = true; lua_pushnil(luaContext->lua); + + void* tablePointer; while (lua_next(luaContext->lua, -2) != 0) { struct mScriptValue* value = NULL; int type = lua_type(luaContext->lua, -1); @@ -553,14 +555,23 @@ struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext) case LUA_TFUNCTION: value = _luaCoerce(luaContext, true); break; + case LUA_TTABLE: + tablePointer = lua_topointer(luaContext->lua, -1); + // Ensure this table doesn't contain any cycles + if (!HashTableLookupBinary(markedObjects, &tablePointer, sizeof(tablePointer))) { + HashTableInsertBinary(markedObjects, &tablePointer, sizeof(tablePointer), tablePointer); + value = _luaCoerceTable(luaContext, markedObjects); + if (value) { + mScriptValueRef(value); + } + } default: - // Don't let values be something that could contain themselves break; } if (!value) { - lua_pop(luaContext->lua, 3); + lua_pop(luaContext->lua, type == LUA_TTABLE ? 2 : 3); mScriptValueDeref(table); - return false; + return NULL; } struct mScriptValue* key = NULL; @@ -628,6 +639,7 @@ struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext, bool size_t size; const void* buffer; + struct Table markedObjects; struct mScriptValue* value = NULL; switch (lua_type(luaContext->lua, -1)) { case LUA_TNIL: @@ -664,7 +676,10 @@ struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext, bool if (!pop) { break; } - return _luaCoerceTable(luaContext); + HashTableInit(&markedObjects, 0, NULL); + value = _luaCoerceTable(luaContext, &markedObjects); + HashTableDeinit(&markedObjects); + return value; case LUA_TUSERDATA: if (!lua_getmetatable(luaContext->lua, -1)) { break; diff --git a/src/script/test/lua.c b/src/script/test/lua.c index 5731b0705..eab8550dc 100644 --- a/src/script/test/lua.c +++ b/src/script/test/lua.c @@ -66,9 +66,14 @@ static int32_t sum(struct mScriptList* list) { return sum; } +static unsigned tableSize(struct Table* table) { + return TableSize(table); +} + mSCRIPT_BIND_FUNCTION(boundIdentityInt, S32, identityInt, 1, S32, a); mSCRIPT_BIND_FUNCTION(boundAddInts, S32, addInts, 2, S32, a, S32, b); mSCRIPT_BIND_FUNCTION(boundSum, S32, sum, 1, LIST, list); +mSCRIPT_BIND_FUNCTION(boundTableSize, U32, tableSize, 1, TABLE, table); mSCRIPT_DECLARE_STRUCT(Test); mSCRIPT_DECLARE_STRUCT_D_METHOD(Test, S32, ifn0, 0); @@ -371,8 +376,50 @@ M_TEST_DEFINE(callCFunc) { assert_true(a.type->equal(&a, val)); mScriptValueDeref(val); + LOAD_PROGRAM("b('a')"); + assert_false(lua->run(lua)); + mScriptContextDeinit(&context); } + +M_TEST_DEFINE(callCTable) { + SETUP_LUA; + + struct mScriptValue a = mSCRIPT_MAKE_S32(1); + struct mScriptValue* val; + + assert_true(lua->setGlobal(lua, "b", &boundTableSize)); + + TEST_PROGRAM("assert(b({}) == 0)"); + assert_null(lua->getError(lua)); + + TEST_PROGRAM("assert(b({[2]=1}) == 1)"); + assert_null(lua->getError(lua)); + + TEST_PROGRAM("assert(b({a=1}) == 1)"); + assert_null(lua->getError(lua)); + + TEST_PROGRAM("assert(b({a={}}) == 1)"); + assert_null(lua->getError(lua)); + + LOAD_PROGRAM( + "a = {}\n" + "a.b = a\n" + "assert(b(a) == 1)\n" + ); + assert_false(lua->run(lua)); + + LOAD_PROGRAM( + "a = {}\n" + "a.b = {}\n" + "a.b.c = a\n" + "assert(b(a) == 1)\n" + ); + assert_false(lua->run(lua)); + + mScriptContextDeinit(&context); +} + M_TEST_DEFINE(globalNull) { SETUP_LUA; @@ -774,6 +821,7 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua, cmocka_unit_test(rootScope), cmocka_unit_test(callLuaFunc), cmocka_unit_test(callCFunc), + cmocka_unit_test(callCTable), cmocka_unit_test(globalNull), cmocka_unit_test(globalStructFieldGet), cmocka_unit_test(globalStructFieldSet), @@ -782,5 +830,5 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua, cmocka_unit_test(tableLookup), cmocka_unit_test(tableIterate), cmocka_unit_test(callList), - cmocka_unit_test(linkedList) + cmocka_unit_test(linkedList), ) From 0193bc3a837a564ece40ea7eab2f0cf30a4d33f3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Feb 2023 18:14:37 -0800 Subject: [PATCH 03/47] Scripting: Fix table unwrapping --- src/script/engines/lua.c | 17 +++++++++++++---- src/script/types.c | 7 ++----- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index 95ac70eba..1e09bcde1 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -1202,8 +1202,11 @@ int _luaGetTable(lua_State* lua) { } lua_pop(lua, 2); - obj = mScriptContextAccessWeakref(luaContext->d.context, obj); - if (!obj) { + obj = mScriptContextAccessWeakref(luaContext->d.context, obj); + if (obj->type->base == mSCRIPT_TYPE_WRAPPER) { + obj = mScriptValueUnwrap(obj); + } + if (!obj || obj->type != mSCRIPT_TYPE_MS_TABLE) { luaL_traceback(lua, lua, "Invalid table", 1); return lua_error(lua); } @@ -1235,7 +1238,10 @@ int _luaLenTable(lua_State* lua) { lua_pop(lua, 1); obj = mScriptContextAccessWeakref(luaContext->d.context, obj); - if (!obj) { + if (obj->type->base == mSCRIPT_TYPE_WRAPPER) { + obj = mScriptValueUnwrap(obj); + } + if (!obj || obj->type != mSCRIPT_TYPE_MS_TABLE) { luaL_traceback(lua, lua, "Invalid table", 1); return lua_error(lua); } @@ -1271,7 +1277,10 @@ static int _luaNextTable(lua_State* lua) { lua_pop(lua, 2); table = mScriptContextAccessWeakref(luaContext->d.context, table); - if (!table) { + if (table->type->base == mSCRIPT_TYPE_WRAPPER) { + table = mScriptValueUnwrap(table); + } + if (!table || table->type != mSCRIPT_TYPE_MS_TABLE) { luaL_traceback(lua, lua, "Invalid table", 1); return lua_error(lua); } diff --git a/src/script/types.c b/src/script/types.c index 0237cd4cb..3f04647a7 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -944,14 +944,11 @@ bool mScriptTableRemove(struct mScriptValue* table, struct mScriptValue* key) { } struct mScriptValue* mScriptTableLookup(struct mScriptValue* table, struct mScriptValue* key) { - if (table->type->base == mSCRIPT_TYPE_WRAPPER) { - table = mScriptValueUnwrap(table); - } if (table->type != mSCRIPT_TYPE_MS_TABLE) { - return false; + return NULL; } if (!key->type->hash) { - return false; + return NULL; } return HashTableLookupCustom(table->value.table, key); } From f2e9ea6a6b2abae476df2d5a9ecf41599ec10841 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Feb 2023 19:07:22 -0800 Subject: [PATCH 04/47] Scripting: Remove unused type macros --- include/mgba/script/types.h | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index 0be1c41e3..c9e3bed42 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -40,7 +40,6 @@ CXX_GUARD_START #define mSCRIPT_TYPE_C_WEAKREF uint32_t #define mSCRIPT_TYPE_C_S(STRUCT) struct STRUCT* #define mSCRIPT_TYPE_C_CS(STRUCT) const struct STRUCT* -#define mSCRIPT_TYPE_C_S_METHOD(STRUCT, NAME) _mSTStructFunctionType_ ## STRUCT ## _ ## NAME #define mSCRIPT_TYPE_C_PS(X) void #define mSCRIPT_TYPE_C_PCS(X) void #define mSCRIPT_TYPE_C_WSTR struct mScriptValue* @@ -68,7 +67,6 @@ CXX_GUARD_START #define mSCRIPT_TYPE_FIELD_WEAKREF u32 #define mSCRIPT_TYPE_FIELD_S(STRUCT) opaque #define mSCRIPT_TYPE_FIELD_CS(STRUCT) copaque -#define mSCRIPT_TYPE_FIELD_S_METHOD(STRUCT, NAME) copaque #define mSCRIPT_TYPE_FIELD_PS(STRUCT) opaque #define mSCRIPT_TYPE_FIELD_PCS(STRUCT) copaque #define mSCRIPT_TYPE_FIELD_WSTR opaque @@ -96,7 +94,6 @@ CXX_GUARD_START #define mSCRIPT_TYPE_MS_WEAKREF (&mSTWeakref) #define mSCRIPT_TYPE_MS_S(STRUCT) (&mSTStruct_ ## STRUCT) #define mSCRIPT_TYPE_MS_CS(STRUCT) (&mSTStructConst_ ## STRUCT) -#define mSCRIPT_TYPE_MS_S_METHOD(STRUCT, NAME) (&_mSTStructBindingType_ ## STRUCT ## _ ## NAME) #define mSCRIPT_TYPE_MS_PS(STRUCT) (&mSTStructPtr_ ## STRUCT) #define mSCRIPT_TYPE_MS_PCS(STRUCT) (&mSTStructPtrConst_ ## STRUCT) #define mSCRIPT_TYPE_MS_WSTR (&mSTStringWrapper) @@ -125,10 +122,9 @@ CXX_GUARD_START #define mSCRIPT_TYPE_CMP_WRAPPER(TYPE) (true) #define mSCRIPT_TYPE_CMP_S(STRUCT) mSCRIPT_TYPE_MS_S(STRUCT)->name == _mSCRIPT_FIELD_NAME #define mSCRIPT_TYPE_CMP_CS(STRUCT) mSCRIPT_TYPE_MS_CS(STRUCT)->name == _mSCRIPT_FIELD_NAME -#define mSCRIPT_TYPE_CMP_S_METHOD(STRUCT, NAME) mSCRIPT_TYPE_MS_S_METHOD(STRUCT, NAME)->name == _mSCRIPT_FIELD_NAME -#define mSCRIPT_TYPE_CMP(TYPE0, TYPE1) mSCRIPT_TYPE_CMP_ ## TYPE0(TYPE1) #define mSCRIPT_TYPE_CMP_WSTR(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_STR, TYPE) || mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_CHARP, TYPE)) #define mSCRIPT_TYPE_CMP_WLIST(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_LIST, TYPE)) +#define mSCRIPT_TYPE_CMP(TYPE0, TYPE1) mSCRIPT_TYPE_CMP_ ## TYPE0(TYPE1) enum mScriptTypeBase { mSCRIPT_TYPE_VOID = 0, From c2bcf0df0758c4cff59f051b0bbfe255b3b72937 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Feb 2023 18:15:03 -0800 Subject: [PATCH 05/47] Scripting: Fix object get thunking --- src/script/types.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/script/types.c b/src/script/types.c index 3f04647a7..c2b367210 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -1262,7 +1262,7 @@ bool mScriptObjectGet(struct mScriptValue* obj, const char* member, struct mScri this->type = obj->type; this->refs = mSCRIPT_VALUE_UNREF; this->flags = 0; - this->value.opaque = obj; + this->value.opaque = obj->value.opaque; mSCRIPT_PUSH(&frame.arguments, CHARP, member); if (!mScriptInvoke(&getMember, &frame) || mScriptListSize(&frame.returnValues) != 1) { mScriptFrameDeinit(&frame); From 39e3b5181a0290735a639a528a79a72dc9c980a4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 3 Feb 2023 23:39:12 -0800 Subject: [PATCH 06/47] Scripting: Add WTABLE --- include/mgba/script/types.h | 5 +++++ src/script/types.c | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index c9e3bed42..9cc2e94b1 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -44,6 +44,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_C_PCS(X) void #define mSCRIPT_TYPE_C_WSTR struct mScriptValue* #define mSCRIPT_TYPE_C_WLIST struct mScriptValue* +#define mSCRIPT_TYPE_C_WTABLE struct mScriptValue* #define mSCRIPT_TYPE_C_W(X) struct mScriptValue* #define mSCRIPT_TYPE_C_CW(X) const struct mScriptValue* @@ -71,6 +72,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_FIELD_PCS(STRUCT) copaque #define mSCRIPT_TYPE_FIELD_WSTR opaque #define mSCRIPT_TYPE_FIELD_WLIST opaque +#define mSCRIPT_TYPE_FIELD_WTABLE opaque #define mSCRIPT_TYPE_FIELD_W(TYPE) opaque #define mSCRIPT_TYPE_FIELD_CW(TYPE) opaque @@ -98,6 +100,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_MS_PCS(STRUCT) (&mSTStructPtrConst_ ## STRUCT) #define mSCRIPT_TYPE_MS_WSTR (&mSTStringWrapper) #define mSCRIPT_TYPE_MS_WLIST (&mSTListWrapper) +#define mSCRIPT_TYPE_MS_WTABLE (&mSTTableWrapper) #define mSCRIPT_TYPE_MS_W(TYPE) (&mSTWrapper_ ## TYPE) #define mSCRIPT_TYPE_MS_CW(TYPE) (&mSTWrapperConst_ ## TYPE) #define mSCRIPT_TYPE_MS_DS(STRUCT) (&mSTStruct_doc_ ## STRUCT) @@ -124,6 +127,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_CMP_CS(STRUCT) mSCRIPT_TYPE_MS_CS(STRUCT)->name == _mSCRIPT_FIELD_NAME #define mSCRIPT_TYPE_CMP_WSTR(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_STR, TYPE) || mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_CHARP, TYPE)) #define mSCRIPT_TYPE_CMP_WLIST(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_LIST, TYPE)) +#define mSCRIPT_TYPE_CMP_WTABLE(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_TABLE, TYPE)) #define mSCRIPT_TYPE_CMP(TYPE0, TYPE1) mSCRIPT_TYPE_CMP_ ## TYPE0(TYPE1) enum mScriptTypeBase { @@ -180,6 +184,7 @@ extern const struct mScriptType mSTWrapper; extern const struct mScriptType mSTWeakref; extern const struct mScriptType mSTStringWrapper; extern const struct mScriptType mSTListWrapper; +extern const struct mScriptType mSTTableWrapper; extern struct mScriptValue mScriptValueNull; diff --git a/src/script/types.c b/src/script/types.c index c2b367210..601d57339 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -243,6 +243,15 @@ const struct mScriptType mSTListWrapper = { .hash = NULL, }; +const struct mScriptType mSTTableWrapper = { + .base = mSCRIPT_TYPE_WRAPPER, + .size = sizeof(struct mScriptValue), + .name = "wrapper table", + .alloc = NULL, + .free = NULL, + .hash = NULL, +}; + const struct mScriptType mSTWeakref = { .base = mSCRIPT_TYPE_WEAKREF, .size = sizeof(uint32_t), From bfab9dc9f20a200f8a1065b0946a51b831a409bd Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 4 Feb 2023 01:12:04 -0800 Subject: [PATCH 07/47] Scripting: Specific wrapper types shouldn't compare equal with wrapped type --- include/mgba/script/types.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index 9cc2e94b1..17b3da70e 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -125,9 +125,9 @@ CXX_GUARD_START #define mSCRIPT_TYPE_CMP_WRAPPER(TYPE) (true) #define mSCRIPT_TYPE_CMP_S(STRUCT) mSCRIPT_TYPE_MS_S(STRUCT)->name == _mSCRIPT_FIELD_NAME #define mSCRIPT_TYPE_CMP_CS(STRUCT) mSCRIPT_TYPE_MS_CS(STRUCT)->name == _mSCRIPT_FIELD_NAME -#define mSCRIPT_TYPE_CMP_WSTR(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_STR, TYPE) || mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_CHARP, TYPE)) -#define mSCRIPT_TYPE_CMP_WLIST(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_LIST, TYPE)) -#define mSCRIPT_TYPE_CMP_WTABLE(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_TABLE, TYPE)) +#define mSCRIPT_TYPE_CMP_WSTR(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_WSTR, TYPE)) +#define mSCRIPT_TYPE_CMP_WLIST(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_WLIST, TYPE)) +#define mSCRIPT_TYPE_CMP_WTABLE(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_WTABLE, TYPE)) #define mSCRIPT_TYPE_CMP(TYPE0, TYPE1) mSCRIPT_TYPE_CMP_ ## TYPE0(TYPE1) enum mScriptTypeBase { From f74db92ccd24aa4b1fefb5d9fff41c8c4f20bae0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 4 Feb 2023 01:13:16 -0800 Subject: [PATCH 08/47] Scripting: Add wrapper drill-down casts --- src/script/types.c | 48 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/src/script/types.c b/src/script/types.c index 601d57339..4ee851812 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -27,6 +27,10 @@ static bool _stringCast(const struct mScriptValue*, const struct mScriptType*, s static bool _castScalar(const struct mScriptValue*, const struct mScriptType*, struct mScriptValue*); static uint32_t _hashScalar(const struct mScriptValue*); +static bool _wstrCast(const struct mScriptValue*, const struct mScriptType*, struct mScriptValue*); +static bool _wlistCast(const struct mScriptValue*, const struct mScriptType*, struct mScriptValue*); +static bool _wtableCast(const struct mScriptValue*, const struct mScriptType*, struct mScriptValue*); + static uint32_t _valHash(const void* val, size_t len, uint32_t seed); static bool _valEqual(const void* a, const void* b); static void* _valRef(void*); @@ -232,6 +236,7 @@ const struct mScriptType mSTStringWrapper = { .alloc = NULL, .free = NULL, .hash = NULL, + .cast = _wstrCast, }; const struct mScriptType mSTListWrapper = { @@ -241,6 +246,7 @@ const struct mScriptType mSTListWrapper = { .alloc = NULL, .free = NULL, .hash = NULL, + .cast = _wlistCast, }; const struct mScriptType mSTTableWrapper = { @@ -250,6 +256,7 @@ const struct mScriptType mSTTableWrapper = { .alloc = NULL, .free = NULL, .hash = NULL, + .cast = _wtableCast, }; const struct mScriptType mSTWeakref = { @@ -375,6 +382,45 @@ uint32_t _hashScalar(const struct mScriptValue* val) { return x; } +bool _wstrCast(const struct mScriptValue* input, const struct mScriptType* type, struct mScriptValue* output) { + if (input->type->base != mSCRIPT_TYPE_WRAPPER) { + return false; + } + const struct mScriptValue* unwrapped = mScriptValueUnwrapConst(input); + if (unwrapped->type != mSCRIPT_TYPE_MS_STR) { + return false; + } + memcpy(output, input, sizeof(*output)); + output->type = type; + return true; +} + +bool _wlistCast(const struct mScriptValue* input, const struct mScriptType* type, struct mScriptValue* output) { + if (input->type->base != mSCRIPT_TYPE_WRAPPER) { + return false; + } + const struct mScriptValue* unwrapped = mScriptValueUnwrapConst(input); + if (unwrapped->type != mSCRIPT_TYPE_MS_LIST) { + return false; + } + memcpy(output, input, sizeof(*output)); + output->type = type; + return true; +} + +bool _wtableCast(const struct mScriptValue* input, const struct mScriptType* type, struct mScriptValue* output) { + if (input->type->base != mSCRIPT_TYPE_WRAPPER) { + return false; + } + const struct mScriptValue* unwrapped = mScriptValueUnwrapConst(input); + if (unwrapped->type != mSCRIPT_TYPE_MS_TABLE) { + return false; + } + memcpy(output, input, sizeof(*output)); + output->type = type; + return true; +} + #define AS(NAME, TYPE) \ bool _as ## NAME(const struct mScriptValue* input, mSCRIPT_TYPE_C_ ## TYPE * T) { \ switch (input->type->base) { \ @@ -1484,7 +1530,7 @@ bool mScriptPopPointer(struct mScriptList* list, void** out) { } bool mScriptCast(const struct mScriptType* type, const struct mScriptValue* input, struct mScriptValue* output) { - if (input->type->base == mSCRIPT_TYPE_WRAPPER) { + if (input->type->base == mSCRIPT_TYPE_WRAPPER && type->base != mSCRIPT_TYPE_WRAPPER) { input = mScriptValueUnwrapConst(input); } if (type->cast && type->cast(input, type, output)) { From 004f68496f5071d68517042019234cf21403cc74 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 3 Feb 2023 23:08:07 -0800 Subject: [PATCH 09/47] Scripting: Add type-overloadable setters --- include/mgba/script/macros.h | 2 +- include/mgba/script/types.h | 2 +- src/script/test/classes.c | 103 ++++++++++++++++++++++++++++ src/script/types.c | 129 +++++++++++++++++++++++++++++++++-- 4 files changed, 229 insertions(+), 7 deletions(-) diff --git a/include/mgba/script/macros.h b/include/mgba/script/macros.h index 42710f9e8..bbde17b07 100644 --- a/include/mgba/script/macros.h +++ b/include/mgba/script/macros.h @@ -423,7 +423,7 @@ CXX_GUARD_START #define mSCRIPT_DEFINE_STRUCT_DEINIT(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(DEINIT, TYPE, _deinit, _deinit) #define mSCRIPT_DEFINE_STRUCT_DEINIT_NAMED(TYPE, NAME) _mSCRIPT_DEFINE_STRUCT_BINDING(DEINIT, TYPE, _deinit, NAME) #define mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(GET, TYPE, _get, _get) -#define mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(SET, TYPE, _set, _set) +#define mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TYPE, SETTER) _mSCRIPT_DEFINE_STRUCT_BINDING(SET, TYPE, SETTER, SETTER) #define mSCRIPT_DEFINE_DOC_STRUCT_METHOD(SCOPE, TYPE, NAME) mSCRIPT_DEFINE_STRUCT_METHOD_NAMED(doc_ ## TYPE, NAME, NAME) diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index 17b3da70e..76d5c1579 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -252,10 +252,10 @@ struct mScriptTypeClass { bool internal; struct Table instanceMembers; struct Table castToMembers; + struct Table setters; struct mScriptClassMember* alloc; // TODO struct mScriptClassMember* free; struct mScriptClassMember* get; - struct mScriptClassMember* set; // TODO }; struct mScriptType { diff --git a/src/script/test/classes.c b/src/script/test/classes.c index 85afb627c..f4dec7722 100644 --- a/src/script/test/classes.c +++ b/src/script/test/classes.c @@ -46,6 +46,14 @@ struct TestF { int* ref; }; +struct TestG { + const char* name; + int64_t s; + uint64_t u; + double f; + const char* c; +}; + static int32_t testAi0(struct TestA* a) { return a->i; } @@ -83,6 +91,26 @@ static void testDeinit(struct TestF* f) { ++*f->ref; } +static void testSetS(struct TestG* g, const char* name, int64_t val) { + g->name = name; + g->s = val; +} + +static void testSetU(struct TestG* g, const char* name, uint64_t val) { + g->name = name; + g->u = val; +} + +static void testSetF(struct TestG* g, const char* name, double val) { + g->name = name; + g->f = val; +} + +static void testSetC(struct TestG* g, const char* name, const char* val) { + g->name = name; + g->c = val; +} + #define MEMBER_A_DOCSTRING "Member a" mSCRIPT_DECLARE_STRUCT(TestA); @@ -157,6 +185,19 @@ mSCRIPT_DEFINE_STRUCT(TestF) mSCRIPT_DEFINE_STRUCT_DEINIT(TestF) mSCRIPT_DEFINE_END; +mSCRIPT_DECLARE_STRUCT(TestG); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(TestG, setS, testSetS, 2, CHARP, name, S64, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(TestG, setU, testSetU, 2, CHARP, name, U64, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(TestG, setF, testSetF, 2, CHARP, name, F64, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(TestG, setC, testSetC, 2, CHARP, name, CHARP, value); + +mSCRIPT_DEFINE_STRUCT(TestG) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TestG, setS) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TestG, setU) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TestG, setF) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TestG, setC) +mSCRIPT_DEFINE_END; + M_TEST_DEFINE(testALayout) { struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestA)->details.cls; assert_false(cls->init); @@ -1032,6 +1073,67 @@ M_TEST_DEFINE(testFDeinit) { assert_false(cls->init); } +M_TEST_DEFINE(testGSet) { + struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestG)->details.cls; + + struct TestG s = { + }; + + assert_int_equal(s.s, 0); + assert_int_equal(s.u, 0); + assert_float_equal(s.f, 0, 0); + assert_null(s.c); + + struct mScriptValue sval = mSCRIPT_MAKE_S(TestG, &s); + struct mScriptValue val; + struct mScriptValue* pval; + + val = mSCRIPT_MAKE_S64(1); + assert_true(mScriptObjectSet(&sval, "a", &val)); + assert_int_equal(s.s, 1); + assert_string_equal(s.name, "a"); + + val = mSCRIPT_MAKE_U64(2); + assert_true(mScriptObjectSet(&sval, "b", &val)); + assert_int_equal(s.u, 2); + assert_string_equal(s.name, "b"); + + val = mSCRIPT_MAKE_F64(1.5); + assert_true(mScriptObjectSet(&sval, "c", &val)); + assert_float_equal(s.f, 1.5, 0); + assert_string_equal(s.name, "c"); + + val = mSCRIPT_MAKE_CHARP("hello"); + assert_true(mScriptObjectSet(&sval, "d", &val)); + assert_string_equal(s.c, "hello"); + assert_string_equal(s.name, "d"); + + val = mSCRIPT_MAKE_S32(3); + assert_true(mScriptObjectSet(&sval, "a", &val)); + assert_int_equal(s.s, 3); + + val = mSCRIPT_MAKE_S16(4); + assert_true(mScriptObjectSet(&sval, "a", &val)); + assert_int_equal(s.s, 4); + + val = mSCRIPT_MAKE_S8(5); + assert_true(mScriptObjectSet(&sval, "a", &val)); + assert_int_equal(s.s, 5); + + val = mSCRIPT_MAKE_BOOL(false); + assert_true(mScriptObjectSet(&sval, "a", &val)); + assert_int_equal(s.u, 0); + + pval = mScriptStringCreateFromASCII("goodbye"); + assert_true(mScriptObjectSet(&sval, "a", pval)); + assert_string_equal(s.c, "goodbye"); + mScriptValueDeref(pval); + + assert_true(cls->init); + mScriptClassDeinit(cls); + assert_false(cls->init); +} + M_TEST_SUITE_DEFINE(mScriptClasses, cmocka_unit_test(testALayout), cmocka_unit_test(testASignatures), @@ -1047,4 +1149,5 @@ M_TEST_SUITE_DEFINE(mScriptClasses, cmocka_unit_test(testDSet), cmocka_unit_test(testEGet), cmocka_unit_test(testFDeinit), + cmocka_unit_test(testGSet), ) diff --git a/src/script/types.c b/src/script/types.c index 4ee851812..a05f6d252 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -1149,12 +1149,16 @@ static void _mScriptClassInit(struct mScriptTypeClass* cls, const struct mScript } break; case mSCRIPT_CLASS_INIT_SET: - cls->set = calloc(1, sizeof(*member)); - memcpy(cls->set, &detail->info.member, sizeof(*member)); + member = calloc(1, sizeof(*member)); + memcpy(member, &detail->info.member, sizeof(*member)); if (docstring) { - cls->set->docstring = docstring; + member->docstring = docstring; docstring = NULL; } + if (detail->info.member.type->details.function.parameters.count != 3) { + abort(); + } + HashTableInsert(&cls->setters, detail->info.member.type->details.function.parameters.entries[2]->name, member); break; case mSCRIPT_CLASS_INIT_INTERNAL: cls->internal = true; @@ -1169,11 +1173,11 @@ void mScriptClassInit(struct mScriptTypeClass* cls) { } HashTableInit(&cls->instanceMembers, 0, free); HashTableInit(&cls->castToMembers, 0, NULL); + HashTableInit(&cls->setters, 0, free); cls->alloc = NULL; cls->free = NULL; cls->get = NULL; - cls->set = NULL; _mScriptClassInit(cls, cls->details, false); cls->init = true; @@ -1185,6 +1189,7 @@ void mScriptClassDeinit(struct mScriptTypeClass* cls) { } HashTableDeinit(&cls->instanceMembers); HashTableDeinit(&cls->castToMembers); + HashTableDeinit(&cls->setters); cls->init = false; } @@ -1352,6 +1357,91 @@ bool mScriptObjectGetConst(const struct mScriptValue* obj, const char* member, s return _accessRawMember(m, obj->value.opaque, true, val); } +static struct mScriptClassMember* _findSetter(const struct mScriptTypeClass* cls, const struct mScriptType* type) { + struct mScriptClassMember* m = HashTableLookup(&cls->setters, type->name); + if (m) { + return m; + } + + switch (type->base) { + case mSCRIPT_TYPE_SINT: + if (type->size < 2) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_S16->name); + if (m) { + return m; + } + } + if (type->size < 4) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_S32->name); + if (m) { + return m; + } + } + if (type->size < 8) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_S64->name); + if (m) { + return m; + } + } + break; + case mSCRIPT_TYPE_UINT: + if (type->size < 2) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_U16->name); + if (m) { + return m; + } + } + if (type->size < 4) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_U32->name); + if (m) { + return m; + } + } + if (type->size < 8) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_U64->name); + if (m) { + return m; + } + } + break; + case mSCRIPT_TYPE_FLOAT: + if (type->size < 8) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_F64->name); + if (m) { + return m; + } + } + break; + case mSCRIPT_TYPE_STRING: + if (type == mSCRIPT_TYPE_MS_STR) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_CHARP->name); + if (m) { + return m; + } + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_WSTR->name); + if (m) { + return m; + } + } + break; + case mSCRIPT_TYPE_LIST: + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_WLIST->name); + if (m) { + return m; + } + break; + case mSCRIPT_TYPE_TABLE: + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_WTABLE->name); + if (m) { + return m; + } + break; + default: + break; + } + return NULL; +} + bool mScriptObjectSet(struct mScriptValue* obj, const char* member, struct mScriptValue* val) { if (obj->type->base != mSCRIPT_TYPE_OBJECT || obj->type->isConst) { return false; @@ -1366,7 +1456,36 @@ bool mScriptObjectSet(struct mScriptValue* obj, const char* member, struct mScri struct mScriptClassMember* m = HashTableLookup(&cls->instanceMembers, member); if (!m) { - return false; + if (val->type->base == mSCRIPT_TYPE_WRAPPER) { + val = mScriptValueUnwrap(val); + } + struct mScriptValue setMember; + m = _findSetter(cls, val->type); + if (!m || !_accessRawMember(m, obj->value.opaque, obj->type->isConst, &setMember)) { + return false; + } + struct mScriptFrame frame; + mScriptFrameInit(&frame); + struct mScriptValue* this = mScriptListAppend(&frame.arguments); + this->type = obj->type; + this->refs = mSCRIPT_VALUE_UNREF; + this->flags = 0; + this->value.opaque = obj->value.opaque; + mSCRIPT_PUSH(&frame.arguments, CHARP, member); + mScriptValueWrap(val, mScriptListAppend(&frame.arguments)); + bool needsDeref = mScriptListGetPointer(&frame.arguments, 2)->type->base == mSCRIPT_TYPE_WRAPPER; + if (!mScriptInvoke(&setMember, &frame) || mScriptListSize(&frame.returnValues) != 0) { + mScriptFrameDeinit(&frame); + if (needsDeref) { + mScriptValueDeref(val); + } + return false; + } + mScriptFrameDeinit(&frame); + if (needsDeref) { + mScriptValueDeref(val); + } + return true; } void* rawMember = (void *)((uintptr_t) obj->value.opaque + m->offset); From 5c0bd1b2454cd64930b05806086b09844f6a4016 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 4 Feb 2023 23:16:33 -0800 Subject: [PATCH 10/47] Scripting: Add faux "NUL" type for type matching --- include/mgba/script/types.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index 76d5c1579..da13883d2 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -38,6 +38,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_C_TABLE struct Table* #define mSCRIPT_TYPE_C_WRAPPER struct mScriptValue* #define mSCRIPT_TYPE_C_WEAKREF uint32_t +#define mSCRIPT_TYPE_C_NUL void* #define mSCRIPT_TYPE_C_S(STRUCT) struct STRUCT* #define mSCRIPT_TYPE_C_CS(STRUCT) const struct STRUCT* #define mSCRIPT_TYPE_C_PS(X) void @@ -66,6 +67,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_FIELD_TABLE table #define mSCRIPT_TYPE_FIELD_WRAPPER opaque #define mSCRIPT_TYPE_FIELD_WEAKREF u32 +#define mSCRIPT_TYPE_FIELD_NUL opaque #define mSCRIPT_TYPE_FIELD_S(STRUCT) opaque #define mSCRIPT_TYPE_FIELD_CS(STRUCT) copaque #define mSCRIPT_TYPE_FIELD_PS(STRUCT) opaque @@ -94,6 +96,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_MS_TABLE (&mSTTable) #define mSCRIPT_TYPE_MS_WRAPPER (&mSTWrapper) #define mSCRIPT_TYPE_MS_WEAKREF (&mSTWeakref) +#define mSCRIPT_TYPE_MS_NUL mSCRIPT_TYPE_MS_VOID #define mSCRIPT_TYPE_MS_S(STRUCT) (&mSTStruct_ ## STRUCT) #define mSCRIPT_TYPE_MS_CS(STRUCT) (&mSTStructConst_ ## STRUCT) #define mSCRIPT_TYPE_MS_PS(STRUCT) (&mSTStructPtr_ ## STRUCT) @@ -123,6 +126,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_CMP_TABLE(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_TABLE, TYPE) #define mSCRIPT_TYPE_CMP_PTR(TYPE) ((TYPE)->base >= mSCRIPT_TYPE_OPAQUE) #define mSCRIPT_TYPE_CMP_WRAPPER(TYPE) (true) +#define mSCRIPT_TYPE_CMP_NUL(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_VOID, TYPE) #define mSCRIPT_TYPE_CMP_S(STRUCT) mSCRIPT_TYPE_MS_S(STRUCT)->name == _mSCRIPT_FIELD_NAME #define mSCRIPT_TYPE_CMP_CS(STRUCT) mSCRIPT_TYPE_MS_CS(STRUCT)->name == _mSCRIPT_FIELD_NAME #define mSCRIPT_TYPE_CMP_WSTR(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_WSTR, TYPE)) From 282a033df265596335800f34a2e9e5111ff5ad00 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 8 Feb 2023 00:39:02 -0800 Subject: [PATCH 11/47] Scripting: Clean up refcounting --- src/script/context.c | 1 + src/script/engines/lua.c | 97 +++++++++++++++++++++++++--------------- src/script/stdlib.c | 1 - src/script/types.c | 8 ---- 4 files changed, 63 insertions(+), 44 deletions(-) diff --git a/src/script/context.c b/src/script/context.c index bf5fcfa54..418050e74 100644 --- a/src/script/context.c +++ b/src/script/context.c @@ -250,6 +250,7 @@ uint32_t mScriptContextAddCallback(struct mScriptContext* context, const char* c HashTableIteratorLookup(&context->callbacks, &iter, callback); info->callback = HashTableIteratorGetKey(&context->callbacks, &iter); info->id = mScriptListSize(list->value.list); + mScriptValueRef(fn); mScriptValueWrap(fn, mScriptListAppend(list->value.list)); while (true) { uint32_t id = context->nextCallbackId; diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index 1e09bcde1..9d652eb4f 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -36,8 +36,11 @@ static const char* _luaGetError(struct mScriptEngineContext*); static bool _luaCall(struct mScriptFrame*, void* context); +static void _freeFrame(struct mScriptList* frame); +static void _autofreeFrame(struct mScriptContext* context, struct mScriptList* frame); + struct mScriptEngineContextLua; -static bool _luaPushFrame(struct mScriptEngineContextLua*, struct mScriptList*, bool internal); +static bool _luaPushFrame(struct mScriptEngineContextLua*, struct mScriptList*); static bool _luaPopFrame(struct mScriptEngineContextLua*, struct mScriptList*); static bool _luaInvoke(struct mScriptEngineContextLua*, struct mScriptFrame*); @@ -520,6 +523,8 @@ struct mScriptValue* _luaRootScope(struct mScriptEngineContext* ctx) { lua_pop(luaContext->lua, 1); key = _luaCoerce(luaContext, false); mScriptValueWrap(key, mScriptListAppend(list->value.list)); + mScriptValueRef(key); + mScriptContextFillPool(luaContext->d.context, key); } lua_pop(luaContext->lua, 1); @@ -544,7 +549,7 @@ struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext, lua_pushnil(luaContext->lua); - void* tablePointer; + const void* tablePointer; while (lua_next(luaContext->lua, -2) != 0) { struct mScriptValue* value = NULL; int type = lua_type(luaContext->lua, -1); @@ -559,11 +564,8 @@ struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext, tablePointer = lua_topointer(luaContext->lua, -1); // Ensure this table doesn't contain any cycles if (!HashTableLookupBinary(markedObjects, &tablePointer, sizeof(tablePointer))) { - HashTableInsertBinary(markedObjects, &tablePointer, sizeof(tablePointer), tablePointer); + HashTableInsertBinary(markedObjects, &tablePointer, sizeof(tablePointer), (void*) tablePointer); value = _luaCoerceTable(luaContext, markedObjects); - if (value) { - mScriptValueRef(value); - } } default: break; @@ -595,18 +597,13 @@ struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext, return false; } mScriptTableInsert(table, key, value); - if (key->type != mSCRIPT_TYPE_MS_STR) { - // Strings are added to the ref pool, so we need to keep it - // ref'd to prevent it from being collected prematurely - mScriptValueDeref(key); - } + mScriptValueDeref(key); mScriptValueDeref(value); } lua_pop(luaContext->lua, 1); size_t len = mScriptTableSize(table); if (!isList || !len) { - mScriptContextFillPool(luaContext->d.context, table); return table; } @@ -616,18 +613,15 @@ struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext, struct mScriptValue* value = mScriptTableLookup(table, &mSCRIPT_MAKE_S64(i)); if (!value) { mScriptValueDeref(list); - mScriptContextFillPool(luaContext->d.context, table); return table; } mScriptValueWrap(value, mScriptListAppend(list->value.list)); } if (i != len + 1) { mScriptValueDeref(list); - mScriptContextFillPool(luaContext->d.context, table); return table; } mScriptValueDeref(table); - mScriptContextFillPool(luaContext->d.context, list); return list; } @@ -663,7 +657,6 @@ struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext, bool case LUA_TSTRING: buffer = lua_tolstring(luaContext->lua, -1, &size); value = mScriptStringCreateFromBytes(buffer, size); - mScriptContextFillPool(luaContext->d.context, value); break; case LUA_TFUNCTION: // This function pops the value internally via luaL_ref @@ -692,6 +685,12 @@ struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext, bool lua_pop(luaContext->lua, 2); value = lua_touserdata(luaContext->lua, -1); value = mScriptContextAccessWeakref(luaContext->d.context, value); + if (value->type->base == mSCRIPT_TYPE_WRAPPER) { + value = mScriptValueUnwrap(value); + } + if (value) { + mScriptValueRef(value); + } break; } if (pop) { @@ -713,6 +712,7 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v lua_pushnil(luaContext->lua); return true; } + mScriptContextFillPool(luaContext->d.context, value); } struct mScriptValue derefPtr; if (value->type->base == mSCRIPT_TYPE_OPAQUE) { @@ -803,6 +803,7 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v if (needsWeakref) { *newValue = mSCRIPT_MAKE(WEAKREF, weakref); } else { + mScriptValueRef(value); mScriptValueWrap(value, newValue); } lua_getfield(luaContext->lua, LUA_REGISTRYINDEX, "mSTList"); @@ -813,6 +814,7 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v if (needsWeakref) { *newValue = mSCRIPT_MAKE(WEAKREF, weakref); } else { + mScriptValueRef(value); mScriptValueWrap(value, newValue); } lua_getfield(luaContext->lua, LUA_REGISTRYINDEX, "mSTTable"); @@ -824,7 +826,6 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v newValue->refs = mSCRIPT_VALUE_UNREF; newValue->type->alloc(newValue); lua_pushcclosure(luaContext->lua, _luaThunk, 1); - mScriptValueDeref(value); break; case mSCRIPT_TYPE_OBJECT: if (!value->value.opaque) { @@ -835,6 +836,7 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v if (needsWeakref) { *newValue = mSCRIPT_MAKE(WEAKREF, weakref); } else { + mScriptValueRef(value); mScriptValueWrap(value, newValue); } lua_getfield(luaContext->lua, LUA_REGISTRYINDEX, "mSTStruct"); @@ -935,16 +937,12 @@ const char* _luaGetError(struct mScriptEngineContext* context) { return luaContext->lastError; } -bool _luaPushFrame(struct mScriptEngineContextLua* luaContext, struct mScriptList* frame, bool internal) { +bool _luaPushFrame(struct mScriptEngineContextLua* luaContext, struct mScriptList* frame) { bool ok = true; if (frame) { size_t i; for (i = 0; i < mScriptListSize(frame); ++i) { struct mScriptValue* value = mScriptListGetPointer(frame, i); - if (internal && value->type->base == mSCRIPT_TYPE_WRAPPER) { - value = mScriptValueUnwrap(value); - mScriptContextFillPool(luaContext->d.context, value); - } if (!_luaWrap(luaContext, value)) { ok = false; break; @@ -968,8 +966,11 @@ bool _luaPopFrame(struct mScriptEngineContextLua* luaContext, struct mScriptList ok = false; break; } - mScriptValueWrap(value, mScriptListAppend(frame)); - mScriptValueDeref(value); + struct mScriptValue* tail = mScriptListAppend(frame); + mScriptValueWrap(value, tail); + if (tail->type == value->type) { + mScriptValueDeref(value); + } } if (count > i) { lua_pop(luaContext->lua, count - i); @@ -996,6 +997,26 @@ bool _luaCall(struct mScriptFrame* frame, void* context) { return true; } +void _freeFrame(struct mScriptList* frame) { + size_t i; + for (i = 0; i < mScriptListSize(frame); ++i) { + struct mScriptValue* val = mScriptValueUnwrap(mScriptListGetPointer(frame, i)); + if (val) { + mScriptValueDeref(val); + } + } +} + +void _autofreeFrame(struct mScriptContext* context, struct mScriptList* frame) { + size_t i; + for (i = 0; i < mScriptListSize(frame); ++i) { + struct mScriptValue* val = mScriptValueUnwrap(mScriptListGetPointer(frame, i)); + if (val) { + mScriptContextFillPool(context, val); + } + } +} + bool _luaInvoke(struct mScriptEngineContextLua* luaContext, struct mScriptFrame* frame) { int nargs = 0; if (frame) { @@ -1007,7 +1028,7 @@ bool _luaInvoke(struct mScriptEngineContextLua* luaContext, struct mScriptFrame* luaContext->lastError = NULL; } - if (frame && !_luaPushFrame(luaContext, &frame->arguments, false)) { + if (frame && !_luaPushFrame(luaContext, &frame->arguments)) { return false; } @@ -1072,6 +1093,7 @@ int _luaThunk(lua_State* lua) { struct mScriptFrame frame; mScriptFrameInit(&frame); if (!_luaPopFrame(luaContext, &frame.arguments)) { + _freeFrame(&frame.arguments); mScriptContextDrainPool(luaContext->d.context); mScriptFrameDeinit(&frame); luaL_traceback(lua, lua, "Error calling function (translating arguments into runtime)", 1); @@ -1079,19 +1101,21 @@ int _luaThunk(lua_State* lua) { } struct mScriptValue* fn = lua_touserdata(lua, lua_upvalueindex(1)); + _autofreeFrame(luaContext->d.context, &frame.arguments); if (!fn || !mScriptInvoke(fn, &frame)) { + mScriptContextDrainPool(luaContext->d.context); mScriptFrameDeinit(&frame); luaL_traceback(lua, lua, "Error calling function (invoking failed)", 1); return lua_error(lua); } - if (!_luaPushFrame(luaContext, &frame.returnValues, true)) { - mScriptFrameDeinit(&frame); + bool ok = _luaPushFrame(luaContext, &frame.returnValues); + mScriptContextDrainPool(luaContext->d.context); + mScriptFrameDeinit(&frame); + if (!ok) { luaL_traceback(lua, lua, "Error calling function (translating return values from runtime)", 1); return lua_error(lua); } - mScriptContextDrainPool(luaContext->d.context); - mScriptFrameDeinit(&frame); return lua_gettop(luaContext->lua); } @@ -1146,19 +1170,22 @@ int _luaSetObject(lua_State* lua) { strlcpy(key, keyPtr, sizeof(key)); lua_pop(lua, 2); - obj = mScriptContextAccessWeakref(luaContext->d.context, obj); - if (!obj) { - luaL_traceback(lua, lua, "Invalid object", 1); - return lua_error(lua); - } - if (!val) { luaL_traceback(lua, lua, "Error translating value to runtime", 1); return lua_error(lua); } + obj = mScriptContextAccessWeakref(luaContext->d.context, obj); + if (!obj) { + mScriptValueDeref(val); + mScriptContextDrainPool(luaContext->d.context); + luaL_traceback(lua, lua, "Invalid object", 1); + return lua_error(lua); + } + if (!mScriptObjectSet(obj, key, val)) { mScriptValueDeref(val); + mScriptContextDrainPool(luaContext->d.context); char error[MAX_KEY_SIZE + 16]; snprintf(error, sizeof(error), "Invalid key '%s'", key); luaL_traceback(lua, lua, "Invalid key", 1); diff --git a/src/script/stdlib.c b/src/script/stdlib.c index 839673cdb..3163a0d98 100644 --- a/src/script/stdlib.c +++ b/src/script/stdlib.c @@ -24,7 +24,6 @@ static uint32_t _mScriptCallbackAdd(struct mScriptCallbackManager* adapter, stru fn = mScriptValueUnwrap(fn); } uint32_t id = mScriptContextAddCallback(adapter->context, name->buffer, fn); - mScriptValueDeref(fn); return id; } diff --git a/src/script/types.c b/src/script/types.c index a05f6d252..dd50f7968 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -892,7 +892,6 @@ void mScriptValueWrap(struct mScriptValue* value, struct mScriptValue* out) { out->type = mSCRIPT_TYPE_MS_WRAPPER; out->value.opaque = value; - mScriptValueRef(value); } struct mScriptValue* mScriptValueUnwrap(struct mScriptValue* value) { @@ -1473,18 +1472,11 @@ bool mScriptObjectSet(struct mScriptValue* obj, const char* member, struct mScri this->value.opaque = obj->value.opaque; mSCRIPT_PUSH(&frame.arguments, CHARP, member); mScriptValueWrap(val, mScriptListAppend(&frame.arguments)); - bool needsDeref = mScriptListGetPointer(&frame.arguments, 2)->type->base == mSCRIPT_TYPE_WRAPPER; if (!mScriptInvoke(&setMember, &frame) || mScriptListSize(&frame.returnValues) != 0) { mScriptFrameDeinit(&frame); - if (needsDeref) { - mScriptValueDeref(val); - } return false; } mScriptFrameDeinit(&frame); - if (needsDeref) { - mScriptValueDeref(val); - } return true; } From 045a2c96dce4aa287612e1c06a5e5b961555a740 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 8 Feb 2023 00:41:33 -0800 Subject: [PATCH 12/47] Scripting: Fix passing mSTList/Table from Lua back into the runtime --- src/script/context.c | 8 -------- src/script/engines/lua.c | 12 +++++++++-- src/script/test/lua.c | 44 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/src/script/context.c b/src/script/context.c index 418050e74..65932fd79 100644 --- a/src/script/context.c +++ b/src/script/context.c @@ -84,14 +84,6 @@ void mScriptContextFillPool(struct mScriptContext* context, struct mScriptValue* if (value->refs == mSCRIPT_VALUE_UNREF) { return; } - switch (value->type->base) { - case mSCRIPT_TYPE_SINT: - case mSCRIPT_TYPE_UINT: - case mSCRIPT_TYPE_FLOAT: - return; - default: - break; - } struct mScriptValue* poolEntry = mScriptListAppend(&context->refPool); poolEntry->type = mSCRIPT_TYPE_MS_WRAPPER; diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index 9d652eb4f..3dfe8d0ac 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -679,8 +679,16 @@ struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext, bool } luaL_getmetatable(luaContext->lua, "mSTStruct"); if (!lua_rawequal(luaContext->lua, -1, -2)) { - lua_pop(luaContext->lua, 2); - break; + lua_pop(luaContext->lua, 1); + luaL_getmetatable(luaContext->lua, "mSTList"); + if (!lua_rawequal(luaContext->lua, -1, -2)) { + lua_pop(luaContext->lua, 1); + luaL_getmetatable(luaContext->lua, "mSTTable"); + if (!lua_rawequal(luaContext->lua, -1, -2)) { + lua_pop(luaContext->lua, 2); + break; + } + } } lua_pop(luaContext->lua, 2); value = lua_touserdata(luaContext->lua, -1); diff --git a/src/script/test/lua.c b/src/script/test/lua.c index eab8550dc..16ecc9987 100644 --- a/src/script/test/lua.c +++ b/src/script/test/lua.c @@ -811,6 +811,48 @@ M_TEST_DEFINE(linkedList) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(listConvert) { + SETUP_LUA; + + struct mScriptValue* list = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST); + + assert_true(lua->setGlobal(lua, "l", list)); + TEST_PROGRAM("assert(l)"); + + struct mScriptValue* val = lua->getGlobal(lua, "l"); + assert_non_null(val); + if (val->type->base == mSCRIPT_TYPE_WRAPPER) { + val = mScriptValueUnwrap(val); + } + assert_ptr_equal(val->type, mSCRIPT_TYPE_MS_LIST); + assert_ptr_equal(val->value.list, list->value.list); + mScriptValueDeref(val); + mScriptValueDeref(list); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(tableConvert) { + SETUP_LUA; + + struct mScriptValue* list = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); + + assert_true(lua->setGlobal(lua, "l", list)); + TEST_PROGRAM("assert(l)"); + + struct mScriptValue* val = lua->getGlobal(lua, "l"); + assert_non_null(val); + if (val->type->base == mSCRIPT_TYPE_WRAPPER) { + val = mScriptValueUnwrap(val); + } + assert_ptr_equal(val->type, mSCRIPT_TYPE_MS_TABLE); + assert_ptr_equal(val->value.table, list->value.table); + mScriptValueDeref(val); + mScriptValueDeref(list); + + mScriptContextDeinit(&context); +} + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua, cmocka_unit_test(create), cmocka_unit_test(loadGood), @@ -831,4 +873,6 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua, cmocka_unit_test(tableIterate), cmocka_unit_test(callList), cmocka_unit_test(linkedList), + cmocka_unit_test(listConvert), + cmocka_unit_test(tableConvert), ) From aefcd174a822ed9f2929eb968631df03f69aede3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 7 Feb 2023 21:09:29 -0800 Subject: [PATCH 13/47] Scripting: Warning cleanup --- src/script/test/lua.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/script/test/lua.c b/src/script/test/lua.c index 16ecc9987..49c5521d7 100644 --- a/src/script/test/lua.c +++ b/src/script/test/lua.c @@ -385,9 +385,6 @@ M_TEST_DEFINE(callCFunc) { M_TEST_DEFINE(callCTable) { SETUP_LUA; - struct mScriptValue a = mSCRIPT_MAKE_S32(1); - struct mScriptValue* val; - assert_true(lua->setGlobal(lua, "b", &boundTableSize)); TEST_PROGRAM("assert(b({}) == 0)"); @@ -424,7 +421,6 @@ M_TEST_DEFINE(globalNull) { SETUP_LUA; struct Test s = {}; - struct mScriptValue* val; struct mScriptValue a; LOAD_PROGRAM("assert(a)"); From 00a34e0d0767f6a1f3f79f52ab24367034ee7204 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 4 Feb 2023 01:18:01 -0800 Subject: [PATCH 14/47] Scripting: Add skeleton of storage API --- include/mgba/script/storage.h | 21 +++++ src/script/CMakeLists.txt | 9 ++ src/script/storage.c | 155 +++++++++++++++++++++++++++++++ src/script/test/storage.c | 169 ++++++++++++++++++++++++++++++++++ 4 files changed, 354 insertions(+) create mode 100644 include/mgba/script/storage.h create mode 100644 src/script/storage.c create mode 100644 src/script/test/storage.c diff --git a/include/mgba/script/storage.h b/include/mgba/script/storage.h new file mode 100644 index 000000000..5b6490e82 --- /dev/null +++ b/include/mgba/script/storage.h @@ -0,0 +1,21 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef M_SCRIPT_STORAGE_H +#define M_SCRIPT_STORAGE_H + +#include + +CXX_GUARD_START + +#include +#include + +struct VFile; +void mScriptContextAttachStorage(struct mScriptContext* context); + +CXX_GUARD_END + +#endif diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt index d84659453..f79b66d08 100644 --- a/src/script/CMakeLists.txt +++ b/src/script/CMakeLists.txt @@ -4,12 +4,17 @@ set(SOURCE_FILES input.c socket.c stdlib.c + storage.c types.c) set(TEST_FILES test/classes.c test/types.c) +if(USE_JSON_C) + list(APPEND SOURCE_FILES storage.c) +endif() + if(USE_LUA) list(APPEND SOURCE_FILES engines/lua.c) list(APPEND TEST_FILES @@ -17,6 +22,10 @@ if(USE_LUA) test/input.c test/lua.c test/stdlib.c) + + if(USE_JSON_C) + list(APPEND TEST_FILES test/storage.c) + endif() endif() source_group("Scripting" FILES ${SOURCE_FILES}) diff --git a/src/script/storage.c b/src/script/storage.c new file mode 100644 index 000000000..9bbbbfa5b --- /dev/null +++ b/src/script/storage.c @@ -0,0 +1,155 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include + +#define STORAGE_LEN_MAX 64 + +struct mScriptStorageBucket { + char* name; + struct mScriptValue* root; + bool dirty; +}; + +struct mScriptStorageContext { + struct Table buckets; +}; + +void mScriptStorageBucketDeinit(void*); +struct mScriptValue* mScriptStorageBucketGet(struct mScriptStorageBucket* adapter, const char* key); +static void mScriptStorageBucketSet(struct mScriptStorageBucket* adapter, const char* key, struct mScriptValue* value); +static void mScriptStorageBucketSetVoid(struct mScriptStorageBucket* adapter, const char* key, struct mScriptValue* value); +static void mScriptStorageBucketSetSInt(struct mScriptStorageBucket* adapter, const char* key, int64_t value); +static void mScriptStorageBucketSetUInt(struct mScriptStorageBucket* adapter, const char* key, uint64_t value); +static void mScriptStorageBucketSetFloat(struct mScriptStorageBucket* adapter, const char* key, double value); +static void mScriptStorageBucketSetBool(struct mScriptStorageBucket* adapter, const char* key, bool value); + +void mScriptStorageContextDeinit(struct mScriptStorageContext*); +struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContext*, const char* name); + +mSCRIPT_DECLARE_STRUCT(mScriptStorageBucket); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, WRAPPER, _get, mScriptStorageBucketGet, 1, CHARP, key); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setSInt, mScriptStorageBucketSetSInt, 2, CHARP, key, S64, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setUInt, mScriptStorageBucketSetUInt, 2, CHARP, key, U64, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setFloat, mScriptStorageBucketSetFloat, 2, CHARP, key, F64, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setBool, mScriptStorageBucketSetBool, 2, CHARP, key, BOOL, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setStr, mScriptStorageBucketSet, 2, CHARP, key, WSTR, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setList, mScriptStorageBucketSet, 2, CHARP, key, WLIST, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setTable, mScriptStorageBucketSet, 2, CHARP, key, WTABLE, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setVoid, mScriptStorageBucketSetVoid, 2, CHARP, key, NUL, value); + +mSCRIPT_DEFINE_STRUCT(mScriptStorageBucket) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setSInt) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setUInt) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setFloat) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setBool) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setStr) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setList) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setTable) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setVoid) + mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(mScriptStorageBucket) +mSCRIPT_DEFINE_END; + +mSCRIPT_DECLARE_STRUCT(mScriptStorageContext); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageContext, _deinit, mScriptStorageContextDeinit, 0); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageContext, S(mScriptStorageBucket), getBucket, mScriptStorageGetBucket, 1, CHARP, key); + +mSCRIPT_DEFINE_STRUCT(mScriptStorageContext) + mSCRIPT_DEFINE_STRUCT_DEINIT(mScriptStorageContext) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageContext, getBucket) +mSCRIPT_DEFINE_END; + +struct mScriptValue* mScriptStorageBucketGet(struct mScriptStorageBucket* bucket, const char* key) { + struct mScriptValue* val = mScriptTableLookup(bucket->root, &mSCRIPT_MAKE_CHARP(key)); + if (val) { + mScriptValueRef(val); + } + return val; +} + +void mScriptStorageBucketSet(struct mScriptStorageBucket* bucket, const char* key, struct mScriptValue* value) { + struct mScriptValue* vkey = mScriptStringCreateFromUTF8(key); + if (value->type->base == mSCRIPT_TYPE_WRAPPER) { + value = mScriptValueUnwrap(value); + } + mScriptTableInsert(bucket->root, vkey, value); + mScriptValueDeref(vkey); + bucket->dirty = true; +} + +void mScriptStorageBucketSetVoid(struct mScriptStorageBucket* bucket, const char* key, struct mScriptValue* value) { + UNUSED(value); + struct mScriptValue* vkey = mScriptStringCreateFromUTF8(key); + mScriptTableInsert(bucket->root, vkey, &mScriptValueNull); + mScriptValueDeref(vkey); + bucket->dirty = true; +} + +#define MAKE_SCALAR_SETTER(NAME, TYPE) \ + void mScriptStorageBucketSet ## NAME (struct mScriptStorageBucket* bucket, const char* key, mSCRIPT_TYPE_C_ ## TYPE value) { \ + struct mScriptValue* vkey = mScriptStringCreateFromUTF8(key); \ + struct mScriptValue* vval = mScriptValueAlloc(mSCRIPT_TYPE_MS_ ## TYPE); \ + vval->value.mSCRIPT_TYPE_FIELD_ ## TYPE = value; \ + mScriptTableInsert(bucket->root, vkey, vval); \ + mScriptValueDeref(vkey); \ + mScriptValueDeref(vval); \ + bucket->dirty = true; \ + } + +MAKE_SCALAR_SETTER(SInt, S64) +MAKE_SCALAR_SETTER(UInt, U64) +MAKE_SCALAR_SETTER(Float, F64) +MAKE_SCALAR_SETTER(Bool, BOOL) + +void mScriptContextAttachStorage(struct mScriptContext* context) { + struct mScriptStorageContext* storage = calloc(1, sizeof(*storage)); + struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptStorageContext)); + value->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER; + value->value.opaque = storage; + + HashTableInit(&storage->buckets, 0, mScriptStorageBucketDeinit); + + mScriptContextSetGlobal(context, "storage", value); + mScriptContextSetDocstring(context, "storage", "Singleton instance of struct::mScriptStorageContext"); +} + +void mScriptStorageContextDeinit(struct mScriptStorageContext* storage) { + HashTableDeinit(&storage->buckets); +} + +struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContext* storage, const char* name) { + if (!name) { + return NULL; + } + + // Check if name is allowed + // Currently only names matching /[0-9A-Za-z_.]+/ are allowed + size_t i; + for (i = 0; name[i]; ++i) { + if (i >= STORAGE_LEN_MAX) { + return NULL; + } + if (!isalnum(name[i]) && name[i] != '_' && name[i] != '.') { + return NULL; + } + } + struct mScriptStorageBucket* bucket = HashTableLookup(&storage->buckets, name); + if (bucket) { + return bucket; + } + + bucket = calloc(1, sizeof(*bucket)); + bucket->root = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); + bucket->name = strdup(name); + HashTableInsert(&storage->buckets, name, bucket); + return bucket; +} + +void mScriptStorageBucketDeinit(void* data) { + struct mScriptStorageBucket* bucket = data; + mScriptValueDeref(bucket->root); + free(bucket->name); + free(bucket); +} diff --git a/src/script/test/storage.c b/src/script/test/storage.c new file mode 100644 index 000000000..9777c6280 --- /dev/null +++ b/src/script/test/storage.c @@ -0,0 +1,169 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/test/suite.h" + +#include +#include +#include + +#include "script/test.h" + +#define SETUP_LUA \ + struct mScriptContext context; \ + mScriptContextInit(&context); \ + struct mScriptEngineContext* lua = mScriptContextRegisterEngine(&context, mSCRIPT_ENGINE_LUA); \ + mScriptContextAttachStdlib(&context); \ + mScriptContextAttachStorage(&context) + +M_TEST_SUITE_SETUP(mScriptStorage) { + if (mSCRIPT_ENGINE_LUA->init) { + mSCRIPT_ENGINE_LUA->init(mSCRIPT_ENGINE_LUA); + } + return 0; +} + +M_TEST_SUITE_TEARDOWN(mScriptStorage) { + if (mSCRIPT_ENGINE_LUA->deinit) { + mSCRIPT_ENGINE_LUA->deinit(mSCRIPT_ENGINE_LUA); + } + return 0; +} + +M_TEST_DEFINE(basicInt) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = 1"); + TEST_PROGRAM("assert(bucket.a == 1)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(basicFloat) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = 0.5"); + TEST_PROGRAM("assert(bucket.a == 0.5)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(basicBool) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = true"); + TEST_PROGRAM("assert(bucket.a == true)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(basicNil) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = nil"); + TEST_PROGRAM("assert(bucket.a == nil)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(basicString) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = 'hello'"); + TEST_PROGRAM("assert(bucket.a == 'hello')"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(basicList) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = {1}"); + TEST_PROGRAM("assert(#bucket.a == 1)"); + TEST_PROGRAM("assert(bucket.a[1] == 1)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(basicTable) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = {['a']=1}"); + TEST_PROGRAM("assert(#bucket.a == 1)"); + TEST_PROGRAM("assert(bucket.a.a == 1)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(nullByteString) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = 'a\\x00b'"); + TEST_PROGRAM("assert(bucket.a == 'a\\x00b')"); + TEST_PROGRAM("assert(#bucket.a == 3)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(structured) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM( + "bucket.a = {\n" + " ['a'] = 1,\n" + " ['b'] = {1},\n" + " ['c'] = {\n" + " ['d'] = 1\n" + " }\n" + "}" + ); + TEST_PROGRAM("assert(bucket.a)"); + TEST_PROGRAM("assert(bucket.a.a == 1)"); + TEST_PROGRAM("assert(#bucket.a.b == 1)"); + TEST_PROGRAM("assert(bucket.a.b[1] == 1)"); + TEST_PROGRAM("assert(#bucket.a.c == 1)"); + TEST_PROGRAM("assert(bucket.a.c.d == 1)"); + + mScriptContextDeinit(&context); +} + +M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage, + cmocka_unit_test(basicInt), + cmocka_unit_test(basicFloat), + cmocka_unit_test(basicBool), + cmocka_unit_test(basicNil), + cmocka_unit_test(basicString), + cmocka_unit_test(basicList), + cmocka_unit_test(basicTable), + cmocka_unit_test(nullByteString), + cmocka_unit_test(structured), +) From c1e1843e5e1e94e7e9137c40cecd866fb0818096 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 29 Jan 2023 21:56:02 -0800 Subject: [PATCH 15/47] CMake: Add json-c optional dependency --- CMakeLists.txt | 22 ++++++++++++++++++++++ src/core/flags.h.in | 4 ++++ 2 files changed, 26 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 42cff1ebc..57cb36029 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,7 @@ if(NOT LIBMGBA_ONLY) set(USE_SQLITE3 ON CACHE BOOL "Whether or not to enable SQLite3 support") set(USE_ELF ON CACHE BOOL "Whether or not to enable ELF support") set(USE_LUA ON CACHE BOOL "Whether or not to enable Lua scripting support") + set(USE_JSON_C ON CACHE BOOL "Whether or not to enable JSON-C support") set(M_CORE_GBA ON CACHE BOOL "Build Game Boy Advance core") set(M_CORE_GB ON CACHE BOOL "Build Game Boy core") set(USE_LZMA ON CACHE BOOL "Whether or not to enable 7-Zip support") @@ -477,6 +478,7 @@ endif() if(DISABLE_DEPS) set(USE_GDB_STUB OFF) set(USE_DISCORD_RPC OFF) + set(USE_JSON_C OFF) set(USE_SQLITE3 OFF) set(USE_PNG OFF) set(USE_ZLIB OFF) @@ -765,11 +767,30 @@ endif() if(ENABLE_SCRIPTING) list(APPEND ENABLES SCRIPTING) + find_feature(USE_JSON_C "json-c") if(NOT USE_LUA VERSION_LESS 5.1) find_feature(USE_LUA "Lua" ${USE_LUA}) else() find_feature(USE_LUA "Lua") endif() + if(USE_JSON_C) + list(APPEND FEATURES JSON_C) + if(TARGET json-c::json-c) + list(APPEND DEPENDENCY_LIB json-c::json-c) + get_target_property(JSON_C_SONAME json-c::json-c IMPORTED_SONAME_NONE) + string(SUBSTRING "${JSON_C_SONAME}" 13 -1 JSON_C_SOVER) + else() + if(${json-c_VERSION} VERSION_LESS 0.13.0) + set(JSON_C_SOVER 3) + elseif(${json-c_VERSION} VERSION_LESS 0.15.0) + set(JSON_C_SOVER 4) + endif() + list(APPEND DEPENDENCY_LIB ${json-c_LIBRARIES}) + include_directories(AFTER ${json-c_INCLUDE_DIRS}) + link_directories(${json-c_LIBDIRS}) + endif() + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libjson-c${JSON_C_SOVER}") + endif() if(USE_LUA) list(APPEND FEATURE_DEFINES USE_LUA) include_directories(AFTER ${LUA_INCLUDE_DIR}) @@ -1289,6 +1310,7 @@ if(NOT QUIET AND NOT LIBMGBA_ONLY) else() message(STATUS " Lua: ${USE_LUA}") endif() + message(STATUS " storage API: ${USE_JSON_C}") endif() message(STATUS "Frontends:") message(STATUS " Qt: ${BUILD_QT}") diff --git a/src/core/flags.h.in b/src/core/flags.h.in index 71ea562ef..ec9db8de5 100644 --- a/src/core/flags.h.in +++ b/src/core/flags.h.in @@ -79,6 +79,10 @@ #cmakedefine USE_GDB_STUB #endif +#ifndef USE_JSON_C +#cmakedefine USE_JSON_C +#endif + #ifndef USE_LIBAV #cmakedefine USE_LIBAV #endif From 0c6b443065bc6c6d1a1b258dc448b68a24f1cf02 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 4 Feb 2023 23:41:20 -0800 Subject: [PATCH 16/47] Scripting: Initial serialization work --- include/mgba/script/storage.h | 4 + src/script/storage.c | 149 ++++++++++++++++++++++++++++ src/script/test/storage.c | 176 ++++++++++++++++++++++++++++++++++ 3 files changed, 329 insertions(+) diff --git a/include/mgba/script/storage.h b/include/mgba/script/storage.h index 5b6490e82..f08efd87c 100644 --- a/include/mgba/script/storage.h +++ b/include/mgba/script/storage.h @@ -16,6 +16,10 @@ CXX_GUARD_START struct VFile; void mScriptContextAttachStorage(struct mScriptContext* context); +bool mScriptStorageSaveBucket(struct mScriptContext* context, const char* bucket); +bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* bucket, struct VFile* vf); +void mScriptStorageGetBucketPath(const char* bucket, char* out); + CXX_GUARD_END #endif diff --git a/src/script/storage.c b/src/script/storage.c index 9bbbbfa5b..a8e785743 100644 --- a/src/script/storage.c +++ b/src/script/storage.c @@ -5,6 +5,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include +#include +#include + +#include +#include + #define STORAGE_LEN_MAX 64 struct mScriptStorageBucket { @@ -29,6 +35,8 @@ static void mScriptStorageBucketSetBool(struct mScriptStorageBucket* adapter, co void mScriptStorageContextDeinit(struct mScriptStorageContext*); struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContext*, const char* name); +static bool mScriptStorageToJson(struct mScriptValue* value, struct json_object** out); + mSCRIPT_DECLARE_STRUCT(mScriptStorageBucket); mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, WRAPPER, _get, mScriptStorageBucketGet, 1, CHARP, key); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setSInt, mScriptStorageBucketSetSInt, 2, CHARP, key, S64, value); @@ -103,6 +111,147 @@ MAKE_SCALAR_SETTER(UInt, U64) MAKE_SCALAR_SETTER(Float, F64) MAKE_SCALAR_SETTER(Bool, BOOL) +void mScriptStorageGetBucketPath(const char* bucket, char* out) { + mCoreConfigDirectory(out, PATH_MAX); + + strncat(out, PATH_SEP "storage" PATH_SEP, PATH_MAX); + mkdir(out, 0755); + + char suffix[STORAGE_LEN_MAX + 6]; + snprintf(suffix, sizeof(suffix), "%s.json", bucket); + strncat(out, suffix, PATH_MAX); +} + +static struct json_object* _tableToJson(struct mScriptValue* rootVal) { + bool ok = true; + + struct TableIterator iter; + struct json_object* rootObj = json_object_new_object(); + if (mScriptTableIteratorStart(rootVal, &iter)) { + do { + struct mScriptValue* key = mScriptTableIteratorGetKey(rootVal, &iter); + struct mScriptValue* value = mScriptTableIteratorGetValue(rootVal, &iter); + const char* ckey; + if (key->type == mSCRIPT_TYPE_MS_CHARP) { + ckey = key->value.copaque; + } else if (key->type == mSCRIPT_TYPE_MS_STR) { + ckey = key->value.string->buffer; + } else { + ok = false; + break; + } + + struct json_object* obj; + ok = mScriptStorageToJson(value, &obj); + + if (!ok || json_object_object_add(rootObj, ckey, obj) < 0) { + ok = false; + } + } while (mScriptTableIteratorNext(rootVal, &iter) && ok); + } + if (!ok) { + json_object_put(rootObj); + return NULL; + } + return rootObj; +} + +bool mScriptStorageToJson(struct mScriptValue* value, struct json_object** out) { + if (value->type->base == mSCRIPT_TYPE_WRAPPER) { + value = mScriptValueUnwrap(value); + } + + size_t i; + bool ok = true; + struct json_object* obj = NULL; + switch (value->type->base) { + case mSCRIPT_TYPE_SINT: + obj = json_object_new_int64(value->value.s64); + break; + case mSCRIPT_TYPE_UINT: + if (value->type == mSCRIPT_TYPE_MS_BOOL) { + obj = json_object_new_boolean(value->value.u32); + break; + } + obj = json_object_new_uint64(value->value.u64); + break; + case mSCRIPT_TYPE_FLOAT: + obj = json_object_new_double(value->value.f64); + break; + case mSCRIPT_TYPE_STRING: + obj = json_object_new_string_len(value->value.string->buffer, value->value.string->size); + break; + case mSCRIPT_TYPE_LIST: + obj = json_object_new_array_ext(mScriptListSize(value->value.list)); + for (i = 0; i < mScriptListSize(value->value.list); ++i) { + struct json_object* listObj; + ok = mScriptStorageToJson(mScriptListGetPointer(value->value.list, i), &listObj); + if (!ok) { + break; + } + json_object_array_add(obj, listObj); + } + break; + case mSCRIPT_TYPE_TABLE: + obj = _tableToJson(value); + if (!obj) { + ok = false; + } + break; + case mSCRIPT_TYPE_VOID: + obj = NULL; + break; + default: + ok = false; + break; + } + + if (!ok) { + if (obj) { + json_object_put(obj); + } + *out = NULL; + } else { + *out = obj; + } + return ok; +} + +bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* bucketName, struct VFile* vf) { + struct mScriptValue* value = mScriptContextGetGlobal(context, "storage"); + if (!value) { + return false; + } + struct mScriptStorageContext* storage = value->value.opaque; + struct mScriptStorageBucket* bucket = mScriptStorageGetBucket(storage, bucketName); + struct json_object* rootObj; + bool ok = mScriptStorageToJson(bucket->root, &rootObj); + if (!ok) { + return false; + } + + const char* json = json_object_to_json_string_ext(rootObj, JSON_C_TO_STRING_PRETTY_TAB); + if (!json) { + json_object_put(rootObj); + return false; + } + + vf->write(vf, json, strlen(json)); + vf->close(vf); + + bucket->dirty = false; + + json_object_put(rootObj); + return true; +} + +bool mScriptStorageSaveBucket(struct mScriptContext* context, const char* bucketName) { + char path[PATH_MAX]; + mScriptStorageGetBucketPath(bucketName, path); + struct VFile* vf = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC); + return mScriptStorageSaveBucketVF(context, bucketName, vf); +} + void mScriptContextAttachStorage(struct mScriptContext* context) { struct mScriptStorageContext* storage = calloc(1, sizeof(*storage)); struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptStorageContext)); diff --git a/src/script/test/storage.c b/src/script/test/storage.c index 9777c6280..d6c491b25 100644 --- a/src/script/test/storage.c +++ b/src/script/test/storage.c @@ -156,6 +156,174 @@ M_TEST_DEFINE(structured) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(serializeInt) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = 1"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\"a\":1}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(serializeFloat) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = 0.5"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\"a\":0.5}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(serializeBool) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = true"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\"a\":true}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(serializeNil) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = nil"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\"a\":null}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(serializeString) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = 'hello'"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\"a\":\"hello\"}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(serializeList) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = {1, 2}"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\"a\":[1,2]}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(serializeTable) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = {['b']=1}"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\"a\":{\"b\":1}}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(serializeNullByteString) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = 'a\\x00b'"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\"a\":\"a\\u0000b\"}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage, cmocka_unit_test(basicInt), cmocka_unit_test(basicFloat), @@ -166,4 +334,12 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage, cmocka_unit_test(basicTable), cmocka_unit_test(nullByteString), cmocka_unit_test(structured), + cmocka_unit_test(serializeInt), + cmocka_unit_test(serializeFloat), + cmocka_unit_test(serializeBool), + cmocka_unit_test(serializeNil), + cmocka_unit_test(serializeString), + cmocka_unit_test(serializeList), + cmocka_unit_test(serializeTable), + cmocka_unit_test(serializeNullByteString), ) From 8b65f3772c5363da3a50bbe1114b880c96665d04 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 5 Feb 2023 01:19:54 -0800 Subject: [PATCH 17/47] Scripting: Initial deserialization work --- include/mgba/script/storage.h | 2 + src/script/storage.c | 111 +++++++++++++++++++++++++++ src/script/test/storage.c | 139 ++++++++++++++++++++++++++++++++++ 3 files changed, 252 insertions(+) diff --git a/include/mgba/script/storage.h b/include/mgba/script/storage.h index f08efd87c..4766ae8e8 100644 --- a/include/mgba/script/storage.h +++ b/include/mgba/script/storage.h @@ -18,6 +18,8 @@ void mScriptContextAttachStorage(struct mScriptContext* context); bool mScriptStorageSaveBucket(struct mScriptContext* context, const char* bucket); bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* bucket, struct VFile* vf); +bool mScriptStorageLoadBucket(struct mScriptContext* context, const char* bucket); +bool mScriptStorageLoadBucketVF(struct mScriptContext* context, const char* bucket, struct VFile* vf); void mScriptStorageGetBucketPath(const char* bucket, char* out); CXX_GUARD_END diff --git a/src/script/storage.c b/src/script/storage.c index a8e785743..bf0e73547 100644 --- a/src/script/storage.c +++ b/src/script/storage.c @@ -36,6 +36,7 @@ void mScriptStorageContextDeinit(struct mScriptStorageContext*); struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContext*, const char* name); static bool mScriptStorageToJson(struct mScriptValue* value, struct json_object** out); +static struct mScriptValue* mScriptStorageFromJson(struct json_object* json); mSCRIPT_DECLARE_STRUCT(mScriptStorageBucket); mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, WRAPPER, _get, mScriptStorageBucketGet, 1, CHARP, key); @@ -220,6 +221,7 @@ bool mScriptStorageToJson(struct mScriptValue* value, struct json_object** out) bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* bucketName, struct VFile* vf) { struct mScriptValue* value = mScriptContextGetGlobal(context, "storage"); if (!value) { + vf->close(vf); return false; } struct mScriptStorageContext* storage = value->value.opaque; @@ -227,12 +229,14 @@ bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* buck struct json_object* rootObj; bool ok = mScriptStorageToJson(bucket->root, &rootObj); if (!ok) { + vf->close(vf); return false; } const char* json = json_object_to_json_string_ext(rootObj, JSON_C_TO_STRING_PRETTY_TAB); if (!json) { json_object_put(rootObj); + vf->close(vf); return false; } @@ -252,6 +256,113 @@ bool mScriptStorageSaveBucket(struct mScriptContext* context, const char* bucket return mScriptStorageSaveBucketVF(context, bucketName, vf); } +struct mScriptValue* mScriptStorageFromJson(struct json_object* json) { + enum json_type type = json_object_get_type(json); + struct mScriptValue* value = NULL; + switch (type) { + case json_type_null: + return &mScriptValueNull; + case json_type_int: + value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S64); + value->value.s64 = json_object_get_int64(json); + break; + case json_type_double: + value = mScriptValueAlloc(mSCRIPT_TYPE_MS_F64); + value->value.f64 = json_object_get_double(json); + break; + case json_type_boolean: + value = mScriptValueAlloc(mSCRIPT_TYPE_MS_BOOL); + value->value.u32 = json_object_get_boolean(json); + break; + case json_type_string: + value = mScriptStringCreateFromBytes(json_object_get_string(json), json_object_get_string_len(json)); + break; + case json_type_array: + value = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST); + { + size_t i; + for (i = 0; i < json_object_array_length(json); ++i) { + struct mScriptValue* vval = mScriptStorageFromJson(json_object_array_get_idx(json, i)); + if (!vval) { + mScriptValueDeref(value); + value = NULL; + break; + } + mScriptValueWrap(vval, mScriptListAppend(value->value.list)); + mScriptValueDeref(vval); + } + } + break; + case json_type_object: + value = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); + { + json_object_object_foreach(json, jkey, jval) { + struct mScriptValue* vval = mScriptStorageFromJson(jval); + if (!vval) { + mScriptValueDeref(value); + value = NULL; + break; + } + struct mScriptValue* vkey = mScriptStringCreateFromUTF8(jkey); + mScriptTableInsert(value, vkey, vval); + mScriptValueDeref(vkey); + mScriptValueDeref(vval); + } + } + break; + } + return value; +} + +bool mScriptStorageLoadBucketVF(struct mScriptContext* context, const char* bucketName, struct VFile* vf) { + struct mScriptValue* value = mScriptContextGetGlobal(context, "storage"); + if (!value) { + vf->close(vf); + return false; + } + struct mScriptStorageContext* storage = value->value.opaque; + + ssize_t size = vf->size(vf); + if (size < 2) { + vf->close(vf); + return false; + } + char* json = calloc(1, size + 1); + if (vf->read(vf, json, size) != size) { + vf->close(vf); + return false; + } + vf->close(vf); + + struct json_object* obj = json_tokener_parse(json); + free(json); + if (!obj) { + return false; + } + + struct mScriptValue* root = mScriptStorageFromJson(obj); + json_object_put(obj); + if (!root) { + return false; + } + + struct mScriptStorageBucket* bucket = mScriptStorageGetBucket(storage, bucketName); + mScriptValueDeref(bucket->root); + bucket->root = root; + + return true; +} + +bool mScriptStorageLoadBucket(struct mScriptContext* context, const char* bucketName) { + char path[PATH_MAX]; + mScriptStorageGetBucketPath(bucketName, path); + struct VFile* vf = VFileOpen(path, O_RDONLY); + if (!vf) { + return false; + } + return mScriptStorageLoadBucketVF(context, bucketName, vf); +} + void mScriptContextAttachStorage(struct mScriptContext* context) { struct mScriptStorageContext* storage = calloc(1, sizeof(*storage)); struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptStorageContext)); diff --git a/src/script/test/storage.c b/src/script/test/storage.c index d6c491b25..41aeff8df 100644 --- a/src/script/test/storage.c +++ b/src/script/test/storage.c @@ -324,6 +324,137 @@ M_TEST_DEFINE(serializeNullByteString) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(deserializeInt) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":1}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(bucket.a == 1)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeFloat) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":0.5}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(bucket.a == 0.5)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeBool) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":true}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(bucket.a == true)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeNil) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":null}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(bucket.a == nil)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeString) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":\"hello\"}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(bucket.a == 'hello')"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeList) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":[1,2]}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(#bucket.a == 2)"); + TEST_PROGRAM("assert(bucket.a[1] == 1)"); + TEST_PROGRAM("assert(bucket.a[2] == 2)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeTable) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":{\"b\":1}}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(bucket.a.b == 1)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeNullByteString) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":\"a\\u0000b\"}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(bucket.a == 'a\\x00b')"); + TEST_PROGRAM("assert(bucket.a ~= 'a\\x00c')"); + + mScriptContextDeinit(&context); +} + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage, cmocka_unit_test(basicInt), cmocka_unit_test(basicFloat), @@ -342,4 +473,12 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage, cmocka_unit_test(serializeList), cmocka_unit_test(serializeTable), cmocka_unit_test(serializeNullByteString), + cmocka_unit_test(deserializeInt), + cmocka_unit_test(deserializeFloat), + cmocka_unit_test(deserializeBool), + cmocka_unit_test(deserializeNil), + cmocka_unit_test(deserializeString), + cmocka_unit_test(deserializeList), + cmocka_unit_test(deserializeTable), + cmocka_unit_test(deserializeNullByteString), ) From 91474e179ca82c8a0574bf5e70bb086e4f74a975 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 5 Feb 2023 15:34:37 -0800 Subject: [PATCH 18/47] Scripting: More storage tests --- src/script/test/storage.c | 67 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/script/test/storage.c b/src/script/test/storage.c index 41aeff8df..a64c5b899 100644 --- a/src/script/test/storage.c +++ b/src/script/test/storage.c @@ -156,6 +156,18 @@ M_TEST_DEFINE(structured) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(invalidObject) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + LOAD_PROGRAM("bucket.a = bucket"); + assert_false(lua->run(lua)); + + mScriptContextDeinit(&context); +} + M_TEST_DEFINE(serializeInt) { SETUP_LUA; @@ -455,6 +467,58 @@ M_TEST_DEFINE(deserializeNullByteString) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(deserializeError) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{a:1}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_false(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(not bucket.a)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(structuredRoundTrip) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM( + "bucket.a = {\n" + " ['a'] = 1,\n" + " ['b'] = {1},\n" + " ['c'] = {\n" + " ['d'] = 1\n" + " }\n" + "}" + ); + + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + + TEST_PROGRAM("bucket.a = nil") + TEST_PROGRAM("assert(not bucket.a)"); + + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + + TEST_PROGRAM("assert(bucket.a)"); + TEST_PROGRAM("assert(bucket.a.a == 1)"); + TEST_PROGRAM("assert(#bucket.a.b == 1)"); + TEST_PROGRAM("assert(bucket.a.b[1] == 1)"); + TEST_PROGRAM("assert(#bucket.a.c == 1)"); + TEST_PROGRAM("assert(bucket.a.c.d == 1)"); + + mScriptContextDeinit(&context); +} + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage, cmocka_unit_test(basicInt), cmocka_unit_test(basicFloat), @@ -464,6 +528,7 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage, cmocka_unit_test(basicList), cmocka_unit_test(basicTable), cmocka_unit_test(nullByteString), + cmocka_unit_test(invalidObject), cmocka_unit_test(structured), cmocka_unit_test(serializeInt), cmocka_unit_test(serializeFloat), @@ -481,4 +546,6 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage, cmocka_unit_test(deserializeList), cmocka_unit_test(deserializeTable), cmocka_unit_test(deserializeNullByteString), + cmocka_unit_test(deserializeError), + cmocka_unit_test(structuredRoundTrip), ) From 63d96ab7129000799fb49ed24b6f66442ce31d97 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 5 Feb 2023 21:01:11 -0800 Subject: [PATCH 19/47] Scripting: Add flushing/reloading --- include/mgba/script/storage.h | 1 + src/script/storage.c | 148 +++++++++++++++++++++++++--------- src/script/test/storage.c | 5 +- 3 files changed, 114 insertions(+), 40 deletions(-) diff --git a/include/mgba/script/storage.h b/include/mgba/script/storage.h index 4766ae8e8..629158e83 100644 --- a/include/mgba/script/storage.h +++ b/include/mgba/script/storage.h @@ -15,6 +15,7 @@ CXX_GUARD_START struct VFile; void mScriptContextAttachStorage(struct mScriptContext* context); +void mScriptStorageFlushAll(struct mScriptContext* context); bool mScriptStorageSaveBucket(struct mScriptContext* context, const char* bucket); bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* bucket, struct VFile* vf); diff --git a/src/script/storage.c b/src/script/storage.c index bf0e73547..d845db708 100644 --- a/src/script/storage.c +++ b/src/script/storage.c @@ -24,15 +24,18 @@ struct mScriptStorageContext { }; void mScriptStorageBucketDeinit(void*); -struct mScriptValue* mScriptStorageBucketGet(struct mScriptStorageBucket* adapter, const char* key); -static void mScriptStorageBucketSet(struct mScriptStorageBucket* adapter, const char* key, struct mScriptValue* value); -static void mScriptStorageBucketSetVoid(struct mScriptStorageBucket* adapter, const char* key, struct mScriptValue* value); -static void mScriptStorageBucketSetSInt(struct mScriptStorageBucket* adapter, const char* key, int64_t value); -static void mScriptStorageBucketSetUInt(struct mScriptStorageBucket* adapter, const char* key, uint64_t value); -static void mScriptStorageBucketSetFloat(struct mScriptStorageBucket* adapter, const char* key, double value); -static void mScriptStorageBucketSetBool(struct mScriptStorageBucket* adapter, const char* key, bool value); +struct mScriptValue* mScriptStorageBucketGet(struct mScriptStorageBucket* bucket, const char* key); +static void mScriptStorageBucketSet(struct mScriptStorageBucket* bucket, const char* key, struct mScriptValue* value); +static void mScriptStorageBucketSetVoid(struct mScriptStorageBucket* bucket, const char* key, struct mScriptValue* value); +static void mScriptStorageBucketSetSInt(struct mScriptStorageBucket* bucket, const char* key, int64_t value); +static void mScriptStorageBucketSetUInt(struct mScriptStorageBucket* bucket, const char* key, uint64_t value); +static void mScriptStorageBucketSetFloat(struct mScriptStorageBucket* bucket, const char* key, double value); +static void mScriptStorageBucketSetBool(struct mScriptStorageBucket* bucket, const char* key, bool value); +static bool mScriptStorageBucketReload(struct mScriptStorageBucket* bucket); +static bool mScriptStorageBucketFlush(struct mScriptStorageBucket* bucket); -void mScriptStorageContextDeinit(struct mScriptStorageContext*); +static void mScriptStorageContextDeinit(struct mScriptStorageContext*); +static void mScriptStorageContextFlushAll(struct mScriptStorageContext*); struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContext*, const char* name); static bool mScriptStorageToJson(struct mScriptValue* value, struct json_object** out); @@ -48,6 +51,8 @@ mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setStr, mScriptStorageB mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setList, mScriptStorageBucketSet, 2, CHARP, key, WLIST, value); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setTable, mScriptStorageBucketSet, 2, CHARP, key, WTABLE, value); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setVoid, mScriptStorageBucketSetVoid, 2, CHARP, key, NUL, value); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, BOOL, reload, mScriptStorageBucketReload, 0); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, BOOL, flush, mScriptStorageBucketFlush, 0); mSCRIPT_DEFINE_STRUCT(mScriptStorageBucket) mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setSInt) @@ -59,15 +64,19 @@ mSCRIPT_DEFINE_STRUCT(mScriptStorageBucket) mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setTable) mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setVoid) mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(mScriptStorageBucket) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageBucket, reload) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageBucket, flush) mSCRIPT_DEFINE_END; mSCRIPT_DECLARE_STRUCT(mScriptStorageContext); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageContext, _deinit, mScriptStorageContextDeinit, 0); mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageContext, S(mScriptStorageBucket), getBucket, mScriptStorageGetBucket, 1, CHARP, key); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageContext, flushAll, mScriptStorageContextFlushAll, 0); mSCRIPT_DEFINE_STRUCT(mScriptStorageContext) mSCRIPT_DEFINE_STRUCT_DEINIT(mScriptStorageContext) mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageContext, getBucket) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageContext, flushAll) mSCRIPT_DEFINE_END; struct mScriptValue* mScriptStorageBucketGet(struct mScriptStorageBucket* bucket, const char* key) { @@ -218,14 +227,7 @@ bool mScriptStorageToJson(struct mScriptValue* value, struct json_object** out) return ok; } -bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* bucketName, struct VFile* vf) { - struct mScriptValue* value = mScriptContextGetGlobal(context, "storage"); - if (!value) { - vf->close(vf); - return false; - } - struct mScriptStorageContext* storage = value->value.opaque; - struct mScriptStorageBucket* bucket = mScriptStorageGetBucket(storage, bucketName); +static bool _mScriptStorageBucketFlushVF(struct mScriptStorageBucket* bucket, struct VFile* vf) { struct json_object* rootObj; bool ok = mScriptStorageToJson(bucket->root, &rootObj); if (!ok) { @@ -249,6 +251,24 @@ bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* buck return true; } +bool mScriptStorageBucketFlush(struct mScriptStorageBucket* bucket) { + char path[PATH_MAX]; + mScriptStorageGetBucketPath(bucket->name, path); + struct VFile* vf = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC); + return _mScriptStorageBucketFlushVF(bucket, vf); +} + +bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* bucketName, struct VFile* vf) { + struct mScriptValue* value = mScriptContextGetGlobal(context, "storage"); + if (!value) { + vf->close(vf); + return false; + } + struct mScriptStorageContext* storage = value->value.opaque; + struct mScriptStorageBucket* bucket = mScriptStorageGetBucket(storage, bucketName); + return _mScriptStorageBucketFlushVF(bucket, vf); +} + bool mScriptStorageSaveBucket(struct mScriptContext* context, const char* bucketName) { char path[PATH_MAX]; mScriptStorageGetBucketPath(bucketName, path); @@ -314,6 +334,51 @@ struct mScriptValue* mScriptStorageFromJson(struct json_object* json) { return value; } +static struct mScriptValue* _mScriptStorageLoadJson(struct VFile* vf) { + ssize_t size = vf->size(vf); + if (size < 2) { + vf->close(vf); + return NULL; + } + char* json = calloc(1, size + 1); + if (vf->read(vf, json, size) != size) { + vf->close(vf); + return NULL; + } + vf->close(vf); + + struct json_object* obj = json_tokener_parse(json); + free(json); + if (!obj) { + return NULL; + } + + struct mScriptValue* root = mScriptStorageFromJson(obj); + json_object_put(obj); + return root; +} + +bool mScriptStorageBucketReload(struct mScriptStorageBucket* bucket) { + char path[PATH_MAX]; + mScriptStorageGetBucketPath(bucket->name, path); + struct VFile* vf = VFileOpen(path, O_RDONLY); + if (!vf) { + return false; + } + struct mScriptValue* root = _mScriptStorageLoadJson(vf); + if (!root) { + return false; + } + if (bucket->root) { + mScriptValueDeref(bucket->root); + } + bucket->root = root; + + bucket->dirty = false; + + return true; +} + bool mScriptStorageLoadBucketVF(struct mScriptContext* context, const char* bucketName, struct VFile* vf) { struct mScriptValue* value = mScriptContextGetGlobal(context, "storage"); if (!value) { @@ -321,35 +386,16 @@ bool mScriptStorageLoadBucketVF(struct mScriptContext* context, const char* buck return false; } struct mScriptStorageContext* storage = value->value.opaque; - - ssize_t size = vf->size(vf); - if (size < 2) { - vf->close(vf); - return false; - } - char* json = calloc(1, size + 1); - if (vf->read(vf, json, size) != size) { - vf->close(vf); - return false; - } - vf->close(vf); - - struct json_object* obj = json_tokener_parse(json); - free(json); - if (!obj) { - return false; - } - - struct mScriptValue* root = mScriptStorageFromJson(obj); - json_object_put(obj); + struct mScriptValue* root = _mScriptStorageLoadJson(vf); if (!root) { return false; } - struct mScriptStorageBucket* bucket = mScriptStorageGetBucket(storage, bucketName); mScriptValueDeref(bucket->root); bucket->root = root; + bucket->dirty = false; + return true; } @@ -375,10 +421,29 @@ void mScriptContextAttachStorage(struct mScriptContext* context) { mScriptContextSetDocstring(context, "storage", "Singleton instance of struct::mScriptStorageContext"); } +void mScriptStorageFlushAll(struct mScriptContext* context) { + struct mScriptValue* value = mScriptContextGetGlobal(context, "storage"); + if (!value) { + return; + } + struct mScriptStorageContext* storage = value->value.opaque; + mScriptStorageContextFlushAll(storage); +} + void mScriptStorageContextDeinit(struct mScriptStorageContext* storage) { HashTableDeinit(&storage->buckets); } +void mScriptStorageContextFlushAll(struct mScriptStorageContext* storage) { + struct TableIterator iter; + if (HashTableIteratorStart(&storage->buckets, &iter)) { + do { + struct mScriptStorageBucket* bucket = HashTableIteratorGetValue(&storage->buckets, &iter); + mScriptStorageBucketFlush(bucket); + } while (HashTableIteratorNext(&storage->buckets, &iter)); + } +} + struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContext* storage, const char* name) { if (!name) { return NULL; @@ -401,14 +466,19 @@ struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContex } bucket = calloc(1, sizeof(*bucket)); - bucket->root = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); bucket->name = strdup(name); + if (!mScriptStorageBucketReload(bucket)) { + bucket->root = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); + } HashTableInsert(&storage->buckets, name, bucket); return bucket; } void mScriptStorageBucketDeinit(void* data) { struct mScriptStorageBucket* bucket = data; + if (bucket->dirty) { + mScriptStorageBucketFlush(bucket); + } mScriptValueDeref(bucket->root); free(bucket->name); free(bucket); diff --git a/src/script/test/storage.c b/src/script/test/storage.c index a64c5b899..9978776b0 100644 --- a/src/script/test/storage.c +++ b/src/script/test/storage.c @@ -16,7 +16,10 @@ mScriptContextInit(&context); \ struct mScriptEngineContext* lua = mScriptContextRegisterEngine(&context, mSCRIPT_ENGINE_LUA); \ mScriptContextAttachStdlib(&context); \ - mScriptContextAttachStorage(&context) + mScriptContextAttachStorage(&context); \ + char bucketPath[PATH_MAX]; \ + mScriptStorageGetBucketPath("xtest", bucketPath); \ + remove(bucketPath) M_TEST_SUITE_SETUP(mScriptStorage) { if (mSCRIPT_ENGINE_LUA->init) { From f3d49527b70d35be464c630c0b5c1c8981e12385 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 5 Feb 2023 21:20:27 -0800 Subject: [PATCH 20/47] Qt: Add scripting storage integration --- src/platform/qt/scripting/ScriptingController.cpp | 11 +++++++++++ src/platform/qt/scripting/ScriptingController.h | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/src/platform/qt/scripting/ScriptingController.cpp b/src/platform/qt/scripting/ScriptingController.cpp index b0fbd51ef..836e379f1 100644 --- a/src/platform/qt/scripting/ScriptingController.cpp +++ b/src/platform/qt/scripting/ScriptingController.cpp @@ -20,6 +20,7 @@ #include "scripting/ScriptingTextBufferModel.h" #include +#include #include #include @@ -51,6 +52,9 @@ ScriptingController::ScriptingController(QObject* parent) m_bufferModel = new ScriptingTextBufferModel(this); QObject::connect(m_bufferModel, &ScriptingTextBufferModel::textBufferCreated, this, &ScriptingController::textBufferCreated); + connect(&m_storageFlush, &QTimer::timeout, this, &ScriptingController::flushStorage); + m_storageFlush.setInterval(5); + mScriptGamepadInit(&m_gamepad); init(); @@ -144,6 +148,10 @@ void ScriptingController::runCode(const QString& code) { load(vf, "*prompt"); } +void ScriptingController::flushStorage() { + mScriptStorageFlushAll(&m_scriptContext); +} + bool ScriptingController::eventFilter(QObject* obj, QEvent* ev) { event(obj, ev); return false; @@ -293,6 +301,7 @@ void ScriptingController::detachGamepad() { void ScriptingController::init() { mScriptContextInit(&m_scriptContext); mScriptContextAttachStdlib(&m_scriptContext); + mScriptContextAttachStorage(&m_scriptContext); mScriptContextAttachSocket(&m_scriptContext); mScriptContextAttachInput(&m_scriptContext); mScriptContextRegisterEngines(&m_scriptContext); @@ -308,6 +317,8 @@ void ScriptingController::init() { if (m_engines.count() == 1) { m_activeEngine = *m_engines.begin(); } + + m_storageFlush.start(); } uint32_t ScriptingController::qtToScriptingKey(const QKeyEvent* event) { diff --git a/src/platform/qt/scripting/ScriptingController.h b/src/platform/qt/scripting/ScriptingController.h index 0e275561d..e34b34542 100644 --- a/src/platform/qt/scripting/ScriptingController.h +++ b/src/platform/qt/scripting/ScriptingController.h @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -55,6 +56,8 @@ public slots: void reset(); void runCode(const QString& code); + void flushStorage(); + protected: bool eventFilter(QObject*, QEvent*) override; @@ -84,6 +87,8 @@ private: std::shared_ptr m_controller; InputController* m_inputController = nullptr; + + QTimer m_storageFlush; }; } From dca1e49c9f3011ca34177a28dc0a0eb7f41609cd Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 5 Feb 2023 22:32:19 -0800 Subject: [PATCH 21/47] Scripting: Add documentation for storage and buckets --- src/script/docgen.c | 2 ++ src/script/storage.c | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/script/docgen.c b/src/script/docgen.c index 65fcc87ce..502baa705 100644 --- a/src/script/docgen.c +++ b/src/script/docgen.c @@ -9,6 +9,7 @@ #include #include #include +#include #include struct mScriptContext context; @@ -469,6 +470,7 @@ int main(int argc, char* argv[]) { mScriptContextInit(&context); mScriptContextAttachStdlib(&context); mScriptContextAttachSocket(&context); + mScriptContextAttachStorage(&context); mScriptContextAttachInput(&context); mScriptContextSetTextBufferFactory(&context, NULL, NULL); diff --git a/src/script/storage.c b/src/script/storage.c index d845db708..822a85b4f 100644 --- a/src/script/storage.c +++ b/src/script/storage.c @@ -55,6 +55,14 @@ mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, BOOL, reload, mScriptStorage mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, BOOL, flush, mScriptStorageBucketFlush, 0); mSCRIPT_DEFINE_STRUCT(mScriptStorageBucket) + mSCRIPT_DEFINE_CLASS_DOCSTRING( + "A single 'bucket' of stored data, appropriate for a single script to store its data. " + "Fields can be set directly on the bucket objct, e.g. if you want to store a value called " + "`foo` on a bucket named `bucket`, you can directly assign to it as `bucket.foo = value`, " + "and retrieve it in the same way later. Primitive types (numbers, strings, lists and tables) " + "can be stored in buckets, but complex data types (e.g. a bucket itself) cannot. Data " + "stored in a bucket is periodically flushed to disk and persists between sessions." + ) mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setSInt) mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setUInt) mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setFloat) @@ -64,7 +72,9 @@ mSCRIPT_DEFINE_STRUCT(mScriptStorageBucket) mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setTable) mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setVoid) mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(mScriptStorageBucket) + mSCRIPT_DEFINE_DOCSTRING("Reload the state of the bucket from disk") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageBucket, reload) + mSCRIPT_DEFINE_DOCSTRING("Flush the bucket to disk manually") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageBucket, flush) mSCRIPT_DEFINE_END; @@ -75,7 +85,12 @@ mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageContext, flushAll, mScriptStora mSCRIPT_DEFINE_STRUCT(mScriptStorageContext) mSCRIPT_DEFINE_STRUCT_DEINIT(mScriptStorageContext) + mSCRIPT_DEFINE_DOCSTRING( + "Get a bucket with the given name. Names can contain letters, numbers, " + "underscores and periods. If a given bucket doesn't exist, it is created." + ) mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageContext, getBucket) + mSCRIPT_DEFINE_DOCSTRING("Flush all buckets to disk manually") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageContext, flushAll) mSCRIPT_DEFINE_END; From e3e0957f1456b3ea5b38270d7b1b8c566fa81e9e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 8 Feb 2023 02:37:35 -0800 Subject: [PATCH 22/47] Scripting: A slew of buildfixes --- CMakeLists.txt | 6 ++++++ src/script/storage.c | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 57cb36029..32551c602 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -779,6 +779,12 @@ if(ENABLE_SCRIPTING) list(APPEND DEPENDENCY_LIB json-c::json-c) get_target_property(JSON_C_SONAME json-c::json-c IMPORTED_SONAME_NONE) string(SUBSTRING "${JSON_C_SONAME}" 13 -1 JSON_C_SOVER) + + # This is only needed on 0.15, but the target unhelpfully does not contain version info + get_target_property(JSON_C_INCLUDE_DIR json-c::json-c INTERFACE_INCLUDE_DIRECTORIES) + if(NOT JSON_C_INCLUDE_DIR MATCHES json-c$) + include_directories(AFTER "${JSON_C_INCLUDE_DIR}/json-c") + endif() else() if(${json-c_VERSION} VERSION_LESS 0.13.0) set(JSON_C_SOVER 3) diff --git a/src/script/storage.c b/src/script/storage.c index 822a85b4f..f9562a488 100644 --- a/src/script/storage.c +++ b/src/script/storage.c @@ -140,7 +140,14 @@ void mScriptStorageGetBucketPath(const char* bucket, char* out) { mCoreConfigDirectory(out, PATH_MAX); strncat(out, PATH_SEP "storage" PATH_SEP, PATH_MAX); +#ifdef _WIN32 + // TODO: Move this to vfs somewhere + WCHAR wout[MAX_PATH]; + MultiByteToWideChar(CP_UTF8, 0, out, -1, wout, MAX_PATH); + CreateDirectoryW(wout, NULL); +#else mkdir(out, 0755); +#endif char suffix[STORAGE_LEN_MAX + 6]; snprintf(suffix, sizeof(suffix), "%s.json", bucket); @@ -169,8 +176,12 @@ static struct json_object* _tableToJson(struct mScriptValue* rootVal) { struct json_object* obj; ok = mScriptStorageToJson(value, &obj); - if (!ok || json_object_object_add(rootObj, ckey, obj) < 0) { - ok = false; + if (ok) { +#if JSON_C_VERSION_NUM >= (13 << 8) + ok = json_object_object_add(rootObj, ckey, obj) >= 0; +#else + json_object_object_add(rootObj, ckey, obj); +#endif } } while (mScriptTableIteratorNext(rootVal, &iter) && ok); } @@ -198,7 +209,15 @@ bool mScriptStorageToJson(struct mScriptValue* value, struct json_object** out) obj = json_object_new_boolean(value->value.u32); break; } +#if JSON_C_VERSION_NUM >= (14 << 8) obj = json_object_new_uint64(value->value.u64); +#else + if (value->value.u64 < (uint64_t) INT64_MAX) { + obj = json_object_new_int64(value->value.u64); + } else { + obj = json_object_new_double(value->value.u64); + } +#endif break; case mSCRIPT_TYPE_FLOAT: obj = json_object_new_double(value->value.f64); @@ -207,7 +226,11 @@ bool mScriptStorageToJson(struct mScriptValue* value, struct json_object** out) obj = json_object_new_string_len(value->value.string->buffer, value->value.string->size); break; case mSCRIPT_TYPE_LIST: +#if JSON_C_VERSION_NUM >= (15 << 8) obj = json_object_new_array_ext(mScriptListSize(value->value.list)); +#else + obj = json_object_new_array(); +#endif for (i = 0; i < mScriptListSize(value->value.list); ++i) { struct json_object* listObj; ok = mScriptStorageToJson(mScriptListGetPointer(value->value.list, i), &listObj); @@ -242,6 +265,10 @@ bool mScriptStorageToJson(struct mScriptValue* value, struct json_object** out) return ok; } +#ifndef JSON_C_TO_STRING_PRETTY_TAB +#define JSON_C_TO_STRING_PRETTY_TAB 0 +#endif + static bool _mScriptStorageBucketFlushVF(struct mScriptStorageBucket* bucket, struct VFile* vf) { struct json_object* rootObj; bool ok = mScriptStorageToJson(bucket->root, &rootObj); @@ -250,7 +277,7 @@ static bool _mScriptStorageBucketFlushVF(struct mScriptStorageBucket* bucket, st return false; } - const char* json = json_object_to_json_string_ext(rootObj, JSON_C_TO_STRING_PRETTY_TAB); + const char* json = json_object_to_json_string_ext(rootObj, JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_PRETTY_TAB); if (!json) { json_object_put(rootObj); vf->close(vf); From 0cc66867ca623d1979ed6b1b16addf83f17acad9 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 8 Feb 2023 02:38:37 -0800 Subject: [PATCH 23/47] CHANGES: Add storage API mention --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 028537466..5334fd617 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,7 @@ 0.11.0: (Future) Features: - Scripting: New `input` API for getting raw keyboard/mouse/controller state + - Scripting: New `storage` API for saving data for a script, e.g. settings - New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng, GGB-81 - Debugger: Add range watchpoints Emulation fixes: From 1268aaee1c12e8cb45c46d5b116db067efd1649b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 8 Feb 2023 02:45:54 -0800 Subject: [PATCH 24/47] Scripting: Fix tests --- src/script/test/storage.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/script/test/storage.c b/src/script/test/storage.c index 9978776b0..d735a1f87 100644 --- a/src/script/test/storage.c +++ b/src/script/test/storage.c @@ -185,7 +185,7 @@ M_TEST_DEFINE(serializeInt) { ssize_t size = vf->size(vf); char* buf = calloc(1, size + 1); assert_int_equal(vf->read(vf, buf, size), size); - assert_string_equal(buf, "{\"a\":1}"); + assert_string_equal(buf, "{\n\t\"a\":1\n}"); free(buf); vf->close(vf); @@ -206,7 +206,7 @@ M_TEST_DEFINE(serializeFloat) { ssize_t size = vf->size(vf); char* buf = calloc(1, size + 1); assert_int_equal(vf->read(vf, buf, size), size); - assert_string_equal(buf, "{\"a\":0.5}"); + assert_string_equal(buf, "{\n\t\"a\":0.5\n}"); free(buf); vf->close(vf); @@ -227,7 +227,7 @@ M_TEST_DEFINE(serializeBool) { ssize_t size = vf->size(vf); char* buf = calloc(1, size + 1); assert_int_equal(vf->read(vf, buf, size), size); - assert_string_equal(buf, "{\"a\":true}"); + assert_string_equal(buf, "{\n\t\"a\":true\n}"); free(buf); vf->close(vf); @@ -248,7 +248,7 @@ M_TEST_DEFINE(serializeNil) { ssize_t size = vf->size(vf); char* buf = calloc(1, size + 1); assert_int_equal(vf->read(vf, buf, size), size); - assert_string_equal(buf, "{\"a\":null}"); + assert_string_equal(buf, "{\n\t\"a\":null\n}"); free(buf); vf->close(vf); @@ -269,7 +269,7 @@ M_TEST_DEFINE(serializeString) { ssize_t size = vf->size(vf); char* buf = calloc(1, size + 1); assert_int_equal(vf->read(vf, buf, size), size); - assert_string_equal(buf, "{\"a\":\"hello\"}"); + assert_string_equal(buf, "{\n\t\"a\":\"hello\"\n}"); free(buf); vf->close(vf); @@ -290,7 +290,7 @@ M_TEST_DEFINE(serializeList) { ssize_t size = vf->size(vf); char* buf = calloc(1, size + 1); assert_int_equal(vf->read(vf, buf, size), size); - assert_string_equal(buf, "{\"a\":[1,2]}"); + assert_string_equal(buf, "{\n\t\"a\":[\n\t\t1,\n\t\t2\n\t]\n}"); free(buf); vf->close(vf); @@ -311,7 +311,7 @@ M_TEST_DEFINE(serializeTable) { ssize_t size = vf->size(vf); char* buf = calloc(1, size + 1); assert_int_equal(vf->read(vf, buf, size), size); - assert_string_equal(buf, "{\"a\":{\"b\":1}}"); + assert_string_equal(buf, "{\n\t\"a\":{\n\t\t\"b\":1\n\t}\n}"); free(buf); vf->close(vf); @@ -332,7 +332,7 @@ M_TEST_DEFINE(serializeNullByteString) { ssize_t size = vf->size(vf); char* buf = calloc(1, size + 1); assert_int_equal(vf->read(vf, buf, size), size); - assert_string_equal(buf, "{\"a\":\"a\\u0000b\"}"); + assert_string_equal(buf, "{\n\t\"a\":\"a\\u0000b\"\n}"); free(buf); vf->close(vf); From ff449dc66c77f47ffc2fa8cb7ba5c84b8a556e4b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 8 Feb 2023 17:57:23 -0800 Subject: [PATCH 25/47] Scripting: Fix non-json-c build --- src/script/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt index f79b66d08..f741b9c05 100644 --- a/src/script/CMakeLists.txt +++ b/src/script/CMakeLists.txt @@ -4,7 +4,6 @@ set(SOURCE_FILES input.c socket.c stdlib.c - storage.c types.c) set(TEST_FILES From 123532ed6e80b0ceec0de40cb5b27d4c2db8c9cb Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 8 Feb 2023 19:14:36 -0800 Subject: [PATCH 26/47] Scripting: Add `callbacks:oneshot` for single-call callbacks --- CHANGES | 1 + include/mgba/script/context.h | 1 + src/script/context.c | 99 ++++++++++++++++++++++++----------- src/script/stdlib.c | 11 ++++ src/script/test/stdlib.c | 33 ++++++++++++ 5 files changed, 115 insertions(+), 30 deletions(-) diff --git a/CHANGES b/CHANGES index 5334fd617..40ca1e7a5 100644 --- a/CHANGES +++ b/CHANGES @@ -22,6 +22,7 @@ Misc: - GBA: Improve detection of valid ELF ROMs - Qt: Include wayland QPA in AppImage (fixes mgba.io/i/2796) - Qt: Stop eating boolean action key events (fixes mgba.io/i/2636) + - Scripting: Add `callbacks:oneshot` for single-call callbacks 0.10.1: (2023-01-10) Emulation fixes: diff --git a/include/mgba/script/context.h b/include/mgba/script/context.h index dcec138e1..9e8e5ec23 100644 --- a/include/mgba/script/context.h +++ b/include/mgba/script/context.h @@ -98,6 +98,7 @@ void mScriptContextExportNamespace(struct mScriptContext* context, const char* n void mScriptContextTriggerCallback(struct mScriptContext*, const char* callback, struct mScriptList* args); uint32_t mScriptContextAddCallback(struct mScriptContext*, const char* callback, struct mScriptValue* value); +uint32_t mScriptContextAddOneshot(struct mScriptContext*, const char* callback, struct mScriptValue* value); void mScriptContextRemoveCallback(struct mScriptContext*, uint32_t cbid); void mScriptContextSetDocstring(struct mScriptContext*, const char* key, const char* docstring); diff --git a/src/script/context.c b/src/script/context.c index 65932fd79..e49d2527b 100644 --- a/src/script/context.c +++ b/src/script/context.c @@ -17,8 +17,10 @@ struct mScriptFileInfo { }; struct mScriptCallbackInfo { + struct mScriptValue* fn; const char* callback; - size_t id; + uint32_t id; + bool oneshot; }; static void _engineContextDestroy(void* ctx) { @@ -56,13 +58,28 @@ static void _contextFindForFile(const char* key, void* value, void* user) { } } +static void _freeTable(void* data) { + struct Table* table = data; + + struct TableIterator iter; + if (TableIteratorStart(table, &iter)) { + do { + struct mScriptCallbackInfo* info = TableIteratorGetValue(table, &iter); + mScriptValueDeref(info->fn); + } while (TableIteratorNext(table, &iter)); + } + + TableDeinit(table); + free(table); +} + void mScriptContextInit(struct mScriptContext* context) { HashTableInit(&context->rootScope, 0, (void (*)(void*)) mScriptValueDeref); HashTableInit(&context->engines, 0, _engineContextDestroy); mScriptListInit(&context->refPool, 0); TableInit(&context->weakrefs, 0, (void (*)(void*)) mScriptValueDeref); context->nextWeakref = 1; - HashTableInit(&context->callbacks, 0, (void (*)(void*)) mScriptValueDeref); + HashTableInit(&context->callbacks, 0, _freeTable); TableInit(&context->callbackId, 0, free); context->nextCallbackId = 1; context->constants = NULL; @@ -204,46 +221,60 @@ void mScriptContextDisownWeakref(struct mScriptContext* context, uint32_t weakre } void mScriptContextTriggerCallback(struct mScriptContext* context, const char* callback, struct mScriptList* args) { - struct mScriptValue* list = HashTableLookup(&context->callbacks, callback); - if (!list) { + struct Table* table = HashTableLookup(&context->callbacks, callback); + if (!table) { return; } - size_t i; - for (i = 0; i < mScriptListSize(list->value.list); ++i) { + struct TableIterator iter; + if (!TableIteratorStart(table, &iter)) { + return; + } + + struct UInt32List oneshots; + UInt32ListInit(&oneshots, 0); + do { struct mScriptFrame frame; - struct mScriptValue* fn = mScriptListGetPointer(list->value.list, i); - if (!fn->type) { - continue; - } + struct mScriptCallbackInfo* info = TableIteratorGetValue(table, &iter); mScriptFrameInit(&frame); if (args) { mScriptListCopy(&frame.arguments, args); } - if (fn->type->base == mSCRIPT_TYPE_WRAPPER) { - fn = mScriptValueUnwrap(fn); - } - mScriptInvoke(fn, &frame); + mScriptInvoke(info->fn, &frame); mScriptFrameDeinit(&frame); + + if (info->oneshot) { + *UInt32ListAppend(&oneshots) = info->id; + } + } while (TableIteratorNext(table, &iter)); + + size_t i; + for (i = 0; i < UInt32ListSize(&oneshots); ++i) { + mScriptContextRemoveCallback(context, *UInt32ListGetPointer(&oneshots, i)); } + UInt32ListDeinit(&oneshots); } -uint32_t mScriptContextAddCallback(struct mScriptContext* context, const char* callback, struct mScriptValue* fn) { +static uint32_t mScriptContextAddCallbackInternal(struct mScriptContext* context, const char* callback, struct mScriptValue* fn, bool oneshot) { if (fn->type->base != mSCRIPT_TYPE_FUNCTION) { return 0; } - struct mScriptValue* list = HashTableLookup(&context->callbacks, callback); - if (!list) { - list = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST); - HashTableInsert(&context->callbacks, callback, list); + struct Table* table = HashTableLookup(&context->callbacks, callback); + if (!table) { + table = calloc(1, sizeof(*table)); + TableInit(table, 0, NULL); + HashTableInsert(&context->callbacks, callback, table); } struct mScriptCallbackInfo* info = malloc(sizeof(*info)); // Steal the string from the table key, since it's guaranteed to outlive this struct struct TableIterator iter; HashTableIteratorLookup(&context->callbacks, &iter, callback); info->callback = HashTableIteratorGetKey(&context->callbacks, &iter); - info->id = mScriptListSize(list->value.list); + info->oneshot = oneshot; + if (fn->type->base == mSCRIPT_TYPE_WRAPPER) { + fn = mScriptValueUnwrap(fn); + } + info->fn = fn; mScriptValueRef(fn); - mScriptValueWrap(fn, mScriptListAppend(list->value.list)); while (true) { uint32_t id = context->nextCallbackId; ++context->nextCallbackId; @@ -251,8 +282,19 @@ uint32_t mScriptContextAddCallback(struct mScriptContext* context, const char* c continue; } TableInsert(&context->callbackId, id, info); - return id; + info->id = id; + break; } + TableInsert(table, info->id, info); + return info->id; +} + +uint32_t mScriptContextAddCallback(struct mScriptContext* context, const char* callback, struct mScriptValue* fn) { + return mScriptContextAddCallbackInternal(context, callback, fn, false); +} + +uint32_t mScriptContextAddOneshot(struct mScriptContext* context, const char* callback, struct mScriptValue* fn) { + return mScriptContextAddCallbackInternal(context, callback, fn, true); } void mScriptContextRemoveCallback(struct mScriptContext* context, uint32_t cbid) { @@ -260,16 +302,13 @@ void mScriptContextRemoveCallback(struct mScriptContext* context, uint32_t cbid) if (!info) { return; } - struct mScriptValue* list = HashTableLookup(&context->callbacks, info->callback); - if (!list) { + struct Table* table = HashTableLookup(&context->callbacks, info->callback); + if (!table) { return; } - if (info->id >= mScriptListSize(list->value.list)) { - return; - } - struct mScriptValue* fn = mScriptValueUnwrap(mScriptListGetPointer(list->value.list, info->id)); - mScriptValueDeref(fn); - mScriptListGetPointer(list->value.list, info->id)->type = NULL; + mScriptValueDeref(info->fn); + TableRemove(table, cbid); + TableRemove(&context->callbackId, cbid); } void mScriptContextExportConstants(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* constants) { diff --git a/src/script/stdlib.c b/src/script/stdlib.c index 3163a0d98..7397bb09e 100644 --- a/src/script/stdlib.c +++ b/src/script/stdlib.c @@ -27,12 +27,21 @@ static uint32_t _mScriptCallbackAdd(struct mScriptCallbackManager* adapter, stru return id; } +static uint32_t _mScriptCallbackOneshot(struct mScriptCallbackManager* adapter, struct mScriptString* name, struct mScriptValue* fn) { + if (fn->type->base == mSCRIPT_TYPE_WRAPPER) { + fn = mScriptValueUnwrap(fn); + } + uint32_t id = mScriptContextAddOneshot(adapter->context, name->buffer, fn); + return id; +} + static void _mScriptCallbackRemove(struct mScriptCallbackManager* adapter, uint32_t id) { mScriptContextRemoveCallback(adapter->context, id); } mSCRIPT_DECLARE_STRUCT(mScriptCallbackManager); mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCallbackManager, U32, add, _mScriptCallbackAdd, 2, STR, callback, WRAPPER, function); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCallbackManager, U32, oneshot, _mScriptCallbackOneshot, 2, STR, callback, WRAPPER, function); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCallbackManager, remove, _mScriptCallbackRemove, 1, U32, cbid); static uint64_t mScriptMakeBitmask(struct mScriptList* list) { @@ -83,6 +92,8 @@ mSCRIPT_DEFINE_STRUCT(mScriptCallbackManager) ) mSCRIPT_DEFINE_DOCSTRING("Add a callback of the named type. The returned id can be used to remove it later") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCallbackManager, add) + mSCRIPT_DEFINE_DOCSTRING("Add a one-shot callback of the named type that will be automatically removed after called. The returned id can be used to remove it early") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCallbackManager, oneshot) mSCRIPT_DEFINE_DOCSTRING("Remove a callback with the previously retuned id") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCallbackManager, remove) mSCRIPT_DEFINE_END; diff --git a/src/script/test/stdlib.c b/src/script/test/stdlib.c index 82a4c425b..0f821dbb4 100644 --- a/src/script/test/stdlib.c +++ b/src/script/test/stdlib.c @@ -96,8 +96,41 @@ M_TEST_DEFINE(callbacks) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(oneshot) { + SETUP_LUA; + + TEST_PROGRAM( + "val = 0\n" + "function cb()\n" + " val = val + 1\n" + "end\n" + "id = callbacks:oneshot('test', cb)\n" + "assert(id)" + ); + + TEST_VALUE(S32, "val", 0); + + mScriptContextTriggerCallback(&context, "test", NULL); + TEST_VALUE(S32, "val", 1); + + mScriptContextTriggerCallback(&context, "test", NULL); + TEST_VALUE(S32, "val", 1); + + TEST_PROGRAM( + "id = callbacks:oneshot('test', cb)\n" + "assert(id)\n" + "callbacks:remove(id)" + ); + + mScriptContextTriggerCallback(&context, "test", NULL); + TEST_VALUE(S32, "val", 1); + + mScriptContextDeinit(&context); +} + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStdlib, cmocka_unit_test(bitMask), cmocka_unit_test(bitUnmask), cmocka_unit_test(callbacks), + cmocka_unit_test(oneshot), ) From 466639ee31d7736f48032fd62fe99a69d6c79acc Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 8 Feb 2023 19:17:28 -0800 Subject: [PATCH 27/47] Qt: Fix build without json-c --- src/platform/qt/scripting/ScriptingController.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform/qt/scripting/ScriptingController.cpp b/src/platform/qt/scripting/ScriptingController.cpp index 836e379f1..e92c3ee7f 100644 --- a/src/platform/qt/scripting/ScriptingController.cpp +++ b/src/platform/qt/scripting/ScriptingController.cpp @@ -301,7 +301,9 @@ void ScriptingController::detachGamepad() { void ScriptingController::init() { mScriptContextInit(&m_scriptContext); mScriptContextAttachStdlib(&m_scriptContext); +#ifdef USE_JSON_C mScriptContextAttachStorage(&m_scriptContext); +#endif mScriptContextAttachSocket(&m_scriptContext); mScriptContextAttachInput(&m_scriptContext); mScriptContextRegisterEngines(&m_scriptContext); From 3cbfaa010dafe1c3af10e12c7e78cd628662205d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 8 Feb 2023 20:37:19 -0800 Subject: [PATCH 28/47] Scripting: Add method to enable/disable storage bucket autoflushing --- src/script/storage.c | 19 ++++++++++++++++++- src/script/test/storage.c | 31 +++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/script/storage.c b/src/script/storage.c index f9562a488..f17da67de 100644 --- a/src/script/storage.c +++ b/src/script/storage.c @@ -16,6 +16,7 @@ struct mScriptStorageBucket { char* name; struct mScriptValue* root; + bool autoflush; bool dirty; }; @@ -33,6 +34,7 @@ static void mScriptStorageBucketSetFloat(struct mScriptStorageBucket* bucket, co static void mScriptStorageBucketSetBool(struct mScriptStorageBucket* bucket, const char* key, bool value); static bool mScriptStorageBucketReload(struct mScriptStorageBucket* bucket); static bool mScriptStorageBucketFlush(struct mScriptStorageBucket* bucket); +static void mScriptStorageBucketEnableAutoFlush(struct mScriptStorageBucket* bucket, bool enable); static void mScriptStorageContextDeinit(struct mScriptStorageContext*); static void mScriptStorageContextFlushAll(struct mScriptStorageContext*); @@ -53,6 +55,7 @@ mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setTable, mScriptStorag mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setVoid, mScriptStorageBucketSetVoid, 2, CHARP, key, NUL, value); mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, BOOL, reload, mScriptStorageBucketReload, 0); mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, BOOL, flush, mScriptStorageBucketFlush, 0); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, enableAutoFlush, mScriptStorageBucketEnableAutoFlush, 1, BOOL, enable); mSCRIPT_DEFINE_STRUCT(mScriptStorageBucket) mSCRIPT_DEFINE_CLASS_DOCSTRING( @@ -76,6 +79,13 @@ mSCRIPT_DEFINE_STRUCT(mScriptStorageBucket) mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageBucket, reload) mSCRIPT_DEFINE_DOCSTRING("Flush the bucket to disk manually") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageBucket, flush) + mSCRIPT_DEFINE_DOCSTRING( + "Enable or disable the automatic flushing of this bucket. This is good for ensuring buckets " + "don't get flushed in an inconsistent state. It will also disable flushing to disk when the " + "emulator is shut down, so make sure to either manually flush the bucket or re-enable " + "automatic flushing whenever you're done updating it if you do disable it prior." + ) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageBucket, enableAutoFlush) mSCRIPT_DEFINE_END; mSCRIPT_DECLARE_STRUCT(mScriptStorageContext); @@ -300,6 +310,10 @@ bool mScriptStorageBucketFlush(struct mScriptStorageBucket* bucket) { return _mScriptStorageBucketFlushVF(bucket, vf); } +void mScriptStorageBucketEnableAutoFlush(struct mScriptStorageBucket* bucket, bool enable) { + bucket->autoflush = enable; +} + bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* bucketName, struct VFile* vf) { struct mScriptValue* value = mScriptContextGetGlobal(context, "storage"); if (!value) { @@ -481,7 +495,9 @@ void mScriptStorageContextFlushAll(struct mScriptStorageContext* storage) { if (HashTableIteratorStart(&storage->buckets, &iter)) { do { struct mScriptStorageBucket* bucket = HashTableIteratorGetValue(&storage->buckets, &iter); - mScriptStorageBucketFlush(bucket); + if (bucket->autoflush) { + mScriptStorageBucketFlush(bucket); + } } while (HashTableIteratorNext(&storage->buckets, &iter)); } } @@ -509,6 +525,7 @@ struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContex bucket = calloc(1, sizeof(*bucket)); bucket->name = strdup(name); + bucket->autoflush = true; if (!mScriptStorageBucketReload(bucket)) { bucket->root = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); } diff --git a/src/script/test/storage.c b/src/script/test/storage.c index d735a1f87..e9bb535ba 100644 --- a/src/script/test/storage.c +++ b/src/script/test/storage.c @@ -522,6 +522,36 @@ M_TEST_DEFINE(structuredRoundTrip) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(autoflush) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + + TEST_PROGRAM("bucket:enableAutoFlush(true)") + TEST_PROGRAM("bucket.a = 1"); + TEST_PROGRAM("storage:flushAll()"); + TEST_PROGRAM("assert(bucket:reload())") + TEST_PROGRAM("assert(bucket.a == 1)"); + + TEST_PROGRAM("bucket:enableAutoFlush(false)") + TEST_PROGRAM("bucket.a = 2"); + TEST_PROGRAM("storage:flushAll()"); + TEST_PROGRAM("assert(bucket:reload())") + TEST_PROGRAM("assert(bucket.a == 1)"); + + TEST_PROGRAM("bucket:enableAutoFlush(false)") + TEST_PROGRAM("bucket.a = 3"); + TEST_PROGRAM("storage:flushAll()"); + TEST_PROGRAM("bucket:enableAutoFlush(true)") + TEST_PROGRAM("storage:flushAll()"); + TEST_PROGRAM("assert(bucket:reload())") + TEST_PROGRAM("assert(bucket.a == 3)"); + + mScriptContextDeinit(&context); +} + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage, cmocka_unit_test(basicInt), cmocka_unit_test(basicFloat), @@ -551,4 +581,5 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage, cmocka_unit_test(deserializeNullByteString), cmocka_unit_test(deserializeError), cmocka_unit_test(structuredRoundTrip), + cmocka_unit_test(autoflush), ) From c709aee0f3895285bfb6743c1d56005c84a79fa5 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 8 Feb 2023 21:15:51 -0800 Subject: [PATCH 29/47] Qt: Getting tired of pushing commits to fix the build without json-c --- src/platform/qt/scripting/ScriptingController.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/platform/qt/scripting/ScriptingController.cpp b/src/platform/qt/scripting/ScriptingController.cpp index e92c3ee7f..8b0200956 100644 --- a/src/platform/qt/scripting/ScriptingController.cpp +++ b/src/platform/qt/scripting/ScriptingController.cpp @@ -149,7 +149,9 @@ void ScriptingController::runCode(const QString& code) { } void ScriptingController::flushStorage() { +#ifdef USE_JSON_C mScriptStorageFlushAll(&m_scriptContext); +#endif } bool ScriptingController::eventFilter(QObject* obj, QEvent* ev) { @@ -320,7 +322,9 @@ void ScriptingController::init() { m_activeEngine = *m_engines.begin(); } +#ifdef USE_JSON_C m_storageFlush.start(); +#endif } uint32_t ScriptingController::qtToScriptingKey(const QKeyEvent* event) { From cade5eebde3a39e0c39e3d9cefd9caa233e43d29 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 9 Feb 2023 00:08:45 -0800 Subject: [PATCH 30/47] Qt: Properly cap number of attached players by platform (fixes #2807) --- CHANGES | 1 + src/platform/qt/MultiplayerController.cpp | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 40ca1e7a5..398934294 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,7 @@ Other fixes: - Qt: Fix crash when attempting to use OpenGL 2.1 to 3.1 (fixes mgba.io/i/2794) - Qt: Disable sync while running scripts from main thread (fixes mgba.io/i/2738) - Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560) + - Qt: Properly cap number of attached players by platform (fixes mgba.io/i/2807) Misc: - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - GBA: Improve detection of valid ELF ROMs diff --git a/src/platform/qt/MultiplayerController.cpp b/src/platform/qt/MultiplayerController.cpp index f44323603..a45f287a9 100644 --- a/src/platform/qt/MultiplayerController.cpp +++ b/src/platform/qt/MultiplayerController.cpp @@ -214,10 +214,6 @@ MultiplayerController::~MultiplayerController() { } bool MultiplayerController::attachGame(CoreController* controller) { - if (m_lockstep.attached == MAX_GBAS) { - return false; - } - if (m_lockstep.attached == 0) { switch (controller->platform()) { #ifdef M_CORE_GBA @@ -243,6 +239,10 @@ bool MultiplayerController::attachGame(CoreController* controller) { switch (controller->platform()) { #ifdef M_CORE_GBA case mPLATFORM_GBA: { + if (m_lockstep.attached >= MAX_GBAS) { + return false; + } + GBA* gba = static_cast(thread->core->board); GBASIOLockstepNode* node = new GBASIOLockstepNode; @@ -259,6 +259,10 @@ bool MultiplayerController::attachGame(CoreController* controller) { #endif #ifdef M_CORE_GB case mPLATFORM_GB: { + if (m_lockstep.attached >= 2) { + return false; + } + GB* gb = static_cast(thread->core->board); GBSIOLockstepNode* node = new GBSIOLockstepNode; From 3bacc33ebe48ea6178f4dc7e5b12256794948511 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 9 Feb 2023 00:17:55 -0800 Subject: [PATCH 31/47] Qt: Disable attempted linking betwen incompatible platforms (fixes #2702) --- CHANGES | 1 + src/platform/qt/MultiplayerController.cpp | 3 +++ src/platform/qt/MultiplayerController.h | 3 +++ 3 files changed, 7 insertions(+) diff --git a/CHANGES b/CHANGES index 398934294..8f69806c9 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,7 @@ Other fixes: - Qt: Disable sync while running scripts from main thread (fixes mgba.io/i/2738) - Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560) - Qt: Properly cap number of attached players by platform (fixes mgba.io/i/2807) + - Qt: Disable attempted linking betwen incompatible platforms (fixes mgba.io/i/2702) Misc: - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - GBA: Improve detection of valid ELF ROMs diff --git a/src/platform/qt/MultiplayerController.cpp b/src/platform/qt/MultiplayerController.cpp index a45f287a9..618efd1f6 100644 --- a/src/platform/qt/MultiplayerController.cpp +++ b/src/platform/qt/MultiplayerController.cpp @@ -229,6 +229,9 @@ bool MultiplayerController::attachGame(CoreController* controller) { default: return false; } + m_platform = controller->platform(); + } else if (controller->platform() != m_platform) { + return false; } mCoreThread* thread = controller->thread(); diff --git a/src/platform/qt/MultiplayerController.h b/src/platform/qt/MultiplayerController.h index 5ad6124db..35cae13bb 100644 --- a/src/platform/qt/MultiplayerController.h +++ b/src/platform/qt/MultiplayerController.h @@ -9,6 +9,7 @@ #include #include +#include #include #ifdef M_CORE_GBA #include @@ -77,6 +78,8 @@ private: GBASIOLockstep m_gbaLockstep; #endif }; + + mPlatform m_platform = mPLATFORM_NONE; QList m_players; QMutex m_lock; }; From 1acaa45ea56e7dddd71d3dae5a1205dc669b11a0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 9 Feb 2023 00:25:50 -0800 Subject: [PATCH 32/47] README: Minor updates --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 20507604e..78b69f255 100644 --- a/README.md +++ b/README.md @@ -125,9 +125,9 @@ Compiling requires using CMake 3.1 or newer. GCC, Clang, and Visual Studio 2019 #### Docker building -The recommended way to build for most platforms is to use Docker. Several Docker images are provided that contain the requisite toolchain and dependencies for building mGBA across several platforms. +The recommended way to build for most platforms is to use Docker. Several Docker images are provided that contain the requisite toolchain and dependencies for building mGBA across several platforms. -Note: If you are on an older Windows system before Windows 10, you may need to configure your Docker to use VirtualBox shared folders to correctly map your current `mgba` checkout directory to the Docker image's working directory. (See issue [#1985](https://mgba.io/i/1985) for details.) +Note: If you are on an older Windows system before Windows 10, you may need to configure your Docker to use VirtualBox shared folders to correctly map your current `mgba` checkout directory to the Docker image's working directory. (See issue [#1985](https://mgba.io/i/1985) for details.) To use a Docker image to build mGBA, simply run the following command while in the root of an mGBA checkout: @@ -234,6 +234,7 @@ mGBA has no hard dependencies, however, the following optional dependencies are - SQLite3: for game databases. - libelf: for ELF loading. - Lua: for scripting. +- json-c: for the scripting `storage` API. SQLite3, libpng, and zlib are included with the emulator, so they do not need to be externally compiled first. @@ -254,7 +255,7 @@ Footnotes Copyright --------- -mGBA is Copyright © 2013 – 2022 Jeffrey Pfau. It is distributed under the [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/). A copy of the license is available in the distributed LICENSE file. +mGBA is Copyright © 2013 – 2023 Jeffrey Pfau. It is distributed under the [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/). A copy of the license is available in the distributed LICENSE file. mGBA contains the following third-party libraries: From 1722fe4530e71a6174eb46496fcdfaa681d02314 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 9 Feb 2023 19:59:55 -0800 Subject: [PATCH 33/47] Qt: Fix modifier key names in shortcut editor (fixes #2817) --- CHANGES | 1 + src/platform/qt/KeyEditor.cpp | 31 ++----------------------------- src/platform/qt/ShortcutModel.cpp | 5 +++-- src/platform/qt/utils.cpp | 24 ++++++++++++++++++++++++ src/platform/qt/utils.h | 2 ++ 5 files changed, 32 insertions(+), 31 deletions(-) diff --git a/CHANGES b/CHANGES index 8f69806c9..700a9d6e0 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,7 @@ Other fixes: - Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560) - Qt: Properly cap number of attached players by platform (fixes mgba.io/i/2807) - Qt: Disable attempted linking betwen incompatible platforms (fixes mgba.io/i/2702) + - Qt: Fix modifier key names in shortcut editor (fixes mgba.io/i/2817) Misc: - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - GBA: Improve detection of valid ELF ROMs diff --git a/src/platform/qt/KeyEditor.cpp b/src/platform/qt/KeyEditor.cpp index e8da70970..7d3ba1990 100644 --- a/src/platform/qt/KeyEditor.cpp +++ b/src/platform/qt/KeyEditor.cpp @@ -8,6 +8,7 @@ #include "input/GamepadAxisEvent.h" #include "input/GamepadButtonEvent.h" #include "ShortcutController.h" +#include "utils.h" #include #include @@ -33,35 +34,7 @@ void KeyEditor::setValue(int key) { if (key < 0) { setText(tr("---")); } else { - QKeySequence seq(key); - switch (key) { -#ifndef Q_OS_MAC - case Qt::Key_Shift: - setText(QCoreApplication::translate("QShortcut", "Shift")); - break; - case Qt::Key_Control: - setText(QCoreApplication::translate("QShortcut", "Control")); - break; - case Qt::Key_Alt: - setText(QCoreApplication::translate("QShortcut", "Alt")); - break; - case Qt::Key_Meta: - setText(QCoreApplication::translate("QShortcut", "Meta")); - break; -#endif - case Qt::Key_Super_L: - setText(tr("Super (L)")); - break; - case Qt::Key_Super_R: - setText(tr("Super (R)")); - break; - case Qt::Key_Menu: - setText(tr("Menu")); - break; - default: - setText(QKeySequence(key).toString(QKeySequence::NativeText)); - break; - } + setText(keyName(key)); } } emit valueChanged(key); diff --git a/src/platform/qt/ShortcutModel.cpp b/src/platform/qt/ShortcutModel.cpp index 54fa83fcf..b73982cad 100644 --- a/src/platform/qt/ShortcutModel.cpp +++ b/src/platform/qt/ShortcutModel.cpp @@ -6,6 +6,7 @@ #include "ShortcutModel.h" #include "ShortcutController.h" +#include "utils.h" using namespace QGBA; @@ -33,7 +34,7 @@ QVariant ShortcutModel::data(const QModelIndex& index, int role) const { case 0: return m_controller->visibleName(item->name); case 1: - return shortcut ? QKeySequence(shortcut->shortcut()).toString(QKeySequence::NativeText) : QVariant(); + return shortcut ? keyName(shortcut->shortcut()) : QVariant(); case 2: if (!shortcut) { return QVariant(); @@ -134,4 +135,4 @@ void ShortcutModel::clearMenu(const QString&) { // TODO beginResetModel(); endResetModel(); -} \ No newline at end of file +} diff --git a/src/platform/qt/utils.cpp b/src/platform/qt/utils.cpp index d6da29781..7a9cd3bd7 100644 --- a/src/platform/qt/utils.cpp +++ b/src/platform/qt/utils.cpp @@ -6,6 +6,7 @@ #include "utils.h" #include +#include #include #include "VFileDevice.h" @@ -129,4 +130,27 @@ bool extractMatchingFile(VDir* dir, std::function filter) return false; } +QString keyName(int key) { + switch (key) { +#ifndef Q_OS_MAC + case Qt::Key_Shift: + return QCoreApplication::translate("QShortcut", "Shift"); + case Qt::Key_Control: + return QCoreApplication::translate("QShortcut", "Control"); + case Qt::Key_Alt: + return QCoreApplication::translate("QShortcut", "Alt"); + case Qt::Key_Meta: + return QCoreApplication::translate("QShortcut", "Meta"); +#endif + case Qt::Key_Super_L: + return QObject::tr("Super (L)"); + case Qt::Key_Super_R: + return QObject::tr("Super (R)"); + case Qt::Key_Menu: + return QObject::tr("Menu"); + default: + return QKeySequence(key).toString(QKeySequence::NativeText); + } +} + } diff --git a/src/platform/qt/utils.h b/src/platform/qt/utils.h index 72ce74c30..0ce974bc5 100644 --- a/src/platform/qt/utils.h +++ b/src/platform/qt/utils.h @@ -75,4 +75,6 @@ constexpr const T& clamp(const T& v, const T& lo, const T& hi) { QString romFilters(bool includeMvl = false); bool extractMatchingFile(VDir* dir, std::function filter); +QString keyName(int key); + } From 30fa0a384345a281a3728f0c1752fa54f4ed24ff Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 11 Feb 2023 21:08:31 -0800 Subject: [PATCH 34/47] OpenGL: Fix null calloc/memcpy --- src/platform/opengl/gles2.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index 4277b7b76..2a8d91297 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -1010,8 +1010,11 @@ bool mGLES2ShaderLoad(struct VideoShader* shader, struct VDir* dir) { } } u = mGLES2UniformListSize(&uniformVector); - struct mGLES2Uniform* uniformBlock = calloc(u, sizeof(*uniformBlock)); - memcpy(uniformBlock, mGLES2UniformListGetPointer(&uniformVector, 0), sizeof(*uniformBlock) * u); + struct mGLES2Uniform* uniformBlock; + if (u) { + uniformBlock = calloc(u, sizeof(*uniformBlock)); + memcpy(uniformBlock, mGLES2UniformListGetPointer(&uniformVector, 0), sizeof(*uniformBlock) * u); + } mGLES2UniformListDeinit(&uniformVector); mGLES2ShaderInit(&shaderBlock[n], vssrc, fssrc, width, height, scaling, uniformBlock, u); From 7b979a679ef86b7ab944bc26aa974fcaf382ca54 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 11 Feb 2023 21:09:29 -0800 Subject: [PATCH 35/47] Res: Port hq2x from SameBoy --- res/shaders/hq2x.shader/hq2x.fs | 143 +++++++++++++++++++++++++++ res/shaders/hq2x.shader/manifest.ini | 11 +++ 2 files changed, 154 insertions(+) create mode 100644 res/shaders/hq2x.shader/hq2x.fs create mode 100644 res/shaders/hq2x.shader/manifest.ini diff --git a/res/shaders/hq2x.shader/hq2x.fs b/res/shaders/hq2x.shader/hq2x.fs new file mode 100644 index 000000000..97019bd3c --- /dev/null +++ b/res/shaders/hq2x.shader/hq2x.fs @@ -0,0 +1,143 @@ +/* MIT License +* +* Copyright (c) 2015-2023 Lior Halphon +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ +/* Based on this (really good) article: http://blog.pkh.me/p/19-butchering-hqx-scaling-filters.html */ + +/* The colorspace used by the HQnx filters is not really YUV, despite the algorithm description claims it is. It is + also not normalized. Therefore, we shall call the colorspace used by HQnx "HQ Colorspace" to avoid confusion. */ +varying vec2 texCoord; +uniform sampler2D tex; +uniform vec2 texSize; + +vec3 rgb_to_hq_colospace(vec4 rgb) +{ + return vec3( 0.250 * rgb.r + 0.250 * rgb.g + 0.250 * rgb.b, + 0.250 * rgb.r - 0.000 * rgb.g - 0.250 * rgb.b, + -0.125 * rgb.r + 0.250 * rgb.g - 0.125 * rgb.b); +} + +bool is_different(vec4 a, vec4 b) +{ + vec3 diff = abs(rgb_to_hq_colospace(a) - rgb_to_hq_colospace(b)); + return diff.x > 0.018 || diff.y > 0.002 || diff.z > 0.005; +} + +#define P(m, r) ((pattern & (m)) == (r)) + +vec4 interp_2px(vec4 c1, float w1, vec4 c2, float w2) +{ + return (c1 * w1 + c2 * w2) / (w1 + w2); +} + +vec4 interp_3px(vec4 c1, float w1, vec4 c2, float w2, vec4 c3, float w3) +{ + return (c1 * w1 + c2 * w2 + c3 * w3) / (w1 + w2 + w3); +} + +vec4 scale(sampler2D image, vec2 position, vec2 input_resolution) +{ + // o = offset, the width of a pixel + vec2 o = vec2(1, 1) / input_resolution; + + /* We always calculate the top left pixel. If we need a different pixel, we flip the image */ + + // p = the position within a pixel [0...1] + vec2 p = fract(position * input_resolution); + + if (p.x > 0.5) o.x = -o.x; + if (p.y > 0.5) o.y = -o.y; + + vec4 w0 = texture2D(image, position + vec2( -o.x, -o.y)); + vec4 w1 = texture2D(image, position + vec2( 0, -o.y)); + vec4 w2 = texture2D(image, position + vec2( o.x, -o.y)); + vec4 w3 = texture2D(image, position + vec2( -o.x, 0)); + vec4 w4 = texture2D(image, position + vec2( 0, 0)); + vec4 w5 = texture2D(image, position + vec2( o.x, 0)); + vec4 w6 = texture2D(image, position + vec2( -o.x, o.y)); + vec4 w7 = texture2D(image, position + vec2( 0, o.y)); + vec4 w8 = texture2D(image, position + vec2( o.x, o.y)); + + int pattern = 0; + if (is_different(w0, w4)) pattern |= 1; + if (is_different(w1, w4)) pattern |= 2; + if (is_different(w2, w4)) pattern |= 4; + if (is_different(w3, w4)) pattern |= 8; + if (is_different(w5, w4)) pattern |= 16; + if (is_different(w6, w4)) pattern |= 32; + if (is_different(w7, w4)) pattern |= 64; + if (is_different(w8, w4)) pattern |= 128; + + if ((P(0xBF,0x37) || P(0xDB,0x13)) && is_different(w1, w5)) { + return interp_2px(w4, 3.0, w3, 1.0); + } + if ((P(0xDB,0x49) || P(0xEF,0x6D)) && is_different(w7, w3)) { + return interp_2px(w4, 3.0, w1, 1.0); + } + if ((P(0x0B,0x0B) || P(0xFE,0x4A) || P(0xFE,0x1A)) && is_different(w3, w1)) { + return w4; + } + if ((P(0x6F,0x2A) || P(0x5B,0x0A) || P(0xBF,0x3A) || P(0xDF,0x5A) || + P(0x9F,0x8A) || P(0xCF,0x8A) || P(0xEF,0x4E) || P(0x3F,0x0E) || + P(0xFB,0x5A) || P(0xBB,0x8A) || P(0x7F,0x5A) || P(0xAF,0x8A) || + P(0xEB,0x8A)) && is_different(w3, w1)) { + return interp_2px(w4, 3.0, w0, 1.0); + } + if (P(0x0B,0x08)) { + return interp_3px(w4, 2.0, w0, 1.0, w1, 1.0); + } + if (P(0x0B,0x02)) { + return interp_3px(w4, 2.0, w0, 1.0, w3, 1.0); + } + if (P(0x2F,0x2F)) { + return interp_3px(w4, 4.0, w3, 1.0, w1, 1.0); + } + if (P(0xBF,0x37) || P(0xDB,0x13)) { + return interp_3px(w4, 5.0, w1, 2.0, w3, 1.0); + } + if (P(0xDB,0x49) || P(0xEF,0x6D)) { + return interp_3px(w4, 5.0, w3, 2.0, w1, 1.0); + } + if (P(0x1B,0x03) || P(0x4F,0x43) || P(0x8B,0x83) || P(0x6B,0x43)) { + return interp_2px(w4, 3.0, w3, 1.0); + } + if (P(0x4B,0x09) || P(0x8B,0x89) || P(0x1F,0x19) || P(0x3B,0x19)) { + return interp_2px(w4, 3.0, w1, 1.0); + } + if (P(0x7E,0x2A) || P(0xEF,0xAB) || P(0xBF,0x8F) || P(0x7E,0x0E)) { + return interp_3px(w4, 2.0, w3, 3.0, w1, 3.0); + } + if (P(0xFB,0x6A) || P(0x6F,0x6E) || P(0x3F,0x3E) || P(0xFB,0xFA) || + P(0xDF,0xDE) || P(0xDF,0x1E)) { + return interp_2px(w4, 3.0, w0, 1.0); + } + if (P(0x0A,0x00) || P(0x4F,0x4B) || P(0x9F,0x1B) || P(0x2F,0x0B) || + P(0xBE,0x0A) || P(0xEE,0x0A) || P(0x7E,0x0A) || P(0xEB,0x4B) || + P(0x3B,0x1B)) { + return interp_3px(w4, 2.0, w3, 1.0, w1, 1.0); + } + + return interp_3px(w4, 6.0, w3, 1.0, w1, 1.0); +} + +void main() { + gl_FragColor = scale(tex, texCoord, texSize); +} diff --git a/res/shaders/hq2x.shader/manifest.ini b/res/shaders/hq2x.shader/manifest.ini new file mode 100644 index 000000000..06c457e81 --- /dev/null +++ b/res/shaders/hq2x.shader/manifest.ini @@ -0,0 +1,11 @@ +[shader] +name=hq2x +author=Lior Halphson +description="High Quality" 2x scaling +passes=1 + +[pass.0] +fragmentShader=hq2x.fs +blend=0 +width=-2 +height=-2 From 422439f0a63356c3cf123e2f79669af0bf183e34 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 11 Feb 2023 22:03:31 -0800 Subject: [PATCH 36/47] OpenGL: Export output buffer size to shader --- src/platform/opengl/gles2.c | 4 +++- src/platform/opengl/gles2.h | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index 2a8d91297..88ac000a5 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -301,7 +301,8 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST); glUseProgram(shader->program); glUniform1i(shader->texLocation, 0); - glUniform2f(shader->texSizeLocation, context->d.width - padW, context->d.height - padH); + glUniform2f(shader->texSizeLocation, context->d.width, context->d.height); + glUniform2f(shader->outputSizeLocation, drawW, drawH); #ifdef BUILD_GLES3 if (shader->vao != (GLuint) -1) { glBindVertexArray(shader->vao); @@ -532,6 +533,7 @@ void mGLES2ShaderInit(struct mGLES2Shader* shader, const char* vs, const char* f shader->texLocation = glGetUniformLocation(shader->program, "tex"); shader->texSizeLocation = glGetUniformLocation(shader->program, "texSize"); shader->positionLocation = glGetAttribLocation(shader->program, "position"); + shader->outputSizeLocation = glGetUniformLocation(shader->program, "outputSize"); size_t i; for (i = 0; i < shader->nUniforms; ++i) { shader->uniforms[i].location = glGetUniformLocation(shader->program, shader->uniforms[i].name); diff --git a/src/platform/opengl/gles2.h b/src/platform/opengl/gles2.h index 144a64a13..b497b4bfa 100644 --- a/src/platform/opengl/gles2.h +++ b/src/platform/opengl/gles2.h @@ -70,6 +70,7 @@ struct mGLES2Shader { GLuint texLocation; GLuint texSizeLocation; GLuint positionLocation; + GLuint outputSizeLocation; struct mGLES2Uniform* uniforms; size_t nUniforms; From 3139ac7d582ab83c5d9b6b9e545f4b15b63c29b8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 11 Feb 2023 22:07:41 -0800 Subject: [PATCH 37/47] Res: Port OmniScale from SameBoy --- res/shaders/omniscale.shader/manifest.ini | 9 + res/shaders/omniscale.shader/omniscale.fs | 292 ++++++++++++++++++++++ 2 files changed, 301 insertions(+) create mode 100644 res/shaders/omniscale.shader/manifest.ini create mode 100644 res/shaders/omniscale.shader/omniscale.fs diff --git a/res/shaders/omniscale.shader/manifest.ini b/res/shaders/omniscale.shader/manifest.ini new file mode 100644 index 000000000..562abcb90 --- /dev/null +++ b/res/shaders/omniscale.shader/manifest.ini @@ -0,0 +1,9 @@ +[shader] +name=OmniScale +author=Lior Halphson +description=Resolution-indepedent scaler inspired by the hqx family scalers +passes=1 + +[pass.0] +fragmentShader=omniscale.fs +blend=0 diff --git a/res/shaders/omniscale.shader/omniscale.fs b/res/shaders/omniscale.shader/omniscale.fs new file mode 100644 index 000000000..d6b71475b --- /dev/null +++ b/res/shaders/omniscale.shader/omniscale.fs @@ -0,0 +1,292 @@ +/* MIT License +* +* Copyright (c) 2015-2023 Lior Halphon +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ +/* OmniScale is derived from the pattern based design of HQnx, but with the following general differences: + - The actual output calculating was completely redesigned as resolution independent graphic generator. This allows + scaling to any factor. + - HQnx approximations that were good enough for a 2x/3x/4x factor were refined, creating smoother gradients. + - "Quarters" can be interpolated in more ways than in the HQnx filters + - If a pattern does not provide enough information to determine the suitable scaling interpolation, up to 16 pixels + per quarter are sampled (in contrast to the usual 9) in order to determine the best interpolation. + */ +varying vec2 texCoord; +uniform sampler2D tex; +uniform vec2 texSize; +uniform vec2 outputSize; + +/* We use the same colorspace as the HQ algorithms. */ +vec3 rgb_to_hq_colospace(vec4 rgb) +{ + return vec3( 0.250 * rgb.r + 0.250 * rgb.g + 0.250 * rgb.b, + 0.250 * rgb.r - 0.000 * rgb.g - 0.250 * rgb.b, + -0.125 * rgb.r + 0.250 * rgb.g - 0.125 * rgb.b); +} + + +bool is_different(vec4 a, vec4 b) +{ + vec3 diff = abs(rgb_to_hq_colospace(a) - rgb_to_hq_colospace(b)); + return diff.x > 0.018 || diff.y > 0.002 || diff.z > 0.005; +} + +#define P(m, r) ((pattern & (m)) == (r)) + +vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + // o = offset, the width of a pixel + vec2 o = vec2(1, 1) / input_resolution; + + /* We always calculate the top left quarter. If we need a different quarter, we flip our co-ordinates */ + + // p = the position within a pixel [0...1] + vec2 p = fract(position * input_resolution); + + if (p.x > 0.5) { + o.x = -o.x; + p.x = 1.0 - p.x; + } + if (p.y > 0.5) { + o.y = -o.y; + p.y = 1.0 - p.y; + } + + vec4 w0 = texture2D(image, position + vec2( -o.x, -o.y)); + vec4 w1 = texture2D(image, position + vec2( 0, -o.y)); + vec4 w2 = texture2D(image, position + vec2( o.x, -o.y)); + vec4 w3 = texture2D(image, position + vec2( -o.x, 0)); + vec4 w4 = texture2D(image, position + vec2( 0, 0)); + vec4 w5 = texture2D(image, position + vec2( o.x, 0)); + vec4 w6 = texture2D(image, position + vec2( -o.x, o.y)); + vec4 w7 = texture2D(image, position + vec2( 0, o.y)); + vec4 w8 = texture2D(image, position + vec2( o.x, o.y)); + + int pattern = 0; + if (is_different(w0, w4)) pattern |= 1 << 0; + if (is_different(w1, w4)) pattern |= 1 << 1; + if (is_different(w2, w4)) pattern |= 1 << 2; + if (is_different(w3, w4)) pattern |= 1 << 3; + if (is_different(w5, w4)) pattern |= 1 << 4; + if (is_different(w6, w4)) pattern |= 1 << 5; + if (is_different(w7, w4)) pattern |= 1 << 6; + if (is_different(w8, w4)) pattern |= 1 << 7; + + if ((P(0xBF,0x37) || P(0xDB,0x13)) && is_different(w1, w5)) { + return mix(w4, w3, 0.5 - p.x); + } + if ((P(0xDB,0x49) || P(0xEF,0x6D)) && is_different(w7, w3)) { + return mix(w4, w1, 0.5 - p.y); + } + if ((P(0x0B,0x0B) || P(0xFE,0x4A) || P(0xFE,0x1A)) && is_different(w3, w1)) { + return w4; + } + if ((P(0x6F,0x2A) || P(0x5B,0x0A) || P(0xBF,0x3A) || P(0xDF,0x5A) || + P(0x9F,0x8A) || P(0xCF,0x8A) || P(0xEF,0x4E) || P(0x3F,0x0E) || + P(0xFB,0x5A) || P(0xBB,0x8A) || P(0x7F,0x5A) || P(0xAF,0x8A) || + P(0xEB,0x8A)) && is_different(w3, w1)) { + return mix(w4, mix(w4, w0, 0.5 - p.x), 0.5 - p.y); + } + if (P(0x0B,0x08)) { + return mix(mix(w0 * 0.375 + w1 * 0.25 + w4 * 0.375, w4 * 0.5 + w1 * 0.5, p.x * 2.0), w4, p.y * 2.0); + } + if (P(0x0B,0x02)) { + return mix(mix(w0 * 0.375 + w3 * 0.25 + w4 * 0.375, w4 * 0.5 + w3 * 0.5, p.y * 2.0), w4, p.x * 2.0); + } + if (P(0x2F,0x2F)) { + float dist = length(p - vec2(0.5)); + float pixel_size = length(1.0 / (output_resolution / input_resolution)); + if (dist < 0.5 - pixel_size / 2) { + return w4; + } + vec4 r; + if (is_different(w0, w1) || is_different(w0, w3)) { + r = mix(w1, w3, p.y - p.x + 0.5); + } + else { + r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + + if (dist > 0.5 + pixel_size / 2) { + return r; + } + return mix(w4, r, (dist - 0.5 + pixel_size / 2) / pixel_size); + } + if (P(0xBF,0x37) || P(0xDB,0x13)) { + float dist = p.x - 2.0 * p.y; + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); + if (dist > pixel_size / 2) { + return w1; + } + vec4 r = mix(w3, w4, p.x + 0.5); + if (dist < -pixel_size / 2) { + return r; + } + return mix(r, w1, (dist + pixel_size / 2) / pixel_size); + } + if (P(0xDB,0x49) || P(0xEF,0x6D)) { + float dist = p.y - 2.0 * p.x; + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); + if (p.y - 2.0 * p.x > pixel_size / 2) { + return w3; + } + vec4 r = mix(w1, w4, p.x + 0.5); + if (dist < -pixel_size / 2) { + return r; + } + return mix(r, w3, (dist + pixel_size / 2) / pixel_size); + } + if (P(0xBF,0x8F) || P(0x7E,0x0E)) { + float dist = p.x + 2.0 * p.y; + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); + + if (dist > 1.0 + pixel_size / 2) { + return w4; + } + + vec4 r; + if (is_different(w0, w1) || is_different(w0, w3)) { + r = mix(w1, w3, p.y - p.x + 0.5); + } + else { + r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + + if (dist < 1.0 - pixel_size / 2) { + return r; + } + + return mix(r, w4, (dist + pixel_size / 2 - 1.0) / pixel_size); + } + + if (P(0x7E,0x2A) || P(0xEF,0xAB)) { + float dist = p.y + 2.0 * p.x; + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); + + if (p.y + 2.0 * p.x > 1.0 + pixel_size / 2) { + return w4; + } + + vec4 r; + + if (is_different(w0, w1) || is_different(w0, w3)) { + r = mix(w1, w3, p.y - p.x + 0.5); + } + else { + r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + + if (dist < 1.0 - pixel_size / 2) { + return r; + } + + return mix(r, w4, (dist + pixel_size / 2 - 1.0) / pixel_size); + } + + if (P(0x1B,0x03) || P(0x4F,0x43) || P(0x8B,0x83) || P(0x6B,0x43)) { + return mix(w4, w3, 0.5 - p.x); + } + + if (P(0x4B,0x09) || P(0x8B,0x89) || P(0x1F,0x19) || P(0x3B,0x19)) { + return mix(w4, w1, 0.5 - p.y); + } + + if (P(0xFB,0x6A) || P(0x6F,0x6E) || P(0x3F,0x3E) || P(0xFB,0xFA) || + P(0xDF,0xDE) || P(0xDF,0x1E)) { + return mix(w4, w0, (1.0 - p.x - p.y) / 2.0); + } + + if (P(0x4F,0x4B) || P(0x9F,0x1B) || P(0x2F,0x0B) || + P(0xBE,0x0A) || P(0xEE,0x0A) || P(0x7E,0x0A) || P(0xEB,0x4B) || + P(0x3B,0x1B)) { + float dist = p.x + p.y; + float pixel_size = length(1.0 / (output_resolution / input_resolution)); + + if (dist > 0.5 + pixel_size / 2) { + return w4; + } + + vec4 r; + if (is_different(w0, w1) || is_different(w0, w3)) { + r = mix(w1, w3, p.y - p.x + 0.5); + } + else { + r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + + if (dist < 0.5 - pixel_size / 2) { + return r; + } + + return mix(r, w4, (dist + pixel_size / 2 - 0.5) / pixel_size); + } + + if (P(0x0B,0x01)) { + return mix(mix(w4, w3, 0.5 - p.x), mix(w1, (w1 + w3) / 2.0, 0.5 - p.x), 0.5 - p.y); + } + + if (P(0x0B,0x00)) { + return mix(mix(w4, w3, 0.5 - p.x), mix(w1, w0, 0.5 - p.x), 0.5 - p.y); + } + + float dist = p.x + p.y; + float pixel_size = length(1.0 / (output_resolution / input_resolution)); + + if (dist > 0.5 + pixel_size / 2) { + return w4; + } + + /* We need more samples to "solve" this diagonal */ + vec4 x0 = texture2D(image, position + vec2( -o.x * 2.0, -o.y * 2.0)); + vec4 x1 = texture2D(image, position + vec2( -o.x , -o.y * 2.0)); + vec4 x2 = texture2D(image, position + vec2( 0.0 , -o.y * 2.0)); + vec4 x3 = texture2D(image, position + vec2( o.x , -o.y * 2.0)); + vec4 x4 = texture2D(image, position + vec2( -o.x * 2.0, -o.y )); + vec4 x5 = texture2D(image, position + vec2( -o.x * 2.0, 0.0 )); + vec4 x6 = texture2D(image, position + vec2( -o.x * 2.0, o.y )); + + if (is_different(x0, w4)) pattern |= 1 << 8; + if (is_different(x1, w4)) pattern |= 1 << 9; + if (is_different(x2, w4)) pattern |= 1 << 10; + if (is_different(x3, w4)) pattern |= 1 << 11; + if (is_different(x4, w4)) pattern |= 1 << 12; + if (is_different(x5, w4)) pattern |= 1 << 13; + if (is_different(x6, w4)) pattern |= 1 << 14; + + int diagonal_bias = -7; + while (pattern != 0) { + diagonal_bias += pattern & 1; + pattern >>= 1; + } + + if (diagonal_bias <= 0) { + vec4 r = mix(w1, w3, p.y - p.x + 0.5); + if (dist < 0.5 - pixel_size / 2) { + return r; + } + return mix(r, w4, (dist + pixel_size / 2 - 0.5) / pixel_size); + } + + return w4; +} + +void main() { + gl_FragColor = scale(tex, texCoord, texSize, outputSize); +} From b1faf674388d69db64e10045cd0c57d50ecdc1f8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 12 Feb 2023 01:46:05 -0800 Subject: [PATCH 38/47] Scripting: Bucket names can't start with . --- src/script/storage.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/script/storage.c b/src/script/storage.c index f17da67de..b1b740162 100644 --- a/src/script/storage.c +++ b/src/script/storage.c @@ -508,15 +508,22 @@ struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContex } // Check if name is allowed - // Currently only names matching /[0-9A-Za-z_.]+/ are allowed + // Currently only names matching /[0-9A-Za-z][0-9A-Za-z_.]*/ are allowed size_t i; for (i = 0; name[i]; ++i) { if (i >= STORAGE_LEN_MAX) { return NULL; } - if (!isalnum(name[i]) && name[i] != '_' && name[i] != '.') { - return NULL; + if (isalnum(name[i])) { + continue; } + if (name[i] == '_') { + continue; + } + if (i > 0 && name[i] == '.') { + continue; + } + return NULL; } struct mScriptStorageBucket* bucket = HashTableLookup(&storage->buckets, name); if (bucket) { From e10f5997be76615eb611fe8928372cac045d26f0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 12 Feb 2023 12:44:14 -0800 Subject: [PATCH 39/47] Res: Fix name spelling --- res/shaders/hq2x.shader/manifest.ini | 2 +- res/shaders/omniscale.shader/manifest.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/res/shaders/hq2x.shader/manifest.ini b/res/shaders/hq2x.shader/manifest.ini index 06c457e81..c0ab0ef8a 100644 --- a/res/shaders/hq2x.shader/manifest.ini +++ b/res/shaders/hq2x.shader/manifest.ini @@ -1,6 +1,6 @@ [shader] name=hq2x -author=Lior Halphson +author=Lior Halphon description="High Quality" 2x scaling passes=1 diff --git a/res/shaders/omniscale.shader/manifest.ini b/res/shaders/omniscale.shader/manifest.ini index 562abcb90..a98e34ce2 100644 --- a/res/shaders/omniscale.shader/manifest.ini +++ b/res/shaders/omniscale.shader/manifest.ini @@ -1,6 +1,6 @@ [shader] name=OmniScale -author=Lior Halphson +author=Lior Halphon description=Resolution-indepedent scaler inspired by the hqx family scalers passes=1 From 033efff86e52621968ebbf6523b7ab8c2269e07c Mon Sep 17 00:00:00 2001 From: Adam Higerd Date: Sun, 12 Feb 2023 14:57:29 -0600 Subject: [PATCH 40/47] hook frame callback in socket connect --- CHANGES | 1 + src/script/engines/lua.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 700a9d6e0..e84a651af 100644 --- a/CHANGES +++ b/CHANGES @@ -49,6 +49,7 @@ Other fixes: - Qt: Fix initializing update revision info - Qt: Redo stable branch detection heuristic (fixes mgba.io/i/2679) - Res: Fix species name location in Ruby/Sapphire revs 1/2 (fixes mgba.io/i/2685) + - Scripting: Fix receiving packets for client sockets - VFS: Fix minizip write returning 0 on success instead of size Misc: - macOS: Add category to plist (closes mgba.io/i/2691) diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index 3dfe8d0ac..58c13d96f 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -134,7 +134,7 @@ static const char* _socketLuaSource = " end,\n" " connect = function(self, address, port)\n" " local status = self._s:connect(address, port)\n" - " return socket._wrap(status)\n" + " return self:_hook(status)\n" " end,\n" " listen = function(self, backlog)\n" " local status = self._s:listen(backlog or 1)\n" From 0b17a40d6be12a6f3327d18505304f0611007698 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 14 Feb 2023 23:13:04 -0800 Subject: [PATCH 41/47] Qt: Fix a handful of edge cases with graphics viewers (fixes #2827) --- CHANGES | 1 + src/platform/qt/MapView.cpp | 12 +++++++++++- src/platform/qt/TileView.cpp | 4 ++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index e84a651af..a34b063f9 100644 --- a/CHANGES +++ b/CHANGES @@ -20,6 +20,7 @@ Other fixes: - Qt: Properly cap number of attached players by platform (fixes mgba.io/i/2807) - Qt: Disable attempted linking betwen incompatible platforms (fixes mgba.io/i/2702) - Qt: Fix modifier key names in shortcut editor (fixes mgba.io/i/2817) + - Qt: Fix a handful of edge cases with graphics viewers (fixes mgba.io/i/2827) Misc: - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - GBA: Improve detection of valid ELF ROMs diff --git a/src/platform/qt/MapView.cpp b/src/platform/qt/MapView.cpp index da65abf9d..d565f2fea 100644 --- a/src/platform/qt/MapView.cpp +++ b/src/platform/qt/MapView.cpp @@ -42,7 +42,7 @@ MapView::MapView(std::shared_ptr controller, QWidget* parent) #ifdef M_CORE_GBA case mPLATFORM_GBA: m_boundary = 2048; - m_ui.tile->setMaxTile(3096); + m_ui.tile->setMaxTile(3072); m_addressBase = GBA_BASE_VRAM; m_addressWidth = 8; m_ui.bgInfo->addCustomProperty("priority", tr("Priority")); @@ -119,6 +119,9 @@ void MapView::selectMap(int map) { } m_map = map; m_mapStatus.fill({}); + // Different maps can have different max palette counts; set it to + // 0 immediately to avoid tile lookups with state palette IDs break + m_ui.tile->setPalette(0); updateTiles(true); } @@ -184,11 +187,18 @@ void MapView::updateTilesGBA(bool) { frame = GBARegisterDISPCNTGetFrameSelect(io[REG_DISPCNT >> 1]); } } + m_boundary = 1024; + m_ui.tile->setMaxTile(1536); priority = GBARegisterBGCNTGetPriority(io[(REG_BG0CNT >> 1) + m_map]); if (mode == 0 || (mode == 1 && m_map != 2)) { offset = QString("%1, %2") .arg(io[(REG_BG0HOFS >> 1) + (m_map << 1)]) .arg(io[(REG_BG0VOFS >> 1) + (m_map << 1)]); + + if (!GBARegisterBGCNTIs256Color(io[(REG_BG0CNT >> 1) + m_map])) { + m_boundary = 2048; + m_ui.tile->setMaxTile(3072); + } } else if ((mode > 0 && m_map == 2) || (mode == 2 && m_map == 3)) { int32_t refX = io[(REG_BG2X_LO >> 1) + ((m_map - 2) << 2)]; refX |= io[(REG_BG2X_HI >> 1) + ((m_map - 2) << 2)] << 16; diff --git a/src/platform/qt/TileView.cpp b/src/platform/qt/TileView.cpp index 116935e29..7d8d1a71e 100644 --- a/src/platform/qt/TileView.cpp +++ b/src/platform/qt/TileView.cpp @@ -51,7 +51,7 @@ TileView::TileView(std::shared_ptr controller, QWidget* parent) #ifdef M_CORE_GBA case mPLATFORM_GBA: m_ui.tile->setBoundary(2048, 0, 2); - m_ui.tile->setMaxTile(3096); + m_ui.tile->setMaxTile(3072); break; #endif #ifdef M_CORE_GB @@ -76,7 +76,7 @@ TileView::TileView(std::shared_ptr controller, QWidget* parent) #ifdef M_CORE_GBA case mPLATFORM_GBA: m_ui.tile->setBoundary(2048 >> selected, selected, selected + 2); - m_ui.tile->setMaxTile(3096 >> selected); + m_ui.tile->setMaxTile(3072 >> selected); break; #endif #ifdef M_CORE_GB From 6f14732e0d03748242bdcf208a26c8deb833ad3c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 15 Feb 2023 02:29:57 -0800 Subject: [PATCH 42/47] Qt: Fix loading a script leaving sync disabled --- src/platform/qt/scripting/ScriptingController.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/platform/qt/scripting/ScriptingController.cpp b/src/platform/qt/scripting/ScriptingController.cpp index 8b0200956..990cbca9b 100644 --- a/src/platform/qt/scripting/ScriptingController.cpp +++ b/src/platform/qt/scripting/ScriptingController.cpp @@ -111,9 +111,11 @@ bool ScriptingController::load(VFileDevice& vf, const QString& name) { emit error(QString::fromUtf8(m_activeEngine->getError(m_activeEngine))); ok = false; } - if (m_controller && m_controller->isPaused()) { + if (m_controller) { m_controller->setSync(true); - m_controller->paused(); + if (m_controller->isPaused()) { + m_controller->paused(); + } } return ok; } From ea345ca8158d7339ab5d7c4dee4910edd3a01a36 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 15 Feb 2023 16:36:00 -0800 Subject: [PATCH 43/47] Res: Add SGB platform icons --- res/sgb-icon-128.png | Bin 0 -> 2808 bytes res/sgb-icon-16.png | Bin 0 -> 1775 bytes res/sgb-icon-24.png | Bin 0 -> 1987 bytes res/sgb-icon-256.png | Bin 0 -> 3209 bytes res/sgb-icon-32.png | Bin 0 -> 1948 bytes res/sgb-icon-48.png | Bin 0 -> 2180 bytes res/sgb-icon-64.png | Bin 0 -> 2234 bytes res/sgb-icon.svg | 1 + 8 files changed, 1 insertion(+) create mode 100644 res/sgb-icon-128.png create mode 100644 res/sgb-icon-16.png create mode 100644 res/sgb-icon-24.png create mode 100644 res/sgb-icon-256.png create mode 100644 res/sgb-icon-32.png create mode 100644 res/sgb-icon-48.png create mode 100644 res/sgb-icon-64.png create mode 100644 res/sgb-icon.svg diff --git a/res/sgb-icon-128.png b/res/sgb-icon-128.png new file mode 100644 index 0000000000000000000000000000000000000000..473adac4cccf1df8b208f1fcba19addd31eb1392 GIT binary patch literal 2808 zcmah}2~<;O7Jdl{JA{g?VR?iAVaW=CBqkvtA%G--0xDQhWAX^mkQbAO2~cZnXL`on zS}oRU)v8rdanNd2RNRq8K&^~hJ?c2eT3bM!9%v8M`4bdm)R}kQcK>_t_kaK0mK3UU z{Jg`xAqeuz%as>_ufzFxx`Fq_z?6`l@x-SR{0ETh(N|!i*mA{#;%A*K=3l> zmRTXlC(!xepbZ;CA&6LDC@IBCRZ4*ttzu|&=p2M$uQCH`2oh%6%^K}I1cT=wdV@(s z>2GeKzy_U&GL5TZsmwB@(vVwgL5geDCED6~TE32wDfSlH1puH5!8EYF%4o6*>>`Q_ zR{;K<%S;OFGQsAFD5a`GScX~-LDU6|ie)bVfqR*RahzUlU@fUJ zBg`ViidI{+NcI8{y;xV^oN*Bp8tjO%RBix4wSsJjC~OYzzqDoV(#lXHYAFU~hyXX2 zlQVXLyef?znP$*om1AgQ3MFFFzoBumGgdpxVT#EB&chi)D;?8`C>b13h|VH$;g!8r zHJS^7i=PVB0eYz}uQ64gOjc+?D-1@UbG9Lzmn}Yk z!{VgUSsXf>Tf#~eq;dq@3_6P^V2$u`S%d!3X)w+Ivv+m@EYxWQm;o~)E@`AIq(*E+ z8WD{KXDbMd8k1f`vD0-(g{InwQN&jL93j)$HAcfrXP5NKTgV&-Ed&%9TVz!{VYAvF3#Sicv(OS_8%%-s*Qaz|95k*>2VE}on zfk9sz^d`gtvl%?bNX$olY?TPWdv_R_uOH?WIU(~6`Xgt5ZGr&5ku@-xz=&gx&Nv{9 zPD#WBMxzDHz{)>E>L3W8SEww>&M_-p!_@hD@#4kx-piiO%R^n1LnIRGYHdS9L#p=` z9v&XOKXhNa+O1G1e0+RF5}ucr7dI`{*Vo6_*O$X#OESgJpFeMJZ)dYvZEbDw@o`KB zox^1i2yRhPkrJ7JlSa=~W-(Za$@GMzl=!p^Wg^J89etbggByoB*HlIi&_>=rQ+b^6vek3w70yWL^@$t6IH&Lk3%|{R6 z@%ZfQ?A&}Ql}Zr^({gha@*Ht=bd+44C6h_ZXH2(MTk{L#lhxpsO)r}!l4OL1h2$!w z)VP>fYD{o&5MP)U6f{wuE9Ubv()sKO69Ra=bS@9jjg!a)v?OXoWO!kLDpMv%NQ@;% zgpo-8YE`~S%ClNf20O`&8oj(c8H^MUo*Rp2yiho?|xpuYdN>4}krHd<9ELAG=bXtw4muE~&bWBW?yN7#SjZLADM@Ev# zF96oUD=y$D0NjE_axnV`>V(6LG_wm$tP-9(ra%qYE=C8MYLvKkYdJLp5 z`TTm5oByG_W1Q5+UvD;k(AqhAExK_)zAX5>df~%|aaZpQwhpQLV}DHSSAS7l#9F_7 zIb#ax>yk*Ekk0nk2TNZNCAw)p1rmEY_S!4I zb9Y>)cuF9AfWtw>hi=>E2Osui;P3%kLI;}aNLQX_o;=`y7S`SJm~eKmH+b+N>VSHU z!MAQ5=!j~0blm>zYGnV9v&3IsN=bo3pWI&|YVJBwOPw`bcB#qj%x{ybx856Y%$`fS zbG*Vb*w=jI+XjpE!IN+4Tx^|w`IoKlpDjH#v*+ShJ2=~?wQPJ8s92XTen7_6-~F)oPmSRd<Kf59K)gHJu|JA<`)4Tdv4J*%f z-}tF^G5u2C!O)V{`v-TH?f$}Xa3@)^#`JI(^wDM<4aW>RrCE$GjVOV(;ZhU?7{ZmD zfFdL}eas~A5At*OvrgGHpHrkA+fP7!rFW#Z6w*v3J|N_CiO0Xr|E0+?bJZVvzHIF% zd$M4-0IxiCI!@v^Hb`VaLHz(?c)w$AJv`XbTYS0To@ZmyWXI!^Q*PAV8|tsGBF$I} zoxUJ}Lib3#SZtP~Cze2+BsmZwar;{xt`A3`rfymG&E_=1Y9h|FXG=&S4t6a&SwS9D s+Vqd^<~@CH7ek8wrh7I!^wQJ7;rmnF7{TouozpTeTP<(Qn*H&A03h|pS^xk5 literal 0 HcmV?d00001 diff --git a/res/sgb-icon-16.png b/res/sgb-icon-16.png new file mode 100644 index 0000000000000000000000000000000000000000..6a76a156fc15b9f1fdf1b3483821b7f0236faf5b GIT binary patch literal 1775 zcmah~Yi!$86!z3rKu5Q+{a~8}lXm2>i_jd7(032h zGMmlXv$b|bOOlSp#zvB&Nt(7HhE4C0A!pcRz1)<@X?TIoYoZE8MaE4{u2acC7eOE# zUl14gytbOrBuiP~Ng#nVkfBaG><)4{9~k0NG_t;sE|fUZL=c@SphGdS2Hiwf(AAUz z6+NY>`Jf7x$ipifQMy-mF*tQKGRyuqJ)JJth7Nt*C~*siP!QAN0(z`R1!ODGm5jy% zUpLBLxs^CGFHA%j=Gqmo}nAi&qg&c zR4yKije1AmAoqWKvjcFaz%x*U60odXN4O=JvvQ^+nr*;HoSbwKhD`toE+avrNl$h< zNwZ@lalzTL7QLNhfwU8a#%mb^i%A)1xWnFH&*ePtlT87nw{{xI<(pXy&PlEa zKezW~(*ybC-l3_4#v7TRZ^+0`NFbxJsG<4yyMKEa&CUanP|O#LK2lx1v*XctWo1Pz z?eMFiUAw9&5{(JFtp+{rY<54WYf4K?=-Tv^`T6p$pF zK6>=kFV3Gm@$NgFd`GCofA#XkvD2qce{>RR>eSTa^z_ukg|9vwIX*r4-3RX<9~?Y< zW^8n3>hjRgz$YJ%zB2k_9|~q8Yz;@>n;0JG!!8U44ptqxbmL6#GjAj0xuDk_+xES0 zpnXd}wYj*w;^(`Is;V9s+dp6Q)Wp5B$KHf{XI`Q!rmsBl0+bHzxq9gJ^}{vnkbkP8 z`nRF#J-v_J`g?BpRD}5D@s64FUt74XKas3{^PBr_-g6>wq^u^8sJ!tcefSP49G`nu z*w=KiG(Ok&$BwV#TpRTodu_+$f$^fgx4zs|c5CCef9{_}A14e8`oi9k=6(JD0*;7C A^#A|> literal 0 HcmV?d00001 diff --git a/res/sgb-icon-24.png b/res/sgb-icon-24.png new file mode 100644 index 0000000000000000000000000000000000000000..5b8bd7a0d13062bb76735d69c106df77b896a3e3 GIT binary patch literal 1987 zcmah}Yfuwc6kfGdj8Luh2epo_OUtM=*(3;sH9XMi5|%mI=VMtk_L0tETd4Y-WK%7bT#9AdpBj8e~Hv z5!GXHy9C_J$2f+@DN?l_^Vl7zkrM!#qsmdKu>u9E6KEgdG|lc)hLRo&ND@!rcq9^0 zMY2_#;Kw!j`T4k7i)*z?$WV$kA&HDCLt>_EB4NV>L`q(nC{1`L`nW}WV`*p>A~QjFht3`6eh0k5eE76-~!QI!vow2L@q2) zz`PV@FEgGvIWL5Ri2~MRVqj85_!@dlJN+TB{0X3uV>!VEixB8BO{0{@NXKYw5h0+iMW6}28^7LbR>=i96Lb*WV=E@%nCEtOFduQip=v;PO zs~@r&jShkHF)YO77DTOPW3DDYH&>}uB?5tuMnGN0S7Rb2$kM8{*-EumsmXC`atUo7 zq0uVUd4xLQPn->VhbATR|M+qTpgNi&Bt~LETuO9=ft32HfTXO9tN-Z#)!@mlHu!@@wcZ zUf}331?YJGpx+5pE#fNE5EszlC{I*|a7OY4bg z#0LiRz&RcMi2I4y2TTvBmpFq{363{BIp3g=oRA;{$D#n|UrW33Q#d6$-{@fYHvH<)zw9L ztE_h8#S0x(-iiR@{e0KKvd+Pc4C z$F{Sv*!FE(Det0+3eWrRts<)yE_`(Xzhp7TFLt_Z&Qg1A?TXskm4^=OKlt@Nr_0v7 zx2eQ6w|?E4`ua8RX5B1;j%G;CV%O+*>bj?OA*J{4rr)_+Ij-mB?(sKo3|sN@flXr$W8)~y|5 z8hSJ`$_(AKrFtgG?*Rl0fox}Dcnl%%b$X_|Agbkfb; z8SeI-*ILs5_$$3`N=DC>3zsI$n{oZh?U5%s&oOtxJsrR5hVQ>O;>z^18?%;o&n`bd zv}*Cal62+pj&VO-sylk?Nb@Jhst(^eA(Rb0y`jo5W2sSM0=*+9!mz~i9 P14pdpV$+_&syF@x4l1WD literal 0 HcmV?d00001 diff --git a/res/sgb-icon-256.png b/res/sgb-icon-256.png new file mode 100644 index 0000000000000000000000000000000000000000..6e3132c5755cadc66a55c7ce13a50604956c469c GIT binary patch literal 3209 zcmai030M=?7QP7~KmrnRX)91-+^uAlEha1ivL#em1fMJk$s|k(nV1YFfJoF@rS(~N zD}tqnRiN?#MX)Rq7fP`TE`=_DP_6K^Mclv*6^bMHAbS<6EM zY)CUm005hyK))~ma99@yEX}Yc>AUC%tRcvKggyY2eMue?n;TJ0Sin+H((G^x8(66V zS7`tsPci;*AUo%M0L(vzBcjo0p@1tvlIdb8k^s?l$tuhm0G^jlC6**XC^Z3+!%9A_ zzowo>g{6F2l#7rlRQW=QaA2Am3Qr4-kfbF^Jft))PZCeZ#R!riR7}++E0h|pj!!f3 za}7py~Esy zykV|NL{LPNh^WRzy)wl({DvbI?zg+>#HzO$3x)sNJURK5WoS_UwOHWBHsKXbFDB4L zq^clB7^Fc`)Dp;lEf&35rob8F!d(vQAVstvj0IJL?FOI5c5!_TUG*;17f~SUaO@aD zd>YH-WQ-j*C|N9rqF^bS_>%1|37=SPl8evD3t- z`N(82wr{CaEU#g?5>iuHba(n_%*TDSiIDMly)%rAiHmt5j>mXI{^;5#R1nN>^chx} zuoA}@uW{I5yh=h!tTd{z8W_0I7QyQ0gXMw<{{WT1R7}VIMn*<{YrAY|X&D+8Kq8Uw zW_TMLYfDQDD=SM23kxEVNH8bh@i;P>JZaJ-GlH45HJL)O#V+d14;>xt1t0s*n?L7r z%g;=fb9w1^t{yCo3thM(K&R7wv}pdc=~L|NCa0ilynUAh3YPM`+_+r##)i|swqEjE z=Dl~%P9BdpYc@3`IHXlTg8-A%hz^KC&M(Sq2~ z(IJYDo!_&Gh%J|ZhJ}WMjo0vb`>9RXo2DYj&nH6nM_*^s>U@C%@oohR?|;`iU)Oel zmqK?g?hHNH=h-!Km8XY(Se?})@a=fij@})qk>;44`(wsNs-^M1(3D0EJbNV=1Gqu$EUCE1)e0yc&W z-t8jlF?r;nb9IaRY=GO|v8{PW(~al_b9V=R+E)-(sN>N;FJDAnI@>O5*|dfb;MDrf zF{eA$4GFt)rR>6@qI|!@mjnGMgjLh)PH-Nscg)`!{u--+gqu3BLpZ#p=*%4bK;w0* zluc*V>#rSs%mwL9)Q_)77Me>yL&Y{r+LJe#wiNj`6WvQp`3zb7iG z^!s#0l+!JjkT##$8{+j@r;)!mr`<}>%BMAq@D^4jt=x3KAniGBF0r{f{5aq9>5~K3 zk^b#fx%i6CH)#LYmOnfD*bP-&`Lw3E&f)>mYK}17%+S2@o2V~-cll4d z+l^*rbL`FD0I%)eKdH#5khUhuM&Pz3U~T!u-~o7Mc8+IE>yn;7`wkxUFLBWoc09kG z(_??c?O*|sS(8>$)Ku_&g~JyOF`dkNu7 zN{hQ$g7Ykzl`Bgp)#hGlTUm3tgVFP#VOVy!Zf|UN-OwP899g!mJO?k`TJ+vAdnvg( zm>q*hE024ARorHHFWlhrS$q8CGgozC@V4e&r(83A)&@>V&bAHMrO|TN=9H>oI$DZh#y2j&NI$ssKWQt|qCyS#KBR6~m zu!;-%xe_nwU`TUe{{s@d~ca5D~kHE7ej#q7bo5)QY}cFVx-6}@-XAeYV` z&xaAf)7mbc-cm;>HGVUit^h#pBY!oJ6x&UEn$jsf#n`b#vwfDDB0s-wpQMIST0FU| zR!g}~vq*<_Ce|!^rq5Ycow?NEW_d`3gRh=*Wq8{4h4s~c9dgtMo%5R6tX?o>_Xpm& zdwe^N2!6Z(N}4{*1nC7W*530hhHws)+z<5BE$-VEm6}mf36yaWFT{X|N9K<$@@g3d zFK{yh_wY@FF-v_5I9VGQEV8(p%-F4|VHid>@7ja;)<^8Se+p9)a_x5gRLMW|eS^a{ z?>E~_iOH~t?qdG=Y_;rUHGKYwmhi}<=aEhJ4NpeZ#ev4T${fNdaVem<+X9vG<9h7M zeuf`1Ti5i!T!(+WtCyT-`vNe`13!E-lTjNpC#X2RRUno8q(hi;Nuj%6u+q^o!M{qWq&8jhAkzDD4MJXyJzS#TDPB`>b+9?82{S}>9^o#K9o%} zJdAz8kF_DVlU?t1JL;dF0{!5ZN9LC-CV*I%T+kKYS=RKsoh?BgS8>eAn)2B5@p!G*V9tC) literal 0 HcmV?d00001 diff --git a/res/sgb-icon-32.png b/res/sgb-icon-32.png new file mode 100644 index 0000000000000000000000000000000000000000..ea28b979db798f2ecf9f76fc521d2a3f123280af GIT binary patch literal 1948 zcmah}eQXnD7{AF_Mj507<`y&K2MH>2V48uPx&ks#DaJBvw!RP?snJwA5T`wqOneYM0p%;iu*gIYW!YBC0A8I3AD3 z&G8bmD2H)td3iZ*vEeqG2{KGdtDrIolb{r7AyP3Ypaf-3QaMpTwU|tk7*k0MgE%@A zoDHU9OEH<(BeOvq@E{5VRl%)hD?XAB65OyFK^aOHOdPHukZu`8SGmwr=o<1EU5bdR zs6<35Eo#&jY4|ZmnC>ULBpB&QWSahOdNexf7>Zii0uwiM38OSU+&~GmN&xo*MU2To zP}u^rSEMIS%M0P-5`eF#IG9ug-Uf-4*d75Jo&vf=UX*KL5dspk>ONZL2zQhTgF22? zBk5Sf>mDG49|bgJd`v5a#R3P7F;QFo=Q%vxSnX0n-62Z5JHKwmD@N--*A*(|malf`DTmIkb4gl#r4 ztK4LnLs(LJbT;fAmQk7i<7*v&+SwqXaw-pWE7cKBUQJmkljpQHAb3UylUTyUf)Eqq zRm`D;o9wvOF+4ZqtXmJg?f9tBb|{UeiE2(!MY(mvZndC!^lp0Np{QO?1QXP5gTy3R zWMe_V>iL7ZQ>Y?_)HovpXBbuzi8(_d4&GZU3VWIh3qVG#<~in6&eJ~e2!QdPo<@A+ zVIGRJF zg8cm4Tw{%w&NUkI@}|JW{QPGN3gBYyq)C$?Iyg9Z?aJ3XcWmj|x%K$*W2L3DX3l)^ zqy2mPjvVRTx995Riyt5CZEoflcq-i<%ImLcTwVvo_!rYxE?ro==Jmse4s7Y(bmZ{C z{!_?w#wG zi~o7=`rU!w?JNF($SpVJ49q#Ze?xfkDof64XTVt1ZV)p5`mycS;NYSw?xJSt=X=|) zP9M0zC3mCur!Ma7k>0J{H1^U`@A_{YRQC2azPVv+8rP6Zz3_dp<@B^)CJ!>%kaC^WFC8Yo5srA#2+#cN+T)$1>iTwi?OGdF`dYLwyga9w>r}2ZIORw$;+=w6@|{kLlrbMoa%q5X7U+%x3rf?|a|>{_p+o z73s9fAu(fPAP5?wR>}3?&10Xaf#84M_tE>oYmiN%kw8#=>ENg4aA1dd^hzmI+Y#3V zjA)lC*8@R=-(sIIXyc|a5EPzG8cmEzlY?1k2iJ_#c?8$zZ~-&~2{U{yvn8Kk;5@=c zIz`CCrlSZ<;v!^rvWBN|$p|~C3b+YFKx?!F@-68&k|B-}`Y=G?AQ&_3b5KqX<`W@7 zUJQJ*%P0Z|AxypqF==$LjCK=nGB=saL&Pz#(2ZL$y*#TY7+502&M+FMbxPk;&p9Dv|>{7%N~<2XI>EJcVzPIxSC(#4Rp6J|Nhd9;@iAqeopJ>v;w4veOLh zvD2>Jr21{q>wYB=$o={dpV{>~utxLWaEGJcF+7Z70T8aI2>o(;zJbT+cM+(b@X%hj zg-|R2>ct0zV|BrFq>rFXauSH@0o4#8)6!mY=Dxuxqbb^L0GE&uA^czrs~o0wm~F&t z5@+naTz%^~gwyu2gUyWcvRC1KCkf6X=;Jc=`9w&n09-=$f&}SuU+e0v0~X9qL*pR1 zq+nE^OD&V>+_aUXfRAlKSgnwy@Y7RLI0A0yAfRX%h$XFlxtRed0Z)*`;R!hWWFtQX z6Qp8E0uCYQJBA{Af`iucXd&8Xp@7HW#i~`s~@W&d$#5HCswbN?MLI zmVQ#K(`lOy?K|A~^{T>UJVD~h!lhi^yID$U!0%PhP^i=jrAoSPZK)zls?3&1Yov0Oc=2NYnf8<7Oe|A|$>bTC^6APMnbSonsvN0QE|$tga-|p(rATCA zaRxS1pOc?&oj>2IH)`wa_Ec4VQL(wa?bPwpCy!n1XusHTX7~Qboj{ABj9zPi?!kup z+egzpRk`e;|&Eub#0mP?GPw)5+Mb*|uz%vzgrUVB_Ut|4aMcMIztG(Y)a;gRlf zBS%kK?RKax&q&YMi!YA+@jbzacML~O%AKZU|E$PUkBZGyV*Kr4ys-n8JjERu{L+aV zZW>0YMH7k)j-*kGd?)ry_C3CmTC;po;l$+uY3vT^dWmx5oZ@F4ZNJ#2NSP`1frP1D z)n)rnO4nVN+-f{~^QYhLq+MuBefR+VD_vXVjs3fMoc7dl4Ap1lZ8&%H%84@9fii7n zQ#;tUY;4Q{m31?RTmNKN@YjTUe{(dLt@k zaiYr3ROz}}hVB|w6q5iqEIoFa2}^MNF(`AxOisk0A%XGmvKz~mw8q8^JrEhUX5Q!B o;!WHq3BwPbowDP8bN8Q%2IfZ=JF;(lX#kag)C#S=I*-avOEeRwffkYCBAqx1Qgybe!NV02oAwi%9 ze2lafo$6>+>^P`JeBm3bRcN(7i?w!YTVJ(mt+qPUPJN9X?cD@He6&CIZqA~aU1o))JF5|F?PSQ{_s_4pVf zs6@lO1pLi?#!+4v!j>t~0+X4iq5Oa+#>AKaO^f3x{0=8!(PnlBLraOeS(YYnJP-(A zfe9Gpci}?0T#gGwxJbl@2tHHkV{Jjck4fSvA{<)4*!?8Ul9Z3faoS3#a#o3=(9i28 zci4Nl>2kj(%aHa1%<;o4}=T)z**Ay3OMI^0ZzLq zmSWr#-IG+GEqdH91VXuA9ulmt*;gll`UYiT# zlMdG1!`1uU2z;*R9b9Ifa;_96`$%{eQ7>0PuTP0eMX(6D0twR-2$B)qpu9_(q-$q zt1gQ+h6m}(mnTo2{CM`;nY5RtJf1RlC^S3Ak|mQ#WKu~@b@dP5pQb3^g86eEKYqM> zSL=-Fh1**~N`~Rd8$NBq1mlcGy;i3# zESyaFywj%@TCL{HOs!Nd%AT0je(*DiT%=Md^YYB;=?XrUJf5H2vSD4r!unl1w$7as z%FfoSbqZWCE;UU`xgc@bkSb&1>Cmhd?;qVA7++xWEpv2)Qw&dm3R99A4RedmiU4JOsK-Q9zhlDQGywTBcu=C#NQxBlF zjRvjST5#z0^4}`zR72;97reiwDM5Vi(wVBR1NSdIdX%m=sLME$&{Wbzev{Fuv=jx4?V zW^qCX|KxxH4}Oh3vRQni{;zYtRHyXs_xIor<8yIoCeCkYxEmirgv-Ep%<&cTj@N$? zYs#83rLCf3$saqBN5!q`)HtZjoAZu;d>mPcxZYX^-nOAJNJRPYiMv7uP$}&_JW8Oh-W*j}+p+at` zuL!WEwTnb&Vve1BtN-jfRUHVpjt@@=r6<1kQ9pcR*~rp;8}!$Nh_HF)$I>wmU(HL_eDo(ZU&-}4#kc*~7e!s=-rSqfB*qSI+;<@D+N290SknT=BJ@qOXSbPc# hHLg3<&{cbWQ*KjK%Yo`~$=tsb#GuR3Zp|pJ`xg*X`fmUL literal 0 HcmV?d00001 diff --git a/res/sgb-icon.svg b/res/sgb-icon.svg new file mode 100644 index 000000000..ca922f1aa --- /dev/null +++ b/res/sgb-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file From 1ca6f7e0939830275876e55895ebd0da0c67e15c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 22 Feb 2023 19:52:33 -0800 Subject: [PATCH 44/47] Scripting: Add WSAEWOULDBLOCK to error translation table --- src/script/socket.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/script/socket.c b/src/script/socket.c index 818c758f6..5b2e95b60 100644 --- a/src/script/socket.c +++ b/src/script/socket.c @@ -36,6 +36,7 @@ static const struct _mScriptSocketErrorMapping { #ifndef USE_GETHOSTBYNAME #ifdef _WIN32 { WSATRY_AGAIN, mSCRIPT_SOCKERR_AGAIN }, + { WSAEWOULDBLOCK, mSCRIPT_SOCKERR_AGAIN }, { WSANO_RECOVERY, mSCRIPT_SOCKERR_FAILED }, { WSANO_DATA, mSCRIPT_SOCKERR_NO_DATA }, { WSAHOST_NOT_FOUND, mSCRIPT_SOCKERR_NOT_FOUND }, From e07684e3acc492c9ab396f1bd95831f5148d6247 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 22 Feb 2023 20:23:44 -0800 Subject: [PATCH 45/47] CHANGES: Update --- CHANGES | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index a34b063f9..6a2bd8652 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,8 @@ Other fixes: - Qt: Disable attempted linking betwen incompatible platforms (fixes mgba.io/i/2702) - Qt: Fix modifier key names in shortcut editor (fixes mgba.io/i/2817) - Qt: Fix a handful of edge cases with graphics viewers (fixes mgba.io/i/2827) + - Scripting: Fix receiving packets for client sockets + - Scripting: Fix empty receive calls returning unknown error on Windows Misc: - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - GBA: Improve detection of valid ELF ROMs @@ -50,7 +52,6 @@ Other fixes: - Qt: Fix initializing update revision info - Qt: Redo stable branch detection heuristic (fixes mgba.io/i/2679) - Res: Fix species name location in Ruby/Sapphire revs 1/2 (fixes mgba.io/i/2685) - - Scripting: Fix receiving packets for client sockets - VFS: Fix minizip write returning 0 on success instead of size Misc: - macOS: Add category to plist (closes mgba.io/i/2691) From 47941aa0b036b302d05dae49fe60e54fb300c26d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 24 Feb 2023 03:51:07 -0800 Subject: [PATCH 46/47] Qt: Automatically change video file extension as appropriate --- CHANGES | 1 + src/platform/qt/VideoView.cpp | 47 +++++++++++++++++++++++++++++++++++ src/platform/qt/VideoView.h | 4 +++ 3 files changed, 52 insertions(+) diff --git a/CHANGES b/CHANGES index 6a2bd8652..0e14f021b 100644 --- a/CHANGES +++ b/CHANGES @@ -28,6 +28,7 @@ Misc: - GBA: Improve detection of valid ELF ROMs - Qt: Include wayland QPA in AppImage (fixes mgba.io/i/2796) - Qt: Stop eating boolean action key events (fixes mgba.io/i/2636) + - Qt: Automatically change video file extension as appropriate - Scripting: Add `callbacks:oneshot` for single-call callbacks 0.10.1: (2023-01-10) diff --git a/src/platform/qt/VideoView.cpp b/src/platform/qt/VideoView.cpp index 3b6382275..b03949c2f 100644 --- a/src/platform/qt/VideoView.cpp +++ b/src/platform/qt/VideoView.cpp @@ -20,6 +20,7 @@ using namespace QGBA; QMap VideoView::s_acodecMap; QMap VideoView::s_vcodecMap; QMap VideoView::s_containerMap; +QMap VideoView::s_extensionMap; bool VideoView::Preset::compatible(const Preset& other) const { if (!other.container.isNull() && !container.isNull() && other.container != container) { @@ -71,6 +72,23 @@ VideoView::VideoView(QWidget* parent) if (s_containerMap.empty()) { s_containerMap["mkv"] = "matroska"; } + if (s_extensionMap.empty()) { + s_extensionMap["matroska"] += ".mkv"; + s_extensionMap["matroska"] += ".mka"; + s_extensionMap["webm"] += ".webm"; + s_extensionMap["avi"] += ".avi"; + s_extensionMap["mp4"] += ".mp4"; + s_extensionMap["mp4"] += ".m4v"; + s_extensionMap["mp4"] += ".m4a"; + + s_extensionMap["flac"] += ".flac"; + s_extensionMap["mpeg"] += ".mpg"; + s_extensionMap["mpeg"] += ".mpeg"; + s_extensionMap["mpegts"] += ".ts"; + s_extensionMap["mp3"] += ".mp3"; + s_extensionMap["ogg"] += ".ogg"; + s_extensionMap["ogv"] += ".ogv"; + } connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &VideoView::close); connect(m_ui.start, &QAbstractButton::clicked, this, &VideoView::startRecording); @@ -195,6 +213,9 @@ void VideoView::setController(std::shared_ptr controller) { } void VideoView::startRecording() { + if (QFileInfo(m_filename).suffix().isEmpty()) { + changeExtension(); + } if (!validateSettings()) { return; } @@ -238,6 +259,7 @@ void VideoView::selectFile() { QString filename = GBAApp::app()->getSaveFileName(this, tr("Select output file")); if (!filename.isEmpty()) { m_ui.filename->setText(filename); + changeExtension(); } } @@ -289,6 +311,7 @@ void VideoView::setContainer(const QString& container) { m_containerCstr = nullptr; m_container = QString(); } + changeExtension(); validateSettings(); uncheckIncompatible(); } @@ -458,6 +481,30 @@ void VideoView::uncheckIncompatible() { } } +void VideoView::changeExtension() { + if (m_filename.isEmpty()) { + return; + } + + if (!s_extensionMap.contains(m_container)) { + return; + } + + QStringList extensions = s_extensionMap.value(m_container); + QString filename = m_filename; + int index = m_filename.lastIndexOf("."); + if (index >= 0) { + if (extensions.contains(filename.mid(index))) { + // This extension is already valid + return; + } + filename.truncate(index); + } + filename += extensions.front(); + + m_ui.filename->setText(filename); +} + QString VideoView::sanitizeCodec(const QString& codec, const QMap& mapping) { QString sanitized = codec.toLower(); sanitized = sanitized.remove(QChar('.')); diff --git a/src/platform/qt/VideoView.h b/src/platform/qt/VideoView.h index 7c2bd99e0..23b5ef034 100644 --- a/src/platform/qt/VideoView.h +++ b/src/platform/qt/VideoView.h @@ -7,6 +7,7 @@ #ifdef USE_FFMPEG +#include #include #include @@ -62,6 +63,8 @@ private slots: void uncheckIncompatible(); void updatePresets(); + void changeExtension(); + private: struct Preset { QString container; @@ -123,6 +126,7 @@ private: static QMap s_acodecMap; static QMap s_vcodecMap; static QMap s_containerMap; + static QMap s_extensionMap; }; } From 682471fa1e5a5597214de7f303453abda7475c82 Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Fri, 24 Feb 2023 00:40:25 -0300 Subject: [PATCH 47/47] Libretro: Fix undeclared constant The `SIZE_CART_FLASH1M` constant was renamed to `GBA_SIZE_FLASH1M` in https://github.com/mgba-emu/mgba/commit/8545271e9ee275b9e733bbfa13195365b1fb82e1 These leftovers make the Libretro build fail, when running: ``` cmake -DBUILD_LIBRETRO=ON .. && make ``` --- src/platform/libretro/libretro.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c index 68f54f2c7..2e4b67c80 100644 --- a/src/platform/libretro/libretro.c +++ b/src/platform/libretro/libretro.c @@ -346,7 +346,7 @@ static void _doDeferredSetup(void) { // On the off-hand chance that a core actually expects its buffers to be populated when // you actually first get them, you're out of luck without workarounds. Yup, seriously. // Here's that workaround, but really the API needs to be thrown out and rewritten. - struct VFile* save = VFileFromMemory(savedata, SIZE_CART_FLASH1M); + struct VFile* save = VFileFromMemory(savedata, GBA_SIZE_FLASH1M); if (!core->loadSave(core, save)) { save->close(save); } @@ -930,8 +930,8 @@ bool retro_load_game(const struct retro_game_info* game) { core->setPeripheral(core, mPERIPH_RUMBLE, &rumble); core->setPeripheral(core, mPERIPH_ROTATION, &rotation); - savedata = anonymousMemoryMap(SIZE_CART_FLASH1M); - memset(savedata, 0xFF, SIZE_CART_FLASH1M); + savedata = anonymousMemoryMap(GBA_SIZE_FLASH1M); + memset(savedata, 0xFF, GBA_SIZE_FLASH1M); _reloadSettings(); core->loadROM(core, rom); @@ -1008,7 +1008,7 @@ void retro_unload_game(void) { core->deinit(core); mappedMemoryFree(data, dataSize); data = 0; - mappedMemoryFree(savedata, SIZE_CART_FLASH1M); + mappedMemoryFree(savedata, GBA_SIZE_FLASH1M); savedata = 0; } @@ -1163,7 +1163,7 @@ size_t retro_get_memory_size(unsigned id) { case mPLATFORM_GBA: switch (((struct GBA*) core->board)->memory.savedata.type) { case SAVEDATA_AUTODETECT: - return SIZE_CART_FLASH1M; + return GBA_SIZE_FLASH1M; default: return GBASavedataSize(&((struct GBA*) core->board)->memory.savedata); } @@ -1362,7 +1362,7 @@ static void _startImage(struct mImageSource* image, unsigned w, unsigned h, int static void _stopImage(struct mImageSource* image) { UNUSED(image); - cam.stop(); + cam.stop(); } static void _requestImage(struct mImageSource* image, const void** buffer, size_t* stride, enum mColorFormat* colorFormat) {