garrysmod-requests icon indicating copy to clipboard operation
garrysmod-requests copied to clipboard

Reimplement debug.getregistry but with restrictions

Open RaphaelIT7 opened this issue 1 year ago • 2 comments

Details

debug.getregistry should be made functions again to not break compatibility with older addons, but it should have restrictions.
My idea would be to let debug.getregistry only return metatables and allow with a __newindex function to add new metatables to the registry and to remove metatables.
They should only be able to be set if nothing with the name is registered in the registry.
Also, a __index function should be added that allows one to get metatables from the registry, but the restriction there should be that only tables containing MetaName should be allowed.

By checking if a table in the registry has the MetaName field as a key, we can check if it's a metatable. How my approach would look:

LUA_FUNCTION(debug_getregistry__newindex)
{
	const char* pKey = LUA->GetString(2);
	if (!LUA->IsType(3, Type::Table) && !LUA->IsType(3, Type::Nil)) { return 0; } // If it's not a table or nil then just return.
	
	LUA->PushSpecial(SPECIAL_REG);
	LUA->GetField(-1, pKey);
	if (LUA->IsType(-1, Type::NIL))
	{
		LUA->GetField(3, "MetaName");
		if (LUA->IsType(-1, Type::Nil)) // Verify that our new Table has a MetaName field. If not, add it.
		{
			LUA->PushString(pKey);
			LUA->SetField(3, "MetaName");
		}
		LUA->Pop(1);

		LUA->Push(3);
		LUA->SetField(-3, pKey);
	} else if (LUA->IsType(-1, Type::Table)) { // Allow one to remove a metatable
		LUA->GetField(-1, "MetaName");
		if (LUA->IsType(-1, Type::String))
		{
			LUA->PushNil();
			LUA->SetField(-4, pKey);
		}
		LUA->Pop(1);
	}
	LUA->Pop(2);

	return 0;
}

LUA_FUNCTION(debug_getregistry__index)
{
	const char* pKey = LUA->GetString(2);
	LUA->PushSpecial(SPECIAL_REG);
	LUA->GetField(-1, pKey);
	if (LUA->IsType(-1, Type::Table))
	{
		LUA->GetField(-1, "MetaName");
		if (LUA->IsType(-1, Type::String)) // If it has MetaID and MetaName it's a metatable.
		{
			LUA->Push(-2);
			int reference = LUA->ReferenceCreate();
			LUA->Pop(4);

			LUA->ReferencePush(reference);
			return 1;
		}
		LUA->Pop(1);
	}
	LUA->Pop(2);

	return 0;
}

std::vector<const char*> entity_classes = {
	"Entity",
	"Player",
	"Weapon",
	"NPC",
	"Vehicle",
	"NextBot"
};
LUA_FUNCTION(debug_getregistry)
{
	LUA->CreateTable();

	// Push default Metatables to reduce __index calls.
	LUA->PushSpecial(SPECIAL_REG);
	for (int i=Type::Vector; i < (Type::COUNT-1); i++)
	{
		bool success = LUA->PushMetaTable(i);
		if (!success) { continue; }

		LUA->GetField(-1, "MetaName");
		const char* pMetaName = LUA->GetString(-1);
		LUA->Pop(2); // Pop MetaName and MetaTable

		LUA->GetField(-1, pMetaName);
		if (LUA->IsType(-1, Type::Table))
		{
			LUA->SetField(-3, pMetaName);
		} else
		{
			LUA->Pop(1);
		}
	}

	for (const char* pMetaName : entity_classes) // Add Entity Metatables. The loop above cannot handle these because they use the same type!
	{
		LUA->GetField(-1, pMetaName);
		if (LUA->IsType(-1, Type::Table))
		{
			LUA->SetField(-3, pMetaName);
		} else
		{
			LUA->Pop(1);
		}
	}
	LUA->Pop(1);

	// Set our Metatable
	LUA->CreateTable();
	LUA->PushCFunction(debug_getregistry__newindex);
	LUA->SetField(-2, "__newindex");
	LUA->PushCFunction(debug_getregistry__index);
	LUA->SetField(-2, "__index");
	LUA->SetMetaTable(-2);

	return 1;
}

RaphaelIT7 avatar Jan 05 '24 16:01 RaphaelIT7

Where would i put this?

MagonYT avatar Jan 07 '24 19:01 MagonYT

Compile it into a module.

Kefta avatar Jan 07 '24 20:01 Kefta