lua_consolelib.c 16 KB
Newer Older
Alam Ed Arias's avatar
Alam Ed Arias committed
1 2
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
3
// Copyright (C) 2012-2016 by John "JTE" Muniz.
James R.'s avatar
James R. committed
4
// Copyright (C) 2012-2020 by Sonic Team Junior.
Alam Ed Arias's avatar
Alam Ed Arias committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file  lua_consolelib.c
/// \brief console modifying/etc library for Lua scripting

#include "doomdef.h"
#include "fastcmp.h"
#include "p_local.h"
#include "g_game.h"
#include "byteptr.h"
#include "z_zone.h"

#include "lua_script.h"
#include "lua_libs.h"
#include "lua_hud.h" // hud_running errors

24
// for functions not allowed in hud.add hooks
25 26
#define NOHUD if (hud_running)\
return luaL_error(L, "HUD rendering code should not call this function!");
27 28 29
// for functions not allowed in hooks or coroutines (supercedes above)
#define NOHOOK if (!lua_lumploading)\
		return luaL_error(L, "This function cannot be called from within a hook or coroutine!");
Alam Ed Arias's avatar
Alam Ed Arias committed
30 31 32 33 34 35 36

static const char *cvname = NULL;

void Got_Luacmd(UINT8 **cp, INT32 playernum)
{
	UINT8 i, argc, flags;
	char buf[256];
Alam Ed Arias's avatar
Alam Ed Arias committed
37 38 39 40 41 42

	// don't use I_Assert here, goto the deny code below
	// to clean up and kick people who try nefarious exploits
	// like sending random junk lua commands to crash the server

	if (!gL) goto deny;
Alam Ed Arias's avatar
Alam Ed Arias committed
43
	lua_getfield(gL, LUA_REGISTRYINDEX, "COM_Command"); // push COM_Command
Alam Ed Arias's avatar
Alam Ed Arias committed
44
	if (!lua_istable(gL, -1)) goto deny;
Alam Ed Arias's avatar
Alam Ed Arias committed
45 46 47

	argc = READUINT8(*cp);
	READSTRINGN(*cp, buf, 255);
Alam Ed Arias's avatar
Alam Ed Arias committed
48
	strlwr(buf); // must lowercase buffer
Alam Ed Arias's avatar
Alam Ed Arias committed
49
	lua_getfield(gL, -1, buf); // push command info table
Alam Ed Arias's avatar
Alam Ed Arias committed
50 51
	if (!lua_istable(gL, -1)) goto deny;

Alam Ed Arias's avatar
Alam Ed Arias committed
52 53 54 55 56 57 58 59 60
	lua_remove(gL, -2); // pop COM_Command

	lua_rawgeti(gL, -1, 2); // push flags from command info table
	if (lua_isboolean(gL, -1))
		flags = (lua_toboolean(gL, -1) ? 1 : 0);
	else
		flags = (UINT8)lua_tointeger(gL, -1);
	lua_pop(gL, 1); // pop flags

Alam Ed Arias's avatar
Alam Ed Arias committed
61
	// requires server/admin and the player is not one of them
wolfs's avatar
wolfs committed
62
	if ((flags & 1) && playernum != serverplayer && !IsPlayerAdmin(playernum))
Alam Ed Arias's avatar
Alam Ed Arias committed
63
		goto deny;
Alam Ed Arias's avatar
Alam Ed Arias committed
64

Alam Ed Arias's avatar
Alam Ed Arias committed
65
	lua_rawgeti(gL, -1, 1); // push function from command info table
Alam Ed Arias's avatar
Alam Ed Arias committed
66

Alam Ed Arias's avatar
Alam Ed Arias committed
67 68 69
	// although honestly this should be true anyway
	// BUT GODDAMNIT I SAID NO I_ASSERTS SO NO I_ASSERTS IT IS
	if (!lua_isfunction(gL, -1)) goto deny;
Alam Ed Arias's avatar
Alam Ed Arias committed
70 71 72 73 74 75 76 77 78 79

	lua_remove(gL, -2); // pop command info table

	LUA_PushUserdata(gL, &players[playernum], META_PLAYER);
	for (i = 1; i < argc; i++)
	{
		READSTRINGN(*cp, buf, 255);
		lua_pushstring(gL, buf);
	}
	LUA_Call(gL, (int)argc); // argc is 1-based, so this will cover the player we passed too.
Alam Ed Arias's avatar
Alam Ed Arias committed
80 81 82 83
	return;

deny:
	//must be hacked/buggy client
Monster Iestyn's avatar
Monster Iestyn committed
84 85 86
	if (gL) // check if Lua is actually turned on first, you dummmy -- Monster Iestyn 04/07/18
		lua_settop(gL, 0); // clear stack

Alam Ed Arias's avatar
Alam Ed Arias committed
87 88
	CONS_Alert(CONS_WARNING, M_GetText("Illegal lua command received from %s\n"), player_names[playernum]);
	if (server)
89
		SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
Alam Ed Arias's avatar
Alam Ed Arias committed
90 91 92
}

