#include "ui/FrameScript.hpp" #include "ui/FrameScriptInternal.hpp" #include "ui/FrameScript_Object.hpp" #include "ui/LuaMemory.hpp" #include "util/CStatus.hpp" #include "util/Lua.hpp" #include "util/SFile.hpp" #include #include #include #include #include int32_t g_glueFrameScriptGenders[] = { 2, // UNIT_SEX_MALE 3, // UNIT_SEX_FEMALE 1, // UNIT_SEX_NONE }; void* FrameScript::s_mempool; lua_State* FrameScript::s_context; int64_t FrameScript::s_scriptTimeUsed; int32_t FrameScript::s_scriptProfileEnabled; int32_t FrameScript::s_errorHandlerFun; double_t FrameScript::s_scriptTimeDivisor; int32_t FrameScript::s_errorHandlerRef; int32_t FrameScript::s_recursiveTableHash; int32_t FrameScript::s_pluralRule; int32_t FrameScript::s_handlingError; TSFixedArray FrameScript::s_scriptEvents; TSHashTable FrameScript::s_scriptEventsHash; int32_t FrameScript::s_blocksizes[9] = { 0x10, 0x18, 0x20, 0x28, 0x40, 0x50, 0x80, 0xa0, 0x100 }; const char* FrameScript::s_compat_lua = R"( ------------------------------------------------------------------- -- Table library local tab = table foreach = tab.foreach foreachi = tab.foreachi getn = tab.getn tinsert = tab.insert tremove = tab.remove sort = tab.sort wipe = tab.wipe ------------------------------------------------------------------- -- math library local math = math abs = math.abs acos = function (x) return math.deg(math.acos(x)) end asin = function (x) return math.deg(math.asin(x)) end atan = function (x) return math.deg(math.atan(x)) end atan2 = function (x,y) return math.deg(math.atan2(x,y)) end ceil = math.ceil cos = function (x) return math.cos(math.rad(x)) end deg = math.deg exp = math.exp floor = math.floor frexp = math.frexp ldexp = math.ldexp log = math.log log10 = math.log10 max = math.max min = math.min mod = math.fmod PI = math.pi --??? pow = math.pow rad = math.rad random = math.random --randomseed = math.randomseed sin = function (x) return math.sin(math.rad(x)) end sqrt = math.sqrt tan = function (x) return math.tan(math.rad(x)) end ------------------------------------------------------------------- -- string library local str = string strbyte = str.byte strchar = str.char strfind = str.find format = str.format gmatch = str.gmatch gsub = str.gsub strlen = str.len strlower = str.lower strmatch = str.match strrep = str.rep strrev = str.reverse strsub = str.sub strupper = str.upper ------------------------------------------------------------------- -- Add custom string functions to the string table str.trim = strtrim str.split = strsplit str.join = strjoin str.replace = strreplace)"; const char* FrameScript_EventObject::GetName() { return this->m_key.GetString(); } int64_t OsGetAsyncClocksPerSecond() { // TODO return 1000.0; } int32_t FrameScript_CompileFunction(const char* name, const char* wrapper, const char* body, CStatus* status) { lua_State* L = FrameScript::s_context; size_t functionLen = SStrLen(wrapper) + SStrLen(body) + 1; char* function = static_cast(alloca(functionLen)); // Insert the function body into the wrapper SStrPrintf(function, functionLen, wrapper, body); lua_rawgeti(L, LUA_REGISTRYINDEX, FrameScript::s_errorHandlerRef); if (luaL_loadbuffer(L, function, SStrLen(function), name)) { if (status) { const char* v10 = lua_tostring(L, -1); status->Add(STATUS_ERROR, "%s", v10); } if (lua_pcall(L, 1, 0, 0)) { lua_settop(L, -2); } return -1; } else if (lua_pcall(L, 0, 1, -2)) { if (status) { const char* v13 = lua_tostring(L, -1); status->Add(STATUS_ERROR, "%s", v13); } lua_settop(L, -3); return -1; } else { int32_t luaRef = luaL_ref(L, LUA_REGISTRYINDEX); lua_settop(L, -2); return luaRef; } } void FrameScript_CreateEvents(const char* names[], uint32_t count) { FrameScript::s_scriptEvents.Clear(); FrameScript::s_scriptEvents.SetCount(count); for (int32_t i = 0; i < count; i++) { if (names[i]) { auto event = FrameScript::s_scriptEventsHash.New(names[i], 0, 0); FrameScript::s_scriptEvents[i] = event; } } } void FrameScript_Destroy() { // TODO } void FrameScript_Execute(const char* source, const char* filename, const char* a3) { /* TODO taint tracking v3 = lua_taintexpected++ == -1; v8 = *(_DWORD *)lua_tainted; if (!v3 && !lua_taintedclosure) { *(_DWORD *)lua_tainted = a3; } */ lua_State* L = FrameScript::s_context; size_t len = SStrLen(source); lua_rawgeti(L, LUA_REGISTRYINDEX, FrameScript::s_errorHandlerRef); if (!luaL_loadbuffer(L, source, len, filename)) { if (lua_pcall(L, 0, 0, -2)) { lua_settop(L, -3); } else { lua_settop(L, -2); } } else if (lua_pcall(L, 1, 0, 0)) { lua_settop(L, -2); } /* TODO taint tracking if (lua_taintexpected && !lua_taintedclosure) { *(_DWORD *)lua_tainted = v8; } v7 = lua_taintexpected - 1; lua_taintexpected = v7; if (v7 <= 0) { lua_taintexpected = 0; } */ } void FrameScript_Execute(int32_t function, FrameScript_Object* objectThis, int32_t argCount, const char* a4, FrameScript_EventObject* event) { lua_State* L = FrameScript::s_context; int32_t v20 = 1 - argCount + lua_gettop(L); int32_t v19 = argCount; lua_checkstack(L, argCount + 2); if (objectThis) { const char* name = objectThis->GetName(); if (!name) { name = ""; } // TODO // v6 = alloca(SStrLen(name) + 5); // v17 = (char *)&v14; // SStrCopy((char *)&v14, "DBG:", 0x7FFFFFFF); // SStrCopy(v17 + 4, name, 0x7FFFFFFF); lua_pushstring(L, "this"); lua_rawget(L, LUA_GLOBALSINDEX); if (!objectThis->lua_registered) { objectThis->RegisterScriptObject(0); } lua_rawgeti(L, LUA_REGISTRYINDEX, objectThis->lua_objectRef); lua_pushstring(L, "this"); lua_insert(L, -2); lua_rawset(L, LUA_GLOBALSINDEX); } if (event) { lua_pushstring(L, "event"); lua_rawget(L, LUA_GLOBALSINDEX); lua_pushvalue(L, v20); lua_pushstring(L, "event"); lua_insert(L, -2); lua_rawset(L, LUA_GLOBALSINDEX); } static char argName[] = { 'a', 'r', 'g', '0', 0, 0, 0 }; int32_t firstArg = event ? 1 : 0; int32_t argId = 0; if (firstArg < argCount) { for (int32_t i = firstArg; i < argCount; i++) { argId++; if (argId >= 10) { SStrPrintf(&argName[3], 3, "%d", argId); } else { argName[3] = '0' + argId; argName[4] = 0; } lua_pushstring(L, argName); lua_rawget(L, LUA_GLOBALSINDEX); lua_pushvalue(L, v20 + i); lua_pushstring(L, argName); lua_insert(L, -2); lua_rawset(L, LUA_GLOBALSINDEX); } } lua_checkstack(L, argCount + 3); lua_rawgeti(L, LUA_REGISTRYINDEX, FrameScript::s_errorHandlerRef); lua_rawgeti(L, LUA_REGISTRYINDEX, function); if (objectThis) { if (!objectThis->lua_registered) { objectThis->RegisterScriptObject(0); } lua_rawgeti(L, LUA_REGISTRYINDEX, objectThis->lua_objectRef); v19 = argCount + 1; } for (int32_t i = 0; i < argCount; ++i) { lua_pushvalue(L, v20 + i); } if (lua_pcall(L, v19, 0, -2 - v19)) { lua_settop(L, -2); } lua_settop(L, -2); for (int32_t i = argId; i > 0; i--) { if (i >= 10) { SStrPrintf(&argName[3], 3, "%d", i); } else { argName[3] = '0' + i; argName[4] = 0; } lua_pushstring(L, argName); lua_insert(L, -2); lua_rawset(L, LUA_GLOBALSINDEX); } if (event) { lua_pushstring(L, "event"); lua_insert(L, -2); lua_rawset(L, LUA_GLOBALSINDEX); } if (objectThis) { lua_pushstring(L, "this"); lua_insert(L, -2); lua_rawset(L, LUA_GLOBALSINDEX); } lua_settop(L, -1 - argCount); } int32_t FrameScript_ExecuteBuffer(const char* buffer, size_t bufferBytes, const char* bufferName, CStatus* status, const char* a5) { lua_State* L = FrameScript::s_context; lua_rawgeti(L, LUA_REGISTRYINDEX, FrameScript::s_errorHandlerRef); if (luaL_loadbuffer(L, buffer, bufferBytes, bufferName)) { if (status) { const char* v7 = lua_tostring(L, -1); status->Add(STATUS_ERROR, "%s", v7); } if (lua_pcall(L, 1, 0, 0)) { lua_settop(L, -2); } return 0; } else { int32_t v9 = 0; if (a5) { lua_pushstring(L, a5); lua_pushvalue(L, -4); v9 = 2; } if (lua_pcall(L, v9, 0, -2 - v9)) { if (status) { const char* v11 = lua_tostring(L, -1); status->Add(STATUS_ERROR, "%s", v11); } lua_settop(L, -3); return 0; } else { lua_settop(L, -2); return 1; } } } int32_t FrameScript_ExecuteFile(const char* filePath, const char* a2, MD5_CTX* md5, CStatus* status) { const char* v4 = filePath; char v12[260]; if (SStrStr(filePath, "..")) { SStrCopy(v12, filePath, 260); char* i; char* j; for (i = (char*)SStrStr(v12, ".."); i; i = (char*)SStrStr(v12, "..")) { char v6 = *(i - 1); if (v6 != 92 && v6 != 47) { break; } char v7 = i[2]; if (v7 != 92 && v7 != 47) { break; } for (j = i - 2; j >= v12; --j) { if (*j == 92) { break; } if (*j == 47) { break; } } SStrCopy(j + 1, i + 3, 0x7FFFFFFF); } v4 = v12; } char v11[264]; SStrPrintf(v11, 261, "@%s", v4); void* fileBuffer; size_t fileBytes; if (SFile::Load(0, v4, &fileBuffer, &fileBytes, 0, 1, nullptr)) { if (md5) { MD5Update(md5, static_cast(fileBuffer), fileBytes); } int32_t v10 = FrameScript_ExecuteBuffer(static_cast(fileBuffer), fileBytes, v11, status, a2); SFile::Unload(fileBuffer); return v10; } else { if (status) { status->Add(STATUS_ERROR, "Error loading %s", v4); } return 0; } } void FrameScript_Flush() { if (FrameScript::s_context) { FrameScript_Destroy(); FrameScript_Initialize(FrameScript::s_scriptProfileEnabled); } } void FrameScript_GetColor(lua_State* L, int32_t idx, CImVector& color) { float r = lua_tonumber(L, idx + 0); r = std::max(0.0f, std::min(r, 1.0f)); float g = lua_tonumber(L, idx + 1); g = std::max(0.0f, std::min(g, 1.0f)); float b = lua_tonumber(L, idx + 2); b = std::max(0.0f, std::min(b, 1.0f)); float a = 1.0f; if (lua_isnumber(L, idx + 3)) { a = lua_tonumber(L, idx + 3); a = std::max(0.0f, std::min(a, 1.0f)); } color.Set(a, r, g, b); } lua_State* FrameScript_GetContext(void) { return FrameScript::s_context; } const char* FrameScript_GetCurrentObject(lua_State* L, int32_t a2) { lua_Debug info; if (!lua_getstack(L, a2, &info)) { return nullptr; } lua_getinfo(L, "Sln", &info); if (info.source[0] != '*' && SStrCmp(info.namewhat, "method", 0x7FFFFFFFu)) { return nullptr; } if (!lua_getlocal(L, &info, 1)) { return nullptr; } const char* objName = nullptr; if (lua_type(L, -1) == LUA_TTABLE) { lua_rawgeti(L, -1, 0); FrameScript_Object* obj = static_cast(lua_touserdata(L, -1)); lua_settop(L, -2); if (obj) { objName = obj->GetName(); if (!objName) { objName = ""; } } } lua_settop(L, -2); return objName; } char FrameScript_GetPluralIndex(int32_t a1) { if (FrameScript::s_pluralRule == PLURAL_RULE_0) { return a1 != 1; } if (FrameScript::s_pluralRule == PLURAL_RULE_1) { return a1 > 1; } if (FrameScript::s_pluralRule != PLURAL_RULE_2) { return 0; } if ((unsigned int)(a1 % 100 - 11) <= 3) { return 2; } if (a1 % 10 == 1) { return 0; } else { return ((unsigned int)(a1 % 10 - 2) > 2) + 1; } } const char* FrameScript_GetText(const char* a1, int32_t count, FRAMESCRIPT_GENDER gender) { const char* text = ""; char pluralIndex = FrameScript_GetPluralIndex(count); // Suffix based on pluralization rules char pluralSuffix[4]; if (pluralIndex - 1 <= 8) { pluralSuffix[0] = 95; // _ pluralSuffix[1] = 80; // P pluralSuffix[2] = 48 + pluralIndex; // 0-8 pluralSuffix[3] = 0; // NULL } // Gender suffix const char* genderSuffix = ""; if (gender == GENDER_FEMALE) { genderSuffix = "_FEMALE"; } uint32_t len = SStrLen((const char*)pluralSuffix) + SStrLen(a1) + SStrLen(genderSuffix) + 1; char* v9 = (char*)alloca(len); SStrPrintf(v9, len, "%s%s%s", a1, &pluralSuffix, genderSuffix); if (FrameScript_GetVariable(v9, &text)) { return text; } SStrPrintf(v9, len, "%s%s", a1, &pluralSuffix); if (FrameScript_GetVariable(v9, &text)) { return text; } SStrPrintf(v9, len, "%s%s", a1, genderSuffix); if (FrameScript_GetVariable(v9, &text)) { return text; } // Fallback FrameScript_GetVariable(a1, &text); return text; } int32_t FrameScript_GetVariable(const char* a1, const char** a2) { lua_State* L = FrameScript::s_context; int32_t v3 = 0; lua_pushstring(L, a1); lua_rawget(L, LUA_GLOBALSINDEX); if (lua_isstring(L, -1)) { v3 = 1; *a2 = lua_tostring(L, -1); } lua_settop(L, -2); return v3; } int32_t FrameScript_HandleError(lua_State* L) { if (!lua_isstring(L, -1)) { lua_pushstring(L, "UNKNOWN ERROR"); lua_insert(L, -1); } const char* v1 = lua_tostring(L, -1); const char* v2 = SStrStr(v1, "*:"); const char* objName = FrameScript_GetCurrentObject(L, 1); // TODO // Remove temporary console debug logging if (v2 && objName) { printf("Error: %s%s\n", objName, v2 + 1); } else { printf("Error: %s\n", v1); } if (v2 && objName) { lua_pushlstring(L, v1, v2 - v1); lua_pushstring(L, objName); lua_pushstring(L, v2 + 1); lua_concat(L, 3); lua_replace(L, -2); } // If seterrorhandler() has been called in Lua, invoke that Lua-side function now if (FrameScript::s_errorHandlerFun != -1) { FrameScript::s_handlingError = 1; lua_rawgeti(L, LUA_REGISTRYINDEX, FrameScript::s_errorHandlerFun); lua_insert(L, -2); lua_call(L, 1, 1); FrameScript::s_handlingError = 0; } return 1; } int32_t FrameScript_Initialize(int32_t a1) { FrameScript::s_mempool = luaM_initPool(); FrameScript::s_context = lua_newstate(luaM_reallocPool, FrameScript::s_mempool); // TODO a1 FrameScript::s_scriptTimeUsed = 0ll; int64_t v1 = OsGetAsyncClocksPerSecond(); FrameScript::s_scriptProfileEnabled = a1; FrameScript::s_errorHandlerFun = -1; FrameScript::s_scriptTimeDivisor = 1000.0 / (double)v1; lua_pushcclosure(FrameScript::s_context, FrameScript_HandleError, 0); FrameScript::s_errorHandlerRef = luaL_ref(FrameScript::s_context, LUA_REGISTRYINDEX); lua_createtable(FrameScript::s_context, 0, 0); FrameScript::s_recursiveTableHash = luaL_ref(FrameScript::s_context, LUA_REGISTRYINDEX); lua_gc(FrameScript::s_context, LUA_GCSETPAUSE, 110); luaopen_base(FrameScript::s_context); lua_settop(FrameScript::s_context, -3); luaopen_string(FrameScript::s_context); lua_settop(FrameScript::s_context, -2); luaopen_table(FrameScript::s_context); lua_settop(FrameScript::s_context, -2); luaopen_math(FrameScript::s_context); lua_settop(FrameScript::s_context, -2); luaopen_bit(FrameScript::s_context); lua_settop(FrameScript::s_context, -2); luaL_register(FrameScript::s_context, "_G", FrameScriptInternal::extra_funcs); lua_settop(FrameScript::s_context, -2); FrameScript_Execute(FrameScript::s_compat_lua, "compat.lua", 0); return 1; } void FrameScript_RegisterFunction(const char* name, int32_t (*function)(struct lua_State *)) { lua_State* L = FrameScript::s_context; lua_pushcclosure(L, function, 0); lua_pushstring(L, name); lua_insert(L, -2); lua_rawset(L, LUA_GLOBALSINDEX); } void FrameScript_RegisterScriptEvent(FrameScript_Object* object, FrameScript_EventObject* event) { if (event->pendingSignalCount) { auto node = event->registerListeners.Head(); while (node) { if (node->listener == object) { return; } node = node->Next(); } node = event->registerListeners.NewNode(2, 0, 0x8); node->listener = object; } else { auto node = event->listeners.NewNode(2, 0, 0x8); node->listener = object; } } void FrameScript_SetPluralRule(PLURAL_RULE rule) { FrameScript::s_pluralRule = rule; } int32_t FrameScript_ShouldSignalEvent(uint32_t index) { // TODO return 1; } void FrameScript_PushEventName(uint32_t index) { // TODO // bounds checks FrameScript_EventObject* event = FrameScript::s_scriptEvents[index]; lua_pushstring(FrameScript::s_context, event->GetName()); } int32_t FrameScript_PushEventArgs(const char* format, va_list args) { int32_t argCount = 0; if (!format || !*format) { return argCount; } const char* cur = format; while (*cur) { if (*cur == '%') { cur++; switch (*cur) { case 'b': lua_pushboolean(FrameScript::s_context, va_arg(args, int32_t)); argCount++; break; case 'd': lua_pushnumber(FrameScript::s_context, va_arg(args, int32_t)); argCount++; break; case 'f': lua_pushnumber(FrameScript::s_context, va_arg(args, double)); argCount++; break; case 's': lua_pushstring(FrameScript::s_context, va_arg(args, char*)); argCount++; break; case 'u': lua_pushnumber(FrameScript::s_context, va_arg(args, uint32_t)); argCount++; break; } } cur++; } return argCount; } void FrameScript_SignalEvent(uint32_t index, lua_State* L, int32_t argCount) { auto event = FrameScript::s_scriptEvents[index]; if (!event) { return; } // TODO event->signalCount++; event->pendingSignalCount++; lua_checkstack(L, argCount); auto node = event->listeners.Head(); while (node) { auto unregisterNode = event->unregisterListeners.Head(); while (unregisterNode) { if (node->listener == unregisterNode->listener) { break; } unregisterNode = unregisterNode->Next(); } if (unregisterNode) { break; } auto script = &node->listener->m_onEvent; if (script->luaRef) { for (int32_t i = 0; i < argCount; i++) { lua_pushvalue(L, -argCount); } FrameScript_Execute(script->luaRef, node->listener, argCount, script->unk, event); } node = node->Next(); } event->pendingSignalCount--; auto unregisterNode = event->unregisterListeners.Head(); while (unregisterNode) { FrameScript_UnregisterScriptEvent(unregisterNode->listener, event); unregisterNode = event->unregisterListeners.DeleteNode(unregisterNode); } auto registerNode = event->registerListeners.Head(); while (registerNode) { FrameScript_RegisterScriptEvent(registerNode->listener, event); registerNode = event->registerListeners.DeleteNode(registerNode); } } void FrameScript_SignalEvent(uint32_t index, const char* format, ...) { if (!FrameScript_ShouldSignalEvent(index)) { return; } va_list args; va_start(args, format); // TODO // v6 = lua_taintexpected++ == -1; // v18 = lua_tainted; // if (!v6 && !lua_taintedclosure) { // lua_tainted = 0; // } FrameScript_PushEventName(index); int32_t argCount = 1; argCount += FrameScript_PushEventArgs(format, args); FrameScript_SignalEvent(index, FrameScript::s_context, argCount); lua_settop(FrameScript::s_context, -1 - argCount); // TODO // if (lua_taintexpected && !lua_taintedclosure) { // lua_tainted = v18; // } // if (--lua_taintexpected <= 0) { // lua_taintexpected = 0; // } va_end(args); } const char* FrameScript_Sprintf(lua_State* L, int32_t idx, char buffer[], uint32_t bufferLen) { auto write = buffer; auto availableBytes = bufferLen; size_t formatLen; auto format = luaL_checklstring(L, idx, &formatLen); auto formatEnd = format + formatLen; char specifier[128]; auto curIdx = idx; while (format < formatEnd && availableBytes > 1) { auto ch = *format++; // Escaped % if (ch == '%' && *format == '%') { *write++ = '%'; format++; availableBytes--; continue; } // Non-specifier if (ch != '%') { *write++ = ch; availableBytes--; continue; } // Specifier ch = *format; specifier[0] = '%'; // Position specifier if (ch >= '0' && ch <= '9' && format[1] == '$') { curIdx = idx + (ch - '0') - 1; format += 2; } curIdx++; // Subspecifiers auto subspecifierStart = format; // Flags while (*format == '-' || *format == '+' || *format == ' ' || *format == '#' || *format == '0') { format++; } // Width while (*format >= '0' && *format <= '9') { format++; } // Precision while (*format == '.' || *format == '-') { format++; } while (*format >= '0' && *format <= '9') { format++; } // Validate subspecifier auto subspecifierLen = format - subspecifierStart; if (subspecifierLen > 125) { luaL_error(L, "invalid format (width or precision too long)"); } // Copy subspecifier and specifier (len + 1) memcpy(&specifier[1], subspecifierStart, subspecifierLen + 1); // Terminate specifier specifier[subspecifierLen + 2] = '\0'; // Evaluate specifier ch = *format++; switch (ch) { // Floating point / scientific notation case 'E': case 'G': case 'e': case 'f': case 'g': { auto number = luaL_checknumber(L, curIdx); auto written = SStrPrintf(write, availableBytes, specifier, number); if (written > 0) { write += written; availableBytes -= written; } break; } // Floating point with decimal conversion case 'F': { // Replace with lowercase f for (char* replace = specifier; *replace; replace++) { if (*replace == 'F') { *replace = 'f'; } } auto number = luaL_checknumber(L, curIdx); auto written = SStrPrintf(write, availableBytes, specifier, number); if (written > 0) { // TODO lua_convertdecimal(write); write += written; availableBytes -= written; } break; } // Unsigned hex / octal / decimal case 'X': case 'o': case 'u': case 'x': { auto number = static_cast(luaL_checknumber(L, curIdx)); auto written = SStrPrintf(write, availableBytes, specifier, static_cast(number)); if (written > 0) { write += written; availableBytes -= written; } break; } // Char case 'c': { *write++ = static_cast(luaL_checknumber(L, curIdx)); availableBytes--; break; } // Signed decimal case 'd': case 'i': { auto number = luaL_checknumber(L, curIdx); auto written = SStrPrintf(write, availableBytes, specifier, static_cast(number)); if (written > 0) { // TODO lua_convertdecimal(write); write += written; availableBytes -= written; } break; } // String case 's': { size_t stringLen; auto string = luaL_checklstring(L, curIdx, &stringLen); auto written = SStrPrintf(write, availableBytes, specifier, string); if (written > 0) { write += written; availableBytes -= written; } break; } default: { luaL_error(L, "invalid option in `format'"); } } } // Terminate write[0] = '\0'; return buffer; } void FrameScript_UnregisterFunction(const char* name) { auto L = FrameScript::s_context; lua_pushnil(L); lua_pushstring(L, name); lua_insert(L, -2); lua_rawset(L, LUA_GLOBALSINDEX); } void FrameScript_UnregisterScriptEvent(FrameScript_Object* object, FrameScript_EventObject* event) { if (event->pendingSignalCount) { auto node = event->unregisterListeners.Head(); while (node) { if (node->listener == object) { return; } node = node->Next(); } node = event->unregisterListeners.NewNode(2, 0, 0x8); node->listener = object; } else { auto node = event->listeners.Head(); while (node) { if (node->listener == object) { event->listeners.DeleteNode(node); break; } node = node->Next(); } } }