aboutsummaryrefslogtreecommitdiff
path: root/engines/sword25/script/luascript.cpp
blob: a58265f7bd4ddc17c622d3f9cda2b9789b3f03c8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
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
302
303
304
305
306
307
308
309
310
311
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
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
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
474
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
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
// -----------------------------------------------------------------------------
// This file is part of Broken Sword 2.5
// Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsd�rfer
//
// Broken Sword 2.5 is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// Broken Sword 2.5 is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Broken Sword 2.5; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
// -----------------------------------------------------------------------------

#define BS_LOG_PREFIX "LUA"

// -----------------------------------------------------------------------------
// Includes
// -----------------------------------------------------------------------------

extern "C"
{
	#include "sword25/util/lua/lua.h"
	#include "sword25/util/lua/lualib.h"
	#include "sword25/util/lua/lauxlib.h"
	#include "sword25/util/pluto/pluto.h"
}

#include "sword25/package/packagemanager.h"
#include "sword25/script/luascript.h"
#include "sword25/script/luabindhelper.h"

#include "sword25/kernel/outputpersistenceblock.h"
#include "sword25/kernel/inputpersistenceblock.h"

#include "sword25/kernel/memlog_off.h"
#include <vector>
#include "sword25/kernel/memlog_on.h"

using namespace std;

// -----------------------------------------------------------------------------
// Konstruktion / Destruktion
// -----------------------------------------------------------------------------

BS_LuaScriptEngine::BS_LuaScriptEngine(BS_Kernel * KernelPtr) :
	BS_ScriptEngine(KernelPtr),
	m_State(0),
	m_PcallErrorhandlerRegistryIndex(0)
{
}

// -----------------------------------------------------------------------------

BS_LuaScriptEngine::~BS_LuaScriptEngine()
{
	// Lua deinitialisieren
	if (m_State) lua_close(m_State);
}

// -----------------------------------------------------------------------------

BS_Service * BS_LuaScriptEngine_CreateObject(BS_Kernel * KernelPtr) { return new BS_LuaScriptEngine(KernelPtr); }

// -----------------------------------------------------------------------------

namespace
{
	int PanicCB(lua_State * L)
	{
		BS_LOG_ERRORLN("Lua panic. Error message: %s", lua_isnil(L, -1) ? "" : lua_tostring(L, -1));
		return 0;
	}
}

// -----------------------------------------------------------------------------

bool BS_LuaScriptEngine::Init()
{
	// Lua-State intialisieren und Standardbibliotheken initialisieren
	m_State = luaL_newstate();
	if (!m_State || ! RegisterStandardLibs() || !RegisterStandardLibExtensions())
	{
		BS_LOG_ERRORLN("Lua could not be initialized.");
		return false;
	}

	// Panic-Callbackfunktion registrieren.
	lua_atpanic(m_State, PanicCB);
	
	// Errorhandlerfunktion f�r lua_pcall-Aufrufe.
	// Der untenstehende Code enth�lt eine lokale ErrorHandler-Funktion und gibt diese zur�ck.
	const char ErrorHandlerCode[] =
		"local function ErrorHandler(message) "
		"	return message .. '\\n' .. debug.traceback('', 2) "
		"end "
		"return ErrorHandler";

	// Den Code compilieren.
	if (luaL_loadbuffer(m_State, ErrorHandlerCode, strlen(ErrorHandlerCode), "PCALL ERRORHANDLER") != 0)
	{
		// Fehlernachricht ausgeben und Methode beenden.
		BS_LOG_ERRORLN("Couldn't compile luaL_pcall errorhandler:\n%s", lua_tostring(m_State, -1));
		lua_pop(m_State, 1);

		return false;
	}
	// Den Code ausf�hren, dies legt die Errorhandler-Funktion oben auf den Stack.
	if (lua_pcall(m_State, 0, 1, 0) != 0)
	{
		// Fehlernachricht ausgeben und Methode beenden.
		BS_LOG_ERRORLN("Couldn't prepare luaL_pcall errorhandler:\n%s", lua_tostring(m_State, -1));
		lua_pop(m_State, 1);

		return false;
	}
	
	// Die Errorhandler-Funktion in der Lua-Registry ablegen und den Index merken.
	m_PcallErrorhandlerRegistryIndex = luaL_ref(m_State, LUA_REGISTRYINDEX);

	// Die Pluto Persistenz-Bibliothek initialisieren.
	luaopen_pluto(m_State);
	lua_pop(m_State, 1);

	BS_LOGLN("Lua initialized.");

	return true;
}