// Wrapper for COM_AddCommand commands
Alam Ed Arias's avatar
Alam Ed Arias committed
93
void COM_Lua_f(void)
Alam Ed Arias's avatar
Alam Ed Arias committed
94 95 96 97 98 99 100 101 102 103
{
	char *buf, *p;
	UINT8 i, flags;
	UINT16 len;
	INT32 playernum = consoleplayer;

	I_Assert(gL != NULL);
	lua_getfield(gL, LUA_REGISTRYINDEX, "COM_Command"); // push COM_Command
	I_Assert(lua_istable(gL, -1));

Alam Ed Arias's avatar
Alam Ed Arias committed
104 105 106 107
	// use buf temporarily -- must use lowercased string
	buf = Z_StrDup(COM_Argv(0));
	strlwr(buf);
	lua_getfield(gL, -1, buf); // push command info table
Alam Ed Arias's avatar
Alam Ed Arias committed
108 109
	I_Assert(lua_istable(gL, -1));
	lua_remove(gL, -2); // pop COM_Command
Alam Ed Arias's avatar
Alam Ed Arias committed
110
	Z_Free(buf);
Alam Ed Arias's avatar
Alam Ed Arias committed
111 112 113

	lua_rawgeti(gL, -1, 2); // push flags from command info table
	if (lua_isboolean(gL, -1))
114
		flags = (lua_toboolean(gL, -1) ? COM_ADMIN : 0);
Alam Ed Arias's avatar
Alam Ed Arias committed
115 116 117 118
	else
		flags = (UINT8)lua_tointeger(gL, -1);
	lua_pop(gL, 1); // pop flags

119
	if (flags & COM_SPLITSCREEN) // flag 2: splitscreen player command.
Alam Ed Arias's avatar
Alam Ed Arias committed
120 121 122 123 124 125 126 127 128
	{
		if (!splitscreen)
		{
			lua_pop(gL, 1); // pop command info table
			return; // can't execute splitscreen command without player 2!
		}
		playernum = secondarydisplayplayer;
	}

129
	if (netgame && !( flags & COM_LOCAL ))/* don't send local commands */
Alam Ed Arias's avatar
Alam Ed Arias committed
130 131 132 133
	{ // Send the command through the network
		UINT8 argc;
		lua_pop(gL, 1); // pop command info table

134
		if (flags & COM_ADMIN && !server && !IsPlayerAdmin(playernum)) // flag 1: only server/admin can use this command.
Alam Ed Arias's avatar
Alam Ed Arias committed
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
		{
			CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
			return;
		}

		if (COM_Argc() > UINT8_MAX)
			argc = UINT8_MAX;
		else
			argc = (UINT8)COM_Argc();
		if (argc == UINT8_MAX)
			len = UINT16_MAX;
		else
			len = (argc+1)*256;

		buf = malloc(len);
		p = buf;
		WRITEUINT8(p, argc);
		for (i = 0; i < argc; i++)
			WRITESTRINGN(p, COM_Argv(i), 255);
James R.'s avatar
James R. committed
154
		if (flags & COM_SPLITSCREEN)
Alam Ed Arias's avatar
Alam Ed Arias committed
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
			SendNetXCmd2(XD_LUACMD, buf, p-buf);
		else
			SendNetXCmd(XD_LUACMD, buf, p-buf);
		free(buf);
		return;
	}

	// Do the command locally, NetXCmds don't go through outside of GS_LEVEL || GS_INTERMISSION
	lua_rawgeti(gL, -1, 1); // push function from command info table
	I_Assert(lua_isfunction(gL, -1));
	lua_remove(gL, -2); // pop command info table

	LUA_PushUserdata(gL, &players[playernum], META_PLAYER);
	for (i = 1; i < COM_Argc(); i++)
		lua_pushstring(gL, COM_Argv(i));
	LUA_Call(gL, (int)COM_Argc()); // COM_Argc is 1-based, so this will cover the player we passed too.
}

