1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 2012-2016 by John "JTE" Muniz.
4 // Copyright (C) 2012-2020 by Sonic Team Junior.
5 //
6 // This program is free software distributed under the
7 // terms of the GNU General Public License, version 2.
8 // See the 'LICENSE' file for more details.
9 //-----------------------------------------------------------------------------
10 /// \file  lua_consolelib.c
11 /// \brief console modifying/etc library for Lua scripting
12 
13 #include "doomdef.h"
14 #include "fastcmp.h"
15 #include "p_local.h"
16 #include "g_game.h"
17 #include "byteptr.h"
18 #include "z_zone.h"
19 
20 #include "lua_script.h"
21 #include "lua_libs.h"
22 #include "lua_hud.h" // hud_running errors
23 
24 // for functions not allowed in hud.add hooks
25 #define NOHUD if (hud_running)\
26 return luaL_error(L, "HUD rendering code should not call this function!");
27 // for functions not allowed in hooks or coroutines (supercedes above)
28 #define NOHOOK if (!lua_lumploading)\
29 		return luaL_error(L, "This function cannot be called from within a hook or coroutine!");
30 
31 static consvar_t *this_cvar;
32 
Got_Luacmd(UINT8 ** cp,INT32 playernum)33 void Got_Luacmd(UINT8 **cp, INT32 playernum)
34 {
35 	UINT8 i, argc, flags;
36 	char buf[256];
37 
38 	// don't use I_Assert here, goto the deny code below
39 	// to clean up and kick people who try nefarious exploits
40 	// like sending random junk lua commands to crash the server
41 
42 	if (!gL) goto deny;
43 
44 	lua_settop(gL, 0); // Just in case...
45 	lua_pushcfunction(gL, LUA_GetErrorMessage);
46 
47 	lua_getfield(gL, LUA_REGISTRYINDEX, "COM_Command"); // push COM_Command
48 	if (!lua_istable(gL, -1)) goto deny;
49 
50 	argc = READUINT8(*cp);
51 	READSTRINGN(*cp, buf, 255);
52 	strlwr(buf); // must lowercase buffer
53 	lua_getfield(gL, -1, buf); // push command info table
54 	if (!lua_istable(gL, -1)) goto deny;
55 
56 	lua_remove(gL, -2); // pop COM_Command
57 
58 	lua_rawgeti(gL, -1, 2); // push flags from command info table
59 	if (lua_isboolean(gL, -1))
60 		flags = (lua_toboolean(gL, -1) ? 1 : 0);
61 	else
62 		flags = (UINT8)lua_tointeger(gL, -1);
63 	lua_pop(gL, 1); // pop flags
64 
65 	// requires server/admin and the player is not one of them
66 	if ((flags & 1) && playernum != serverplayer && !IsPlayerAdmin(playernum))
67 		goto deny;
68 
69 	lua_rawgeti(gL, -1, 1); // push function from command info table
70 
71 	// although honestly this should be true anyway
72 	// BUT GODDAMNIT I SAID NO I_ASSERTS SO NO I_ASSERTS IT IS
73 	if (!lua_isfunction(gL, -1)) goto deny;
74 
75 	lua_remove(gL, -2); // pop command info table
76 
77 	LUA_PushUserdata(gL, &players[playernum], META_PLAYER);
78 	for (i = 1; i < argc; i++)
79 	{
80 		READSTRINGN(*cp, buf, 255);
81 		lua_pushstring(gL, buf);
82 	}
83 	LUA_Call(gL, (int)argc, 0, 1); // argc is 1-based, so this will cover the player we passed too.
84 	return;
85 
86 deny:
87 	//must be hacked/buggy client
88 	if (gL) // check if Lua is actually turned on first, you dummmy -- Monster Iestyn 04/07/18
89 		lua_settop(gL, 0); // clear stack
90 
91 	CONS_Alert(CONS_WARNING, M_GetText("Illegal lua command received from %s\n"), player_names[playernum]);
92 	if (server)
93 		SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
94 }
95 
96 // Wrapper for COM_AddCommand commands
COM_Lua_f(void)97 void COM_Lua_f(void)
98 {
99 	char *buf, *p;
100 	UINT8 i, flags;
101 	UINT16 len;
102 	INT32 playernum = consoleplayer;
103 
104 	I_Assert(gL != NULL);
105 
106 	lua_settop(gL, 0); // Just in case...
107 	lua_pushcfunction(gL, LUA_GetErrorMessage);
108 
109 	lua_getfield(gL, LUA_REGISTRYINDEX, "COM_Command"); // push COM_Command
110 	I_Assert(lua_istable(gL, -1));
111 
112 	// use buf temporarily -- must use lowercased string
113 	buf = Z_StrDup(COM_Argv(0));
114 	strlwr(buf);
115 	lua_getfield(gL, -1, buf); // push command info table
116 	I_Assert(lua_istable(gL, -1));
117 	lua_remove(gL, -2); // pop COM_Command
118 	Z_Free(buf);
119 
120 	lua_rawgeti(gL, -1, 2); // push flags from command info table
121 	if (lua_isboolean(gL, -1))
122 		flags = (lua_toboolean(gL, -1) ? COM_ADMIN : 0);
123 	else
124 		flags = (UINT8)lua_tointeger(gL, -1);
125 	lua_pop(gL, 1); // pop flags
126 
127 	if (flags & COM_SPLITSCREEN) // flag 2: splitscreen player command.
128 	{
129 		if (!splitscreen)
130 		{
131 			lua_pop(gL, 1); // pop command info table
132 			return; // can't execute splitscreen command without player 2!
133 		}
134 		playernum = secondarydisplayplayer;
135 	}
136 
137 	if (netgame && !( flags & COM_LOCAL ))/* don't send local commands */
138 	{ // Send the command through the network
139 		UINT8 argc;
140 		lua_pop(gL, 1); // pop command info table
141 
142 		if (flags & COM_ADMIN && !server && !IsPlayerAdmin(playernum)) // flag 1: only server/admin can use this command.
143 		{
144 			CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
145 			return;
146 		}
147 
148 		if (COM_Argc() > UINT8_MAX)
149 			argc = UINT8_MAX;
150 		else
151 			argc = (UINT8)COM_Argc();
152 		if (argc == UINT8_MAX)
153 			len = UINT16_MAX;
154 		else
155 			len = (argc+1)*256;
156 
157 		buf = malloc(len);
158 		p = buf;
159 		WRITEUINT8(p, argc);
160 		for (i = 0; i < argc; i++)
161 			WRITESTRINGN(p, COM_Argv(i), 255);
162 		if (flags & COM_SPLITSCREEN)
163 			SendNetXCmd2(XD_LUACMD, buf, p-buf);
164 		else
165 			SendNetXCmd(XD_LUACMD, buf, p-buf);
166 		free(buf);
167 		return;
168 	}
169 
170 	// Do the command locally, NetXCmds don't go through outside of GS_LEVEL || GS_INTERMISSION
171 	lua_rawgeti(gL, -1, 1); // push function from command info table
172 	I_Assert(lua_isfunction(gL, -1));
173 	lua_remove(gL, -2); // pop command info table
174 
175 	LUA_PushUserdata(gL, &players[playernum], META_PLAYER);
176 	for (i = 1; i < COM_Argc(); i++)
177 		lua_pushstring(gL, COM_Argv(i));
178 	LUA_Call(gL, (int)COM_Argc(), 0, 1); // COM_Argc is 1-based, so this will cover the player we passed too.
179 }
180 
181 // Wrapper for COM_AddCommand
lib_comAddCommand(lua_State * L)182 static int lib_comAddCommand(lua_State *L)
183 {
184 	int com_return = -1;
185 	const char *luaname = luaL_checkstring(L, 1);
186 
187 	// must store in all lowercase
188 	char *name = Z_StrDup(luaname);
189 	strlwr(name);
190 
191 	luaL_checktype(L, 2, LUA_TFUNCTION);
192 	NOHOOK
193 	if (lua_gettop(L) >= 3)
194 	{ // For the third argument, only take a boolean or a number.
195 		lua_settop(L, 3);
196 		if (lua_type(L, 3) == LUA_TBOOLEAN)
197 		{
198 			CONS_Alert(CONS_WARNING,
199 					"Using a boolean for admin commands is "
200 					"deprecated and will be removed.\n"
201 					"Use \"COM_ADMIN\" instead.\n"
202 			);
203 		}
204 		else
205 			luaL_checktype(L, 3, LUA_TNUMBER);
206 	}
207 	else
208 	{ // No third argument? Default to 0.
209 		lua_settop(L, 2);
210 		lua_pushinteger(L, 0);
211 	}
212 
213 	lua_getfield(L, LUA_REGISTRYINDEX, "COM_Command");
214 	I_Assert(lua_istable(L, -1));
215 
216 	lua_createtable(L, 2, 0);
217 		lua_pushvalue(L, 2);
218 		lua_rawseti(L, -2, 1);
219 
220 		lua_pushvalue(L, 3);
221 		lua_rawseti(L, -2, 2);
222 	lua_setfield(L, -2, name);
223 
224 	// Try to add the Lua command
225 	com_return = COM_AddLuaCommand(name);
226 
227 	if (com_return < 0)
228 	{ // failed to add -- free the lowercased name and return error
229 		Z_Free(name);
230 		return luaL_error(L, "Couldn't add a new console command \"%s\"", luaname);
231 	}
232 	else if (com_return == 1)
233 	{ // command existed already -- free our name as the old string will continue to be used
234 		CONS_Printf("Replaced command \"%s\"\n", name);
235 		Z_Free(name);
236 	}
237 	else
238 	{ // new command was added -- do NOT free the string as it will forever be used by the console
239 		CONS_Printf("Added command \"%s\"\n", name);
240 	}
241 	return 0;
242 }
243 
lib_comBufAddText(lua_State * L)244 static int lib_comBufAddText(lua_State *L)
245 {
246 	int n = lua_gettop(L);  /* number of arguments */
247 	player_t *plr = NULL;
248 	if (n < 2)
249 		return luaL_error(L, "COM_BufAddText requires two arguments: player and text.");
250 	NOHUD
251 	lua_settop(L, 2);
252 	if (!lua_isnoneornil(L, 1))
253 		plr = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
254 	if (plr && plr != &players[consoleplayer])
255 		return 0;
256 	COM_BufAddTextEx(va("%s\n", luaL_checkstring(L, 2)), COM_SAFE);
257 	return 0;
258 }
259 
lib_comBufInsertText(lua_State * L)260 static int lib_comBufInsertText(lua_State *L)
261 {
262 	int n = lua_gettop(L);  /* number of arguments */
263 	player_t *plr = NULL;
264 	if (n < 2)
265 		return luaL_error(L, "COM_BufInsertText requires two arguments: player and text.");
266 	NOHUD
267 	lua_settop(L, 2);
268 	if (!lua_isnoneornil(L, 1))
269 		plr = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
270 	if (plr && plr != &players[consoleplayer])
271 		return 0;
272 	COM_BufInsertTextEx(va("%s\n", luaL_checkstring(L, 2)), COM_SAFE);
273 	return 0;
274 }
275 
LUA_CVarChanged(void * cvar)276 void LUA_CVarChanged(void *cvar)
277 {
278 	this_cvar = cvar;
279 }
280 
Lua_OnChange(void)281 static void Lua_OnChange(void)
282 {
283 	/// \todo Network this! XD_LUAVAR
284 
285 	lua_pushcfunction(gL, LUA_GetErrorMessage);
286 	lua_insert(gL, 1); // Because LUA_Call wants it at index 1.
287 
288 	// From CV_OnChange registry field, get the function for this cvar by name.
289 	lua_getfield(gL, LUA_REGISTRYINDEX, "CV_OnChange");
290 	I_Assert(lua_istable(gL, -1));
291 	lua_pushlightuserdata(gL, this_cvar);
292 	lua_rawget(gL, -2); // get function
293 
294 	LUA_RawPushUserdata(gL, this_cvar);
295 
296 	LUA_Call(gL, 1, 0, 1); // call function(cvar)
297 	lua_pop(gL, 1); // pop CV_OnChange table
298 	lua_remove(gL, 1); // remove LUA_GetErrorMessage
299 }
300 
lib_cvRegisterVar(lua_State * L)301 static int lib_cvRegisterVar(lua_State *L)
302 {
303 	const char *k;
304 	lua_Integer i;
305 	consvar_t *cvar;
306 	luaL_checktype(L, 1, LUA_TTABLE);
307 	lua_settop(L, 1); // Clear out all other possible arguments, leaving only the first one.
308 	NOHOOK
309 	cvar = ZZ_Calloc(sizeof(consvar_t));
310 	LUA_PushUserdata(L, cvar, META_CVAR);
311 
312 #define FIELDERROR(f, e) luaL_error(L, "bad value for " LUA_QL(f) " in table passed to " LUA_QL("CV_RegisterVar") " (%s)", e);
313 #define TYPEERROR(f, t) FIELDERROR(f, va("%s expected, got %s", lua_typename(L, t), luaL_typename(L, -1)))
314 
315 	lua_pushnil(L);
316 	while (lua_next(L, 1)) {
317 		// stack: cvar table, cvar userdata, key/index, value
318 		//            1             2            3        4
319 		i = 0;
320 		k = NULL;
321 		if (lua_isnumber(L, 3))
322 			i = lua_tointeger(L, 3);
323 		else if (lua_isstring(L, 3))
324 			k = lua_tostring(L, 3);
325 
326 		if (i == 1 || (k && fasticmp(k, "name"))) {
327 			if (!lua_isstring(L, 4))
328 				TYPEERROR("name", LUA_TSTRING)
329 			cvar->name = Z_StrDup(lua_tostring(L, 4));
330 		} else if (i == 2 || (k && fasticmp(k, "defaultvalue"))) {
331 			if (!lua_isstring(L, 4))
332 				TYPEERROR("defaultvalue", LUA_TSTRING)
333 			cvar->defaultvalue = Z_StrDup(lua_tostring(L, 4));
334 		} else if (i == 3 || (k && fasticmp(k, "flags"))) {
335 			if (!lua_isnumber(L, 4))
336 				TYPEERROR("flags", LUA_TNUMBER)
337 			cvar->flags = (INT32)lua_tointeger(L, 4);
338 		} else if (i == 4 || (k && fasticmp(k, "PossibleValue"))) {
339 			if (lua_islightuserdata(L, 4)) {
340 				CV_PossibleValue_t *pv = lua_touserdata(L, 4);
341 				if (pv == CV_OnOff || pv == CV_YesNo || pv == CV_Unsigned || pv == CV_Natural)
342 					cvar->PossibleValue = pv;
343 				else
344 					FIELDERROR("PossibleValue", "CV_PossibleValue_t expected, got unrecognised pointer")
345 			} else if (lua_istable(L, 4)) {
346 				// Accepts tables in the form of {MIN=0, MAX=9999} or {Red=0, Green=1, Blue=2}
347 				// and converts them to CV_PossibleValue_t {{0,"MIN"},{9999,"MAX"}} or {{0,"Red"},{1,"Green"},{2,"Blue"}}
348 				//
349 				// I don't really like the way this does it because a single PossibleValue table
350 				// being used for multiple cvars will be converted and stored multiple times.
351 				// So maybe instead it should be a seperate function which must be run beforehand or something.
352 				size_t count = 0;
353 				CV_PossibleValue_t *cvpv;
354 
355 				lua_pushnil(L);
356 				while (lua_next(L, 4)) {
357 					count++;
358 					lua_pop(L, 1);
359 				}
360 
361 				lua_getfield(L, LUA_REGISTRYINDEX, "CV_PossibleValue");
362 				I_Assert(lua_istable(L, 5));
363 				lua_pushlightuserdata(L, cvar);
364 				cvpv = lua_newuserdata(L, sizeof(CV_PossibleValue_t) * (count+1));
365 				lua_rawset(L, 5);
366 				lua_pop(L, 1); // pop CV_PossibleValue registry table
367 
368 				i = 0;
369 				lua_pushnil(L);
370 				while (lua_next(L, 4)) {
371 					// stack: [...] PossibleValue table, index, value
372 					//                       4             5      6
373 					if (lua_type(L, 5) != LUA_TSTRING
374 					|| lua_type(L, 6) != LUA_TNUMBER)
375 						FIELDERROR("PossibleValue", "custom PossibleValue table requires a format of string=integer, i.e. {MIN=0, MAX=9999}");
376 					cvpv[i].strvalue = Z_StrDup(lua_tostring(L, 5));
377 					cvpv[i].value = (INT32)lua_tonumber(L, 6);
378 					i++;
379 					lua_pop(L, 1);
380 				}
381 				cvpv[i].value = 0;
382 				cvpv[i].strvalue = NULL;
383 				cvar->PossibleValue = cvpv;
384 			} else
385 				FIELDERROR("PossibleValue", va("%s or CV_PossibleValue_t expected, got %s", lua_typename(L, LUA_TTABLE), luaL_typename(L, -1)))
386 		} else if (cvar->flags & CV_CALL && (i == 5 || (k && fasticmp(k, "func")))) {
387 			if (!lua_isfunction(L, 4))
388 				TYPEERROR("func", LUA_TFUNCTION)
389 			lua_getfield(L, LUA_REGISTRYINDEX, "CV_OnChange");
390 			I_Assert(lua_istable(L, 5));
391 			lua_pushlightuserdata(L, cvar);
392 			lua_pushvalue(L, 4);
393 			lua_rawset(L, 5);
394 			lua_pop(L, 1);
395 			cvar->func = Lua_OnChange;
396 		}
397 		lua_pop(L, 1);
398 	}
399 
400 #undef FIELDERROR
401 #undef TYPEERROR
402 
403 	if (!cvar->name)
404 		return luaL_error(L, M_GetText("Variable has no name!\n"));
405 	if ((cvar->flags & CV_NOINIT) && !(cvar->flags & CV_CALL))
406 		return luaL_error(L, M_GetText("Variable %s has CV_NOINIT without CV_CALL\n"), cvar->name);
407 	if ((cvar->flags & CV_CALL) && !cvar->func)
408 		return luaL_error(L, M_GetText("Variable %s has CV_CALL without a function\n"), cvar->name);
409 
410 	// actually time to register it to the console now! Finally!
411 	cvar->flags |= CV_MODIFIED;
412 	CV_RegisterVar(cvar);
413 	if (cvar->flags & CV_MODIFIED)
414 		return luaL_error(L, "failed to register cvar (probable conflict with internal variable/command names)");
415 
416 	// return cvar userdata
417 	return 1;
418 }
419 
lib_cvFindVar(lua_State * L)420 static int lib_cvFindVar(lua_State *L)
421 {
422 	const char *name = luaL_checkstring(L, 1);
423 	LUA_PushUserdata(L, CV_FindVar(name), META_CVAR);
424 	return 1;
425 }
426 
CVarSetFunction(lua_State * L,void (* Set)(consvar_t *,const char *),void (* SetValue)(consvar_t *,INT32))427 static int CVarSetFunction
428 (
429 		lua_State *L,
430 		void (*Set)(consvar_t *, const char *),
431 		void (*SetValue)(consvar_t *, INT32)
432 ){
433 	consvar_t *cvar = *(consvar_t **)luaL_checkudata(L, 1, META_CVAR);
434 
435 	if (cvar->flags & CV_NOLUA)
436 		return luaL_error(L, "Variable %s cannot be set from Lua.", cvar->name);
437 
438 	switch (lua_type(L, 2))
439 	{
440 		case LUA_TSTRING:
441 			(*Set)(cvar, lua_tostring(L, 2));
442 			break;
443 		case LUA_TNUMBER:
444 			(*SetValue)(cvar, (INT32)lua_tonumber(L, 2));
445 			break;
446 		default:
447 			return luaL_typerror(L, 1, "string or number");
448 	}
449 
450 	return 0;
451 }
452 
lib_cvSet(lua_State * L)453 static int lib_cvSet(lua_State *L)
454 {
455 	return CVarSetFunction(L, CV_Set, CV_SetValue);
456 }
457 
lib_cvStealthSet(lua_State * L)458 static int lib_cvStealthSet(lua_State *L)
459 {
460 	return CVarSetFunction(L, CV_StealthSet, CV_StealthSetValue);
461 }
462 
lib_cvAddValue(lua_State * L)463 static int lib_cvAddValue(lua_State *L)
464 {
465 	consvar_t *cvar = *(consvar_t **)luaL_checkudata(L, 1, META_CVAR);
466 
467 	if (cvar->flags & CV_NOLUA)
468 		return luaL_error(L, "Variable %s cannot be set from Lua.", cvar->name);
469 
470 	CV_AddValue(cvar, (INT32)luaL_checknumber(L, 2));
471 
472 	return 0;
473 }
474 
475 // CONS_Printf for a single player
476 // Use 'print' in baselib for a global message.
lib_consPrintf(lua_State * L)477 static int lib_consPrintf(lua_State *L)
478 {
479 	int n = lua_gettop(L);  /* number of arguments */
480 	int i;
481 	player_t *plr;
482 	if (n < 2)
483 		return luaL_error(L, "CONS_Printf requires at least two arguments: player and text.");
484 	//HUDSAFE
485 
486 	plr = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
487 	if (!plr)
488 		return LUA_ErrInvalid(L, "player_t");
489 	if (plr != &players[consoleplayer])
490 		return 0;
491 	lua_getglobal(L, "tostring");
492 	for (i=2; i<=n; i++) {
493 		const char *s;
494 		lua_pushvalue(L, -1);  /* function to be called */
495 		lua_pushvalue(L, i);   /* value to print */
496 		lua_call(L, 1, 1);
497 		s = lua_tostring(L, -1);  /* get result */
498 		if (s == NULL)
499 			return luaL_error(L, LUA_QL("tostring") " must return a string to "
500 													 LUA_QL("CONS_Printf"));
501 		if (i>2) CONS_Printf("\n");
502 		CONS_Printf("%s", s);
503 		lua_pop(L, 1);  /* pop result */
504 	}
505 	CONS_Printf("\n");
506 	return 0;
507 }
508 
509 static luaL_Reg lib[] = {
510 	{"COM_AddCommand", lib_comAddCommand},
511 	{"COM_BufAddText", lib_comBufAddText},
512 	{"COM_BufInsertText", lib_comBufInsertText},
513 	{"CV_RegisterVar", lib_cvRegisterVar},
514 	{"CV_FindVar", lib_cvFindVar},
515 	{"CV_Set", lib_cvSet},
516 	{"CV_StealthSet", lib_cvStealthSet},
517 	{"CV_AddValue", lib_cvAddValue},
518 	{"CONS_Printf", lib_consPrintf},
519 	{NULL, NULL}
520 };
521 
cvar_get(lua_State * L)522 static int cvar_get(lua_State *L)
523 {
524 	consvar_t *cvar = *(consvar_t **)luaL_checkudata(L, 1, META_CVAR);
525 	const char *field = luaL_checkstring(L, 2);
526 
527 	if(fastcmp(field,"name"))
528 		lua_pushstring(L, cvar->name);
529 	else if(fastcmp(field,"defaultvalue"))
530 		lua_pushstring(L, cvar->defaultvalue);
531 	else if(fastcmp(field,"flags"))
532 		lua_pushinteger(L, cvar->flags);
533 	else if(fastcmp(field,"value"))
534 		lua_pushinteger(L, cvar->value);
535 	else if(fastcmp(field,"string"))
536 		lua_pushstring(L, cvar->string);
537 	else if(fastcmp(field,"changed"))
538 		lua_pushboolean(L, cvar->changed);
539 	else if (devparm)
540 		return luaL_error(L, LUA_QL("consvar_t") " has no field named " LUA_QS, field);
541 	else
542 		return 0;
543 	return 1;
544 }
545 
LUA_ConsoleLib(lua_State * L)546 int LUA_ConsoleLib(lua_State *L)
547 {
548 	// Metatable for consvar_t
549 	luaL_newmetatable(L, META_CVAR);
550 		lua_pushcfunction(L, cvar_get);
551 		lua_setfield(L, -2, "__index");
552 	lua_pop(L,1);
553 
554 	// Set empty registry tables
555 	lua_newtable(L);
556 	lua_setfield(L, LUA_REGISTRYINDEX, "COM_Command");
557 	lua_newtable(L);
558 	lua_setfield(L, LUA_REGISTRYINDEX, "CV_Vars");
559 	lua_newtable(L);
560 	lua_setfield(L, LUA_REGISTRYINDEX, "CV_PossibleValue");
561 	lua_newtable(L);
562 	lua_setfield(L, LUA_REGISTRYINDEX, "CV_OnChange");
563 
564 	// Push opaque CV_PossibleValue pointers
565 	// Because I don't care enough to bother.
566 	lua_pushlightuserdata(L, CV_OnOff);
567 	lua_setglobal(L, "CV_OnOff");
568 	lua_pushlightuserdata(L, CV_YesNo);
569 	lua_setglobal(L, "CV_YesNo");
570 	lua_pushlightuserdata(L, CV_Unsigned);
571 	lua_setglobal(L, "CV_Unsigned");
572 	lua_pushlightuserdata(L, CV_Natural);
573 	lua_setglobal(L, "CV_Natural");
574 
575 	// Set global functions
576 	lua_pushvalue(L, LUA_GLOBALSINDEX);
577 	luaL_register(L, NULL, lib);
578 	return 0;
579 }
580