// -----------------------------------------------------------------------------

bool BS_LuaScriptEngine::ExecuteFile(const std::string & FileName)
{
#ifdef DEBUG
	int __startStackDepth = lua_gettop(m_State);
#endif

	// Pointer auf den Packagemanager holen
	BS_PackageManager * pPackage = static_cast<BS_PackageManager *>(BS_Kernel::GetInstance()->GetService("package"));
	BS_ASSERT(pPackage);

	// Datei einlesen
	unsigned int FileSize;
	char * FileData = static_cast<char *>(pPackage->GetFile(FileName, &FileSize));
	if (!FileData)
	{
		BS_LOG_ERRORLN("Couldn't read \"%s\".", FileName.c_str());
#ifdef DEBUG
		BS_ASSERT(__startStackDepth == lua_gettop(m_State));
#endif
		return false;
	}

	// Dateiinhalt ausf�hren
	if (!ExecuteBuffer(FileData, FileSize, "@" + pPackage->GetAbsolutePath(FileName)))
	{
		// Dateipuffer freigeben
		delete[] FileData;
#ifdef DEBUG
		BS_ASSERT(__startStackDepth == lua_gettop(m_State));
#endif
		return false;
	}

	// Dateipuffer freigeben
	delete[] FileData;

#ifdef DEBUG
	BS_ASSERT(__startStackDepth == lua_gettop(m_State));
#endif

	return true;
}

// -----------------------------------------------------------------------------

bool BS_LuaScriptEngine::ExecuteString(const std::string & Code)
{
	return ExecuteBuffer(Code.c_str(), Code.length(), "???");
}

// -----------------------------------------------------------------------------

namespace
{
	void RemoveForbiddenFunctions(lua_State * L)
	{
		static const char * FORBIDDEN_FUNCTIONS[] =
		{
			"dofile",
			0
		};

		const char ** Iterator = FORBIDDEN_FUNCTIONS;
		while (*Iterator)
		{
			lua_pushnil(L);
			lua_setfield(L, LUA_GLOBALSINDEX, *Iterator);
			++Iterator;
		}
	}
}

bool BS_LuaScriptEngine::RegisterStandardLibs()
{
	luaL_openlibs(m_State);
	RemoveForbiddenFunctions(m_State);
	return true;
}

// -----------------------------------------------------------------------------

bool BS_LuaScriptEngine::ExecuteBuffer(const char * Data, unsigned int Size, const std::string & Name) const
{
	// Puffer kompilieren
	if (luaL_loadbuffer(m_State, Data, Size, Name.c_str()) != 0)
	{
		BS_LOG_ERRORLN("Couldn't compile \"%s\":\n%s", Name.c_str(), lua_tostring(m_State, -1));
		lua_pop(m_State, 1);

		return false;
	}

	// Error-Handler Funktion hinter der auszuf�hrenden Funktion auf den Stack legen.
	lua_rawgeti(m_State, LUA_REGISTRYINDEX, m_PcallErrorhandlerRegistryIndex);
	lua_insert(m_State, -2);

	// Pufferinhalt ausf�hren
	if (lua_pcall(m_State, 0, 0, -2) != 0)
	{
		BS_LOG_ERRORLN("An error occured while executing \"%s\":\n%s.",
						Name.c_str(),
						lua_tostring(m_State, -1));
		lua_pop(m_State, 2);

		return false;
	}

	// Error-Handler Funktion vom Stack nehmen.
	lua_pop(m_State, 1);

	return true;
}

// -----------------------------------------------------------------------------