// Wrapper for COM_AddCommand
static int lib_comAddCommand(lua_State *L)
{
Alam Ed Arias's avatar
Alam Ed Arias committed
176 177 178 179 180 181 182
	int com_return = -1;
	const char *luaname = luaL_checkstring(L, 1);

	// must store in all lowercase
	char *name = Z_StrDup(luaname);
	strlwr(name);

Alam Ed Arias's avatar
Alam Ed Arias committed
183
	luaL_checktype(L, 2, LUA_TFUNCTION);
184
	NOHOOK
Alam Ed Arias's avatar
Alam Ed Arias committed
185 186 187
	if (lua_gettop(L) >= 3)
	{ // For the third argument, only take a boolean or a number.
		lua_settop(L, 3);
188 189 190
		if (lua_type(L, 3) == LUA_TBOOLEAN)
		{
			CONS_Alert(CONS_WARNING,
191 192 193
					"Using a boolean for admin commands is "
					"deprecated and will be removed.\n"
					"Use \"COM_ADMIN\" instead.\n"
194 195 196
			);
		}
		else
Alam Ed Arias's avatar
Alam Ed Arias committed
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
			luaL_checktype(L, 3, LUA_TNUMBER);
	}
	else
	{ // No third argument? Default to 0.
		lua_settop(L, 2);
		lua_pushinteger(L, 0);
	}

	lua_getfield(L, LUA_REGISTRYINDEX, "COM_Command");
	I_Assert(lua_istable(L, -1));

	lua_createtable(L, 2, 0);
		lua_pushvalue(L, 2);
		lua_rawseti(L, -2, 1);

		lua_pushvalue(L, 3);
		lua_rawseti(L, -2, 2);
	lua_setfield(L, -2, name);

Alam Ed Arias's avatar
Alam Ed Arias committed
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
	// Try to add the Lua command
	com_return = COM_AddLuaCommand(name);

	if (com_return < 0)
	{ // failed to add -- free the lowercased name and return error
		Z_Free(name);
		return luaL_error(L, "Couldn't add a new console command \"%s\"", luaname);
	}
	else if (com_return == 1)
	{ // command existed already -- free our name as the old string will continue to be used
		CONS_Printf("Replaced command \"%s\"\n", name);
		Z_Free(name);
	}
	else
	{ // new command was added -- do NOT free the string as it will forever be used by the console
		CONS_Printf("Added command \"%s\"\n", name);
	}
Alam Ed Arias's avatar
Alam Ed Arias committed
233 234 235 236 237 238
	return 0;
}

static int lib_comBufAddText(lua_State *L)
{
	int n = lua_gettop(L);  /* number of arguments */
239
	player_t *plr = NULL;
Alam Ed Arias's avatar
Alam Ed Arias committed
240 241 242 243
	if (n < 2)
		return luaL_error(L, "COM_BufAddText requires two arguments: player and text.");
	NOHUD
	lua_settop(L, 2);
244 245 246
	if (!lua_isnoneornil(L, 1))
		plr = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
	if (plr && plr != &players[consoleplayer])
Alam Ed Arias's avatar
Alam Ed Arias committed
247
		return 0;
248
	COM_BufAddTextEx(va("%s\n", luaL_checkstring(L, 2)), COM_SAFE);
Alam Ed Arias's avatar
Alam Ed Arias committed
249 250 251 252 253 254
	return 0;
}

static int lib_comBufInsertText(lua_State *L)
{
	int n = lua_gettop(L);  /* number of arguments */
255
	player_t *plr = NULL;
Alam Ed Arias's avatar
Alam Ed Arias committed
256 257 258 259
	if (n < 2)
		return luaL_error(L, "COM_BufInsertText requires two arguments: player and text.");
	NOHUD
	lua_settop(L, 2);
260 261 262
	if (!lua_isnoneornil(L, 1))
		plr = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
	if (plr && plr != &players[consoleplayer])
Alam Ed Arias's avatar
Alam Ed Arias committed
263
		return 0;
264
	COM_BufInsertTextEx(va("%s\n", luaL_checkstring(L, 2)), COM_SAFE);
Alam Ed Arias's avatar
Alam Ed Arias committed
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
	return 0;
}