void BS_LuaScriptEngine::SetCommandLine(const vector<string> & CommandLineParameters)
{
	lua_newtable(m_State);

	for (size_t i = 0; i < CommandLineParameters.size(); ++i)
	{
		lua_pushnumber(m_State, i + 1);
		lua_pushstring(m_State, CommandLineParameters[i].c_str());
		lua_settable(m_State, -3);
	}

	lua_setglobal(m_State, "CommandLine");
}

// -----------------------------------------------------------------------------

namespace
{
	const char * PERMANENTS_TABLE_NAME = "Permanents";

	// -------------------------------------------------------------------------

	// Diese Array enth�lt die Namen der globalen Lua-Objekte, die nicht persistiert werden sollen.
	const char * STANDARD_PERMANENTS[] =
	{
		"string",
		"xpcall",
		"package",
		"tostring",
		"print",
		"os",
		"unpack",
		"require",
		"getfenv",
		"setmetatable",
		"next",
		"assert",
		"tonumber",
		"io",
		"rawequal",
		"collectgarbage",
		"getmetatable",
		"module",
		"rawset",
		"warning",
		"math",
		"debug",
		"pcall",
		"table",
		"newproxy",
		"type",
		"coroutine",
		"select",
		"gcinfo",
		"pairs",
		"rawget",
		"loadstring",
		"ipairs",
		"_VERSION",
		"setfenv",
		"load",
		"error",
		"loadfile",

		"pairs_next",
		"ipairs_next",
		"pluto",
		"Cfg",
		"Translator",
		"Persistence",
		"CommandLine",
		0
	};

	// -------------------------------------------------------------------------

	enum PERMANENT_TABLE_TYPE
	{
		PTT_PERSIST,
		PTT_UNPERSIST,
	};

	// -------------------------------------------------------------------------

	bool PushPermanentsTable(lua_State * L, PERMANENT_TABLE_TYPE TableType)
	{
		// Permanents-Tabelle erstellen.
		lua_newtable(L);

		// Alle Standard-Permanents in die Tabelle einf�gen.
		unsigned int Index = 0;
		while (STANDARD_PERMANENTS[Index])
		{
			// Permanent auf den Stack legen, falls es nicht existiert, wird es einfach ignoriert.
			lua_getglobal(L, STANDARD_PERMANENTS[Index]);
			if (!lua_isnil(L, -1))
			{
				// Namen des Elementes als einzigartigen Wert auf den Stack legen.
				lua_pushstring(L, STANDARD_PERMANENTS[Index]);

				// Falls geladen wird, ist der Name der Index und das Objekt der Wert.
				// In diesem Fall m�ssen also die Position von Name und Objekt auf dem Stack vertauscht werden.
				if (TableType == PTT_UNPERSIST) lua_insert(L, -2);

				// Eintrag in der Tabelle vornehmen.
				lua_settable(L, -3);
			}
			else
			{
				// nil von Stack poppen.
				lua_pop(L, 1);
			}

			++Index;
		}
		
		// Alle Registrierten C-Funktionen in die Tabelle einf�gen.
		// BS_LuaBindhelper legt in der Registry eine Tabelle an, in der alle registrierten C-Funktionen gespeichert werden.

		// Tabelle mit den C-Permanents auf den Stack legen.
		lua_getfield(L, LUA_REGISTRYINDEX, PERMANENTS_TABLE_NAME);

		if (!lua_isnil(L, -1))
		{
			// �ber alle Elemente der Tabelle iterieren.
			lua_pushnil(L);
			while (lua_next(L, -2) != 0)
			{
				// Wert und Index auf dem Stack duplizieren und in der Reihenfolge vertauschen.
				lua_pushvalue(L, -1);
				lua_pushvalue(L, -3);

				// Falls geladen wird, ist der Name der Index und das Objekt der Wert.
				// In diesem Fall m�ssen also die Position von Name und Objekt auf dem Stack vertauscht werden.
				if (TableType == PTT_UNPERSIST) lua_insert(L, -2);

				// Eintrag in der Ergebnistabelle vornehmen.
				lua_settable(L, -6);

				// Wert vom Stack poppen. Der Index liegt dann oben f�r den n�chsten Aufruf von lua_next().
				lua_pop(L, 1);
			}
		}

		// Tabelle mit den C-Permanents vom Stack poppen.
		lua_pop(L, 1);

		// coroutine.yield muss extra in die Permanents-Tabelle eingetragen werden, da inaktive Coroutinen diese C-Funktion auf dem Stack liegen
		// haben.

		// Funktion coroutine.yield auf den Stack legen.
		lua_getglobal(L, "coroutine");
		lua_pushstring(L, "yield");
		lua_gettable(L, -2);

		// coroutine.yield mit eigenem eindeutigen Wert in der Permanents-Tabelle ablegen.
		lua_pushstring(L, "coroutine.yield");

		if (TableType == PTT_UNPERSIST) lua_insert(L, -2);

		lua_settable(L, -4);

		// Tabelle coroutine vom Stack poppen.
		lua_pop(L, 1);

		return true;
	}
}