void LUA_CVarChanged(const char *name)
{
	cvname = name;
}

static void Lua_OnChange(void)
{
	I_Assert(gL != NULL);
	I_Assert(cvname != NULL);

	/// \todo Network this! XD_LUAVAR

	// From CV_OnChange registry field, get the function for this cvar by name.
	lua_getfield(gL, LUA_REGISTRYINDEX, "CV_OnChange");
	I_Assert(lua_istable(gL, -1));
	lua_getfield(gL, -1, cvname); // get function

	// From the CV_Vars registry field, get the cvar's userdata by name.
	lua_getfield(gL, LUA_REGISTRYINDEX, "CV_Vars");
	I_Assert(lua_istable(gL, -1));
	lua_getfield(gL, -1, cvname); // get consvar_t* userdata.
	lua_remove(gL, -2); // pop the CV_Vars table.

	LUA_Call(gL, 1); // call function(cvar)
	lua_pop(gL, 1); // pop CV_OnChange table
}

static int lib_cvRegisterVar(lua_State *L)
{
	const char *k;
	lua_Integer i;
	consvar_t *cvar;
	luaL_checktype(L, 1, LUA_TTABLE);
	lua_settop(L, 1); // Clear out all other possible arguments, leaving only the first one.
302
	NOHOOK
Alam Ed Arias's avatar
Alam Ed Arias committed
303 304 305 306 307 308 309
	cvar = lua_newuserdata(L, sizeof(consvar_t));
	luaL_getmetatable(L, META_CVAR);
	lua_setmetatable(L, -2);

#define FIELDERROR(f, e) luaL_error(L, "bad value for " LUA_QL(f) " in table passed to " LUA_QL("CV_RegisterVar") " (%s)", e);
#define TYPEERROR(f, t) FIELDERROR(f, va("%s expected, got %s", lua_typename(L, t), luaL_typename(L, -1)))

310 311
	memset(cvar, 0x00, sizeof(consvar_t)); // zero everything by default

Alam Ed Arias's avatar
Alam Ed Arias committed
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398
	lua_pushnil(L);
	while (lua_next(L, 1)) {
		// stack: cvar table, cvar userdata, key/index, value
		//            1             2            3        4
		i = 0;
		k = NULL;
		if (lua_isnumber(L, 3))
			i = lua_tointeger(L, 3);
		else if (lua_isstring(L, 3))
			k = lua_tostring(L, 3);

		if (i == 1 || (k && fasticmp(k, "name"))) {
			if (!lua_isstring(L, 4))
				TYPEERROR("name", LUA_TSTRING)
			cvar->name = Z_StrDup(lua_tostring(L, 4));
		} else if (i == 2 || (k && fasticmp(k, "defaultvalue"))) {
			if (!lua_isstring(L, 4))
				TYPEERROR("defaultvalue", LUA_TSTRING)
			cvar->defaultvalue = Z_StrDup(lua_tostring(L, 4));
		} else if (i == 3 || (k && fasticmp(k, "flags"))) {
			if (!lua_isnumber(L, 4))
				TYPEERROR("flags", LUA_TNUMBER)
			cvar->flags = (INT32)lua_tointeger(L, 4);
		} else if (i == 4 || (k && fasticmp(k, "PossibleValue"))) {
			if (lua_islightuserdata(L, 4)) {
				CV_PossibleValue_t *pv = lua_touserdata(L, 4);
				if (pv == CV_OnOff || pv == CV_YesNo || pv == CV_Unsigned || pv == CV_Natural)
					cvar->PossibleValue = pv;
				else
					FIELDERROR("PossibleValue", "CV_PossibleValue_t expected, got unrecognised pointer")
			} else if (lua_istable(L, 4)) {
				// Accepts tables in the form of {MIN=0, MAX=9999} or {Red=0, Green=1, Blue=2}
				// and converts them to CV_PossibleValue_t {{0,"MIN"},{9999,"MAX"}} or {{0,"Red"},{1,"Green"},{2,"Blue"}}
				//
				// I don't really like the way this does it because a single PossibleValue table
				// being used for multiple cvars will be converted and stored multiple times.
				// So maybe instead it should be a seperate function which must be run beforehand or something.
				size_t count = 0;
				CV_PossibleValue_t *cvpv;

				lua_pushnil(L);
				while (lua_next(L, 4)) {
					count++;
					lua_pop(L, 1);
				}

				lua_getfield(L, LUA_REGISTRYINDEX, "CV_PossibleValue");
				I_Assert(lua_istable(L, 5));
				lua_pushvalue(L, 2); // cvar userdata
				cvpv = lua_newuserdata(L, sizeof(CV_PossibleValue_t) * (count+1));
				lua_rawset(L, 5);
				lua_pop(L, 1); // pop CV_PossibleValue registry table

				i = 0;
				lua_pushnil(L);
				while (lua_next(L, 4)) {
					// stack: [...] PossibleValue table, index, value
					//                       4             5      6
					if (lua_type(L, 5) != LUA_TSTRING
					|| lua_type(L, 6) != LUA_TNUMBER)
						FIELDERROR("PossibleValue", "custom PossibleValue table requires a format of string=integer, i.e. {MIN=0, MAX=9999}");
					cvpv[i].strvalue = Z_StrDup(lua_tostring(L, 5));
					cvpv[i].value = (INT32)lua_tonumber(L, 6);
					i++;
					lua_pop(L, 1);
				}
				cvpv[i].value = 0;
				cvpv[i].strvalue = NULL;
				cvar->PossibleValue = cvpv;
			} else
				FIELDERROR("PossibleValue", va("%s or CV_PossibleValue_t expected, got %s", lua_typename(L, LUA_TTABLE), luaL_typename(L, -1)))
		} else if (cvar->flags & CV_CALL && (i == 5 || (k && fasticmp(k, "func")))) {
			if (!lua_isfunction(L, 4))
				TYPEERROR("func", LUA_TFUNCTION)
			lua_getfield(L, LUA_REGISTRYINDEX, "CV_OnChange");
			I_Assert(lua_istable(L, 5));
			lua_pushvalue(L, 4);
			lua_setfield(L, 5, cvar->name);
			lua_pop(L, 1);
			cvar->func = Lua_OnChange;
		}
		lua_pop(L, 1);
	}

#undef FIELDERROR
#undef TYPEERROR

399 400 401 402 403 404 405
	if (!cvar->name)
		return luaL_error(L, M_GetText("Variable has no name!\n"));
	if ((cvar->flags & CV_NOINIT) && !(cvar->flags & CV_CALL))
		return luaL_error(L, M_GetText("Variable %s has CV_NOINIT without CV_CALL\n"), cvar->name);
	if ((cvar->flags & CV_CALL) && !cvar->func)
		return luaL_error(L, M_GetText("Variable %s has CV_CALL without a function\n"), cvar->name);

Alam Ed Arias's avatar
Alam Ed Arias committed
406 407 408
	// stack: cvar table, cvar userdata
	lua_getfield(L, LUA_REGISTRYINDEX, "CV_Vars");
	I_Assert(lua_istable(L, 3));
409 410 411 412 413 414

	lua_getfield(L, 3, cvar->name);
	if (lua_type(L, -1) != LUA_TNIL)
		return luaL_error(L, M_GetText("Variable %s is already defined\n"), cvar->name);
	lua_pop(L, 1);

Alam Ed Arias's avatar
Alam Ed Arias committed
415 416 417 418 419
	lua_pushvalue(L, 2);
	lua_setfield(L, 3, cvar->name);
	lua_pop(L, 1);

	// actually time to register it to the console now! Finally!
420
	cvar->flags |= CV_MODIFIED;
Alam Ed Arias's avatar
Alam Ed Arias committed
421
	CV_RegisterVar(cvar);
422 423
	if (cvar->flags & CV_MODIFIED)
		return luaL_error(L, "failed to register cvar (probable conflict with internal variable/command names)");
Alam Ed Arias's avatar
Alam Ed Arias committed
424 425 426 427 428

	// return cvar userdata
	return 1;
}