// -----------------------------------------------------------------------------

namespace
{
	int Chunkwriter(lua_State *L, const void* p, size_t sz, void* ud)
	{
		vector<unsigned char> & chunkData = *reinterpret_cast<vector<unsigned char> * >(ud);
		const unsigned char * buffer = reinterpret_cast<const unsigned char *>(p);

		while (sz--) chunkData.push_back(*buffer++);

		return 1;
	}
}

bool BS_LuaScriptEngine::Persist(BS_OutputPersistenceBlock & Writer)
{
	// Den Lua-Stack leeren. pluto_persist() erwartet, dass der Stack bis aus seine Parameter leer ist.
	lua_settop(m_State, 0);

	// Garbage Collection erzwingen.
	lua_gc(m_State, LUA_GCCOLLECT, 0);

	// Permanents-Tabelle und die zu persistierende Tabelle auf den Stack legen.
	// pluto_persist erwartet diese beiden Objekte auf dem Lua-Stack.
	PushPermanentsTable(m_State, PTT_PERSIST);
	lua_getglobal(m_State, "_G");

	// Lua persistieren und die Daten in einem vector ablegen.
	vector<unsigned char> chunkData;
	pluto_persist(m_State, Chunkwriter, &chunkData);

	// Persistenzdaten in den Writer schreiben.
	Writer.Write(&chunkData[0], chunkData.size());

	// Die beiden Tabellen vom Stack nehmen.
	lua_pop(m_State, 2);

	return true;
}

// -----------------------------------------------------------------------------

namespace
{
	// -------------------------------------------------------------------------

	struct ChunkreaderData
	{
		void *	BufferPtr;
		size_t	Size;
		bool	BufferReturned;
	};

	// -------------------------------------------------------------------------

	const char * Chunkreader(lua_State *L, void *ud, size_t *sz)
	{
		ChunkreaderData & cd = *reinterpret_cast<ChunkreaderData *>(ud);

		if (!cd.BufferReturned)
		{
			cd.BufferReturned = true;
			*sz = cd.Size;
			return reinterpret_cast<const char *>(cd.BufferPtr);
		}
		else
		{
			return 0;
		}
	}

	// -------------------------------------------------------------------------