James R.'s avatar
James R. committed
429 430
static int lib_cvFindVar(lua_State *L)
{
431 432
	LUA_PushLightUserdata(L, CV_FindVar(luaL_checkstring(L,1)), META_CVAR);
	return 1;
James R.'s avatar
James R. committed
433 434
}

Alam Ed Arias's avatar
Alam Ed Arias committed
435 436 437 438 439 440 441 442 443 444
// CONS_Printf for a single player
// Use 'print' in baselib for a global message.
static int lib_consPrintf(lua_State *L)
{
	int n = lua_gettop(L);  /* number of arguments */
	int i;
	player_t *plr;
	if (n < 2)
		return luaL_error(L, "CONS_Printf requires at least two arguments: player and text.");
	//HUDSAFE
kaysrishaq's avatar
kaysrishaq committed
445

Alam Ed Arias's avatar
Alam Ed Arias committed
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
	plr = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
	if (!plr)
		return LUA_ErrInvalid(L, "player_t");
	if (plr != &players[consoleplayer])
		return 0;
	lua_getglobal(L, "tostring");
	for (i=2; i<=n; i++) {
		const char *s;
		lua_pushvalue(L, -1);  /* function to be called */
		lua_pushvalue(L, i);   /* value to print */
		lua_call(L, 1, 1);
		s = lua_tostring(L, -1);  /* get result */
		if (s == NULL)
			return luaL_error(L, LUA_QL("tostring") " must return a string to "
													 LUA_QL("CONS_Printf"));
		if (i>2) CONS_Printf("\n");
		CONS_Printf("%s", s);
		lua_pop(L, 1);  /* pop result */
	}
	CONS_Printf("\n");
	return 0;
}