	void ClearGlobalTable(lua_State * L, const char ** Exceptions)
	{
		// �ber alle Elemente der globalen Tabelle iterieren.
		lua_pushvalue(L, LUA_GLOBALSINDEX);
		lua_pushnil(L);
		while (lua_next(L, -2) != 0)
		{
			// Jetzt liegen der Wert und der Index des aktuellen Elementes auf dem Stack.
			// Der Wert interessiert uns nicht, daher wird er vom Stack gepoppt.
			lua_pop(L, 1);

			// Feststellen, ob das Element auf nil gesetzt , also aus der globalen Tabelle entfernt werden soll.
			// Hierf�r wird gepr�ft, ob das Elementname ein String ist und in der Liste der Ausnahmen vorkommt.
			bool SetElementToNil = true;
			if (lua_isstring(L, -1))
			{
				const char * IndexString = lua_tostring(L, -1);
				const char ** ExceptionsWalker = Exceptions;
				while (*ExceptionsWalker)
				{
					if (strcmp(IndexString, *ExceptionsWalker) == 0) SetElementToNil = false;
					++ExceptionsWalker;
				}
			}

			// Wenn der obige Test ergeben hat, dass das Element entfernt werden soll, wird es entfernt indem der Wert auf nil gesetzt wird.
			if (SetElementToNil)
			{
				lua_pushvalue(L, -1);
				lua_pushnil(L);
				lua_settable(L, LUA_GLOBALSINDEX);
			}
		}	

		// Globale Tabelle vom Stack nehmen.
		lua_pop(L, 1);

		// Garbage-Collection vornehmen, damit die entfernten Elemente alle gel�scht werden.
		lua_gc(L, LUA_GCCOLLECT, 0);
	}
}

// -----------------------------------------------------------------------------

bool BS_LuaScriptEngine::Unpersist(BS_InputPersistenceBlock & Reader)
{
	// Den Lua-Stack leeren. pluto_unpersist() erwartet, dass der Stack bis aus seine Parameter leer ist.
	lua_settop(m_State, 0);

	// Permanents Tabelle auf den Stack legen. Dies passiert schon an dieser Stelle, da zum Erstellen der Tabelle alle Permanents zugreifbar sein
	// m�ssen. Dies ist nur am Anfang dieser Funktion der Fall, im Folgenden wird die globale Tabelle geleert.
	PushPermanentsTable(m_State, PTT_UNPERSIST);

	// Alle Elemente aus der globalen Tabelle mit Ausnhame von _G und __METATABLES entfernen.
	// Danach wird eine Garbage Collection durchgef�hrt und somit alle von Lua verwalteten Objekte gel�scht.
	// __METATABLES wird zun�chst nicht entfernt, da die Metatables f�r die Finalizer der Objekte ben�tigt werden.
	static const char * ClearExceptionsFirstPass[] =
	{
		"_G",
		"__METATABLES",
		0
	};
	ClearGlobalTable(m_State, ClearExceptionsFirstPass);

	// Im zweiten Durchgang werden auch die Metatables entfernt.
	static const char * ClearExceptionsSecondPass[] =
	{
		"_G",
		0
	};
	ClearGlobalTable(m_State, ClearExceptionsSecondPass);

	// Persistierte Lua-Daten einlesen.
	vector<unsigned char> chunkData;
	Reader.Read(chunkData);

	// Chunk-Reader initialisieren. Er wird von pluto_unpersist benutzt um die ben�tigten Daten einzulesen.
	ChunkreaderData cd;
	cd.BufferPtr = &chunkData[0];
	cd.Size = chunkData.size();
	cd.BufferReturned = false;

	pluto_unpersist(m_State, Chunkreader, &cd);

	// Permanents-Tabelle vom Stack nehmen.
	lua_remove(m_State, -2);

	// Die eingelesenen Elemente in die globale Tabelle eintragen.
	lua_pushnil(m_State);
	while (lua_next(m_State, -2) != 0)
	{
		// Die Referenz auf die globale Tabelle (_G) darf nicht �berschrieben werden, sonst tickt Lua total aus.
		bool IsGlobalReference = lua_isstring(m_State, -2) && strcmp(lua_tostring(m_State, -2), "_G") == 0;
		if (!IsGlobalReference)
		{
			lua_pushvalue(m_State, -2);
			lua_pushvalue(m_State, -2);

			lua_settable(m_State, LUA_GLOBALSINDEX);
		}

		// Wert vom Stack poppen. Der Index liegt dann oben f�r den n�chsten Aufruf von lua_next().
		lua_pop(m_State, 1);
	}

	// Tabelle mit den geladenen Daten vom Stack poppen.
	lua_pop(m_State, 1);
	
	// Garbage Collection erzwingen.
	lua_gc(m_State, LUA_GCCOLLECT, 0);

	return true;
}