static luaL_Reg lib[] = {
	{"COM_AddCommand", lib_comAddCommand},
	{"COM_BufAddText", lib_comBufAddText},
	{"COM_BufInsertText", lib_comBufInsertText},
	{"CV_RegisterVar", lib_cvRegisterVar},
James R.'s avatar
James R. committed
474
	{"CV_FindVar", lib_cvFindVar},
Alam Ed Arias's avatar
Alam Ed Arias committed
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536
	{"CONS_Printf", lib_consPrintf},
	{NULL, NULL}
};

static int cvar_get(lua_State *L)
{
	consvar_t *cvar = (consvar_t *)luaL_checkudata(L, 1, META_CVAR);
	const char *field = luaL_checkstring(L, 2);

	if(fastcmp(field,"name"))
		lua_pushstring(L, cvar->name);
	else if(fastcmp(field,"defaultvalue"))
		lua_pushstring(L, cvar->defaultvalue);
	else if(fastcmp(field,"flags"))
		lua_pushinteger(L, cvar->flags);
	else if(fastcmp(field,"value"))
		lua_pushinteger(L, cvar->value);
	else if(fastcmp(field,"string"))
		lua_pushstring(L, cvar->string);
	else if(fastcmp(field,"changed"))
		lua_pushboolean(L, cvar->changed);
	else if (devparm)
		return luaL_error(L, LUA_QL("consvar_t") " has no field named " LUA_QS, field);
	else
		return 0;
	return 1;
}

int LUA_ConsoleLib(lua_State *L)
{
	// Metatable for consvar_t
	luaL_newmetatable(L, META_CVAR);
		lua_pushcfunction(L, cvar_get);
		lua_setfield(L, -2, "__index");
	lua_pop(L,1);

	// Set empty registry tables
	lua_newtable(L);
	lua_setfield(L, LUA_REGISTRYINDEX, "COM_Command");
	lua_newtable(L);
	lua_setfield(L, LUA_REGISTRYINDEX, "CV_Vars");
	lua_newtable(L);
	lua_setfield(L, LUA_REGISTRYINDEX, "CV_PossibleValue");
	lua_newtable(L);
	lua_setfield(L, LUA_REGISTRYINDEX, "CV_OnChange");

	// Push opaque CV_PossibleValue pointers
	// Because I don't care enough to bother.
	lua_pushlightuserdata(L, CV_OnOff);
	lua_setglobal(L, "CV_OnOff");
	lua_pushlightuserdata(L, CV_YesNo);
	lua_setglobal(L, "CV_YesNo");
	lua_pushlightuserdata(L, CV_Unsigned);
	lua_setglobal(L, "CV_Unsigned");
	lua_pushlightuserdata(L, CV_Natural);
	lua_setglobal(L, "CV_Natural");

	// Set global functions
	lua_pushvalue(L, LUA_GLOBALSINDEX);
	luaL_register(L, NULL, lib);
	return 0;
}