1// Aseprite
2// Copyright (C) 2018-2022 Igara Studio S.A.
3// Copyright (C) 2001-2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "app/script/engine.h"
13
14#include "app/app.h"
15#include "app/console.h"
16#include "app/doc_exporter.h"
17#include "app/doc_range.h"
18#include "app/pref/preferences.h"
19#include "app/script/luacpp.h"
20#include "app/script/security.h"
21#include "app/sprite_sheet_type.h"
22#include "app/tilemap_mode.h"
23#include "app/tileset_mode.h"
24#include "app/tools/ink_type.h"
25#include "base/chrono.h"
26#include "base/file_handle.h"
27#include "base/fs.h"
28#include "base/fstream_path.h"
29#include "doc/anidir.h"
30#include "doc/blend_mode.h"
31#include "doc/color_mode.h"
32#include "filters/target.h"
33#include "ui/mouse_button.h"
34
35#include <fstream>
36#include <sstream>
37#include <stack>
38#include <string>
39
40namespace app {
41namespace script {
42
43namespace {
44
45// High precision clock.
46base::Chrono luaClock;
47
48// Stack of script filenames that are being executed.
49std::stack<std::string> current_script_dirs;
50
51// Just one debugger delegate is possible.
52DebuggerDelegate* g_debuggerDelegate = nullptr;
53
54class AddScriptFilename {
55public:
56 AddScriptFilename(const std::string& fn) {
57 current_script_dirs.push(fn);
58 }
59 ~AddScriptFilename() {
60 current_script_dirs.pop();
61 }
62};
63
64int print(lua_State* L)
65{
66 std::string output;
67 int n = lua_gettop(L); /* number of arguments */
68 int i;
69 lua_getglobal(L, "tostring");
70 for (i=1; i<=n; i++) {
71 lua_pushvalue(L, -1); // function to be called
72 lua_pushvalue(L, i); // value to print
73 lua_call(L, 1, 1);
74 size_t l;
75 const char* s = lua_tolstring(L, -1, &l); // get result
76 if (s == nullptr)
77 return luaL_error(L, "'tostring' must return a string to 'print'");
78 if (i > 1)
79 output.push_back('\t');
80 output.insert(output.size(), s, l);
81 lua_pop(L, 1); // pop result
82 }
83 if (!output.empty()) {
84 auto app = App::instance();
85 if (app && app->scriptEngine())
86 app->scriptEngine()->consolePrint(output.c_str());
87 else {
88 std::printf("%s\n", output.c_str());
89 std::fflush(stdout);
90 }
91 }
92 return 0;
93}
94
95static int dofilecont(lua_State *L, int d1, lua_KContext d2)
96{
97 (void)d1;
98 (void)d2;
99 return lua_gettop(L) - 1;
100}
101
102int dofile(lua_State *L)
103{
104 const char* argFname = luaL_optstring(L, 1, NULL);
105 std::string fname = argFname;
106
107 if (!base::is_file(fname) &&
108 !current_script_dirs.empty()) {
109 // Try to complete a relative filename
110 std::string altFname =
111 base::join_path(base::get_file_path(current_script_dirs.top()),
112 fname);
113 if (base::is_file(altFname))
114 fname = altFname;
115 }
116
117 lua_settop(L, 1);
118 if (luaL_loadfile(L, fname.c_str()) != LUA_OK)
119 return lua_error(L);
120 {
121 AddScriptFilename add(fname);
122 lua_callk(L, 0, LUA_MULTRET, 0, dofilecont);
123 }
124 return dofilecont(L, 0, 0);
125}
126
127int os_clock(lua_State* L)
128{
129 lua_pushnumber(L, luaClock.elapsed());
130 return 1;
131}
132
133int unsupported(lua_State* L)
134{
135 // debug.getinfo(1, "n").name
136 lua_getglobal(L, "debug");
137 lua_getfield(L, -1, "getinfo");
138 lua_remove(L, -2);
139 lua_pushinteger(L, 1);
140 lua_pushstring(L, "n");
141 lua_call(L, 2, 1);
142 lua_getfield(L, -1, "name");
143 return luaL_error(L, "unsupported function '%s'",
144 lua_tostring(L, -1));
145}
146
147} // anonymous namespace
148
149void register_app_object(lua_State* L);
150void register_app_pixel_color_object(lua_State* L);
151void register_app_fs_object(lua_State* L);
152void register_app_command_object(lua_State* L);
153void register_app_preferences_object(lua_State* L);
154
155void register_brush_class(lua_State* L);
156void register_cel_class(lua_State* L);
157void register_cels_class(lua_State* L);
158void register_color_class(lua_State* L);
159void register_color_space_class(lua_State* L);
160#ifdef ENABLE_UI
161void register_dialog_class(lua_State* L);
162#endif
163void register_events_class(lua_State* L);
164void register_frame_class(lua_State* L);
165void register_frames_class(lua_State* L);
166void register_grid_class(lua_State* L);
167void register_image_class(lua_State* L);
168void register_image_iterator_class(lua_State* L);
169void register_image_spec_class(lua_State* L);
170void register_images_class(lua_State* L);
171void register_layer_class(lua_State* L);
172void register_layers_class(lua_State* L);
173void register_palette_class(lua_State* L);
174void register_palettes_class(lua_State* L);
175void register_plugin_class(lua_State* L);
176void register_point_class(lua_State* L);
177void register_range_class(lua_State* L);
178void register_rect_class(lua_State* L);
179void register_selection_class(lua_State* L);
180void register_site_class(lua_State* L);
181void register_size_class(lua_State* L);
182void register_slice_class(lua_State* L);
183void register_slices_class(lua_State* L);
184void register_sprite_class(lua_State* L);
185void register_sprites_class(lua_State* L);
186void register_tag_class(lua_State* L);
187void register_tags_class(lua_State* L);
188void register_tileset_class(lua_State* L);
189void register_tilesets_class(lua_State* L);
190void register_tool_class(lua_State* L);
191void register_version_class(lua_State* L);
192void register_websocket_class(lua_State* L);
193
194void set_app_params(lua_State* L, const Params& params);
195
196// We use our own fopen() that supports Unicode filename on Windows
197extern "C" FILE* lua_user_fopen(const char* fname,
198 const char* mode)
199{
200 return base::open_file_raw(fname, mode);
201}
202
203Engine::Engine()
204 : L(luaL_newstate())
205 , m_delegate(nullptr)
206 , m_printLastResult(false)
207{
208#if _DEBUG
209 int top = lua_gettop(L);
210#endif
211
212 // Standard Lua libraries
213 luaL_requiref(L, LUA_GNAME, luaopen_base, 1);
214 luaL_requiref(L, LUA_COLIBNAME, luaopen_coroutine, 1);
215 luaL_requiref(L, LUA_TABLIBNAME, luaopen_table, 1);
216 luaL_requiref(L, LUA_IOLIBNAME, luaopen_io, 1);
217 luaL_requiref(L, LUA_OSLIBNAME, luaopen_os, 1);
218 luaL_requiref(L, LUA_STRLIBNAME, luaopen_string, 1);
219 luaL_requiref(L, LUA_MATHLIBNAME, luaopen_math, 1);
220 luaL_requiref(L, LUA_UTF8LIBNAME, luaopen_utf8, 1);
221 luaL_requiref(L, LUA_DBLIBNAME, luaopen_debug, 1);
222 lua_pop(L, 9);
223
224 // Overwrite Lua functions
225 lua_register(L, "print", print);
226 lua_register(L, "dofile", dofile);
227
228 lua_getglobal(L, "os");
229 for (const char* name : { "remove", "rename", "exit", "tmpname" }) {
230 lua_pushcfunction(L, unsupported);
231 lua_setfield(L, -2, name);
232 }
233 lua_pushcfunction(L, os_clock);
234 lua_setfield(L, -2, "clock");
235 lua_pop(L, 1);
236
237 // Wrap io.open()
238 lua_getglobal(L, "io");
239 lua_getfield(L, -1, "open");
240 lua_pushcclosure(L, secure_io_open, 1);
241 lua_setfield(L, -2, "open");
242 lua_pop(L, 1);
243
244 // Wrap os.execute()
245 lua_getglobal(L, "os");
246 lua_getfield(L, -1, "execute");
247 lua_pushcclosure(L, secure_os_execute, 1);
248 lua_setfield(L, -2, "execute");
249 lua_pop(L, 1);
250
251 // Generic code used by metatables
252 run_mt_index_code(L);
253
254 // Register global app object
255 register_app_object(L);
256 register_app_pixel_color_object(L);
257 register_app_fs_object(L);
258 register_app_command_object(L);
259 register_app_preferences_object(L);
260
261 // Register constants
262 lua_newtable(L);
263 lua_pushvalue(L, -1);
264 lua_setglobal(L, "ColorMode");
265 setfield_integer(L, "RGB", doc::ColorMode::RGB);
266 setfield_integer(L, "GRAY", doc::ColorMode::GRAYSCALE);
267 setfield_integer(L, "GRAYSCALE", doc::ColorMode::GRAYSCALE);
268 setfield_integer(L, "INDEXED", doc::ColorMode::INDEXED);
269 setfield_integer(L, "TILEMAP", doc::ColorMode::TILEMAP);
270 lua_pop(L, 1);
271
272 lua_newtable(L);
273 lua_pushvalue(L, -1);
274 lua_setglobal(L, "AniDir");
275 setfield_integer(L, "FORWARD", doc::AniDir::FORWARD);
276 setfield_integer(L, "REVERSE", doc::AniDir::REVERSE);
277 setfield_integer(L, "PING_PONG", doc::AniDir::PING_PONG);
278 lua_pop(L, 1);
279
280 lua_newtable(L);
281 lua_pushvalue(L, -1);
282 lua_setglobal(L, "BlendMode");
283 setfield_integer(L, "NORMAL", doc::BlendMode::NORMAL);
284 setfield_integer(L, "MULTIPLY", doc::BlendMode::MULTIPLY);
285 setfield_integer(L, "SCREEN", doc::BlendMode::SCREEN);
286 setfield_integer(L, "OVERLAY", doc::BlendMode::OVERLAY);
287 setfield_integer(L, "DARKEN", doc::BlendMode::DARKEN);
288 setfield_integer(L, "LIGHTEN", doc::BlendMode::LIGHTEN);
289 setfield_integer(L, "COLOR_DODGE", doc::BlendMode::COLOR_DODGE);
290 setfield_integer(L, "COLOR_BURN", doc::BlendMode::COLOR_BURN);
291 setfield_integer(L, "HARD_LIGHT", doc::BlendMode::HARD_LIGHT);
292 setfield_integer(L, "SOFT_LIGHT", doc::BlendMode::SOFT_LIGHT);
293 setfield_integer(L, "DIFFERENCE", doc::BlendMode::DIFFERENCE);
294 setfield_integer(L, "EXCLUSION", doc::BlendMode::EXCLUSION);
295 setfield_integer(L, "HSL_HUE", doc::BlendMode::HSL_HUE);
296 setfield_integer(L, "HSL_SATURATION", doc::BlendMode::HSL_SATURATION);
297 setfield_integer(L, "HSL_COLOR", doc::BlendMode::HSL_COLOR);
298 setfield_integer(L, "HSL_LUMINOSITY", doc::BlendMode::HSL_LUMINOSITY);
299 setfield_integer(L, "ADDITION", doc::BlendMode::ADDITION);
300 setfield_integer(L, "SUBTRACT", doc::BlendMode::SUBTRACT);
301 setfield_integer(L, "DIVIDE", doc::BlendMode::DIVIDE);
302 lua_pop(L, 1);
303
304 lua_newtable(L);
305 lua_pushvalue(L, -1);
306 lua_setglobal(L, "RangeType");
307 setfield_integer(L, "EMPTY", DocRange::kNone);
308 setfield_integer(L, "LAYERS", DocRange::kLayers);
309 setfield_integer(L, "FRAMES", DocRange::kFrames);
310 setfield_integer(L, "CELS", DocRange::kCels);
311 lua_pop(L, 1);
312
313 lua_newtable(L);
314 lua_pushvalue(L, -1);
315 lua_setglobal(L, "SpriteSheetType");
316 setfield_integer(L, "HORIZONTAL", SpriteSheetType::Horizontal);
317 setfield_integer(L, "VERTICAL", SpriteSheetType::Vertical);
318 setfield_integer(L, "ROWS", SpriteSheetType::Rows);
319 setfield_integer(L, "COLUMNS", SpriteSheetType::Columns);
320 setfield_integer(L, "PACKED", SpriteSheetType::Packed);
321 lua_pop(L, 1);
322
323 lua_newtable(L);
324 lua_pushvalue(L, -1);
325 lua_setglobal(L, "SpriteSheetDataFormat");
326 setfield_integer(L, "JSON_HASH", SpriteSheetDataFormat::JsonHash);
327 setfield_integer(L, "JSON_ARRAY", SpriteSheetDataFormat::JsonArray);
328 lua_pop(L, 1);
329
330 lua_newtable(L);
331 lua_pushvalue(L, -1);
332 lua_setglobal(L, "BrushType");
333 setfield_integer(L, "CIRCLE", doc::kCircleBrushType);
334 setfield_integer(L, "SQUARE", doc::kSquareBrushType);
335 setfield_integer(L, "LINE", doc::kLineBrushType);
336 setfield_integer(L, "IMAGE", doc::kImageBrushType);
337 lua_pop(L, 1);
338
339 lua_newtable(L);
340 lua_pushvalue(L, -1);
341 lua_setglobal(L, "BrushPattern");
342 setfield_integer(L, "ORIGIN", doc::BrushPattern::ALIGNED_TO_SRC);
343 setfield_integer(L, "TARGET", doc::BrushPattern::ALIGNED_TO_DST);
344 setfield_integer(L, "NONE", doc::BrushPattern::PAINT_BRUSH);
345 lua_pop(L, 1);
346
347 lua_newtable(L);
348 lua_pushvalue(L, -1);
349 lua_setglobal(L, "Ink");
350 setfield_integer(L, "SIMPLE", app::tools::InkType::SIMPLE);
351 setfield_integer(L, "ALPHA_COMPOSITING", app::tools::InkType::ALPHA_COMPOSITING);
352 setfield_integer(L, "COPY_COLOR", app::tools::InkType::COPY_COLOR);
353 setfield_integer(L, "LOCK_ALPHA", app::tools::InkType::LOCK_ALPHA);
354 setfield_integer(L, "SHADING", app::tools::InkType::SHADING);
355 lua_pop(L, 1);
356
357 lua_newtable(L);
358 lua_pushvalue(L, -1);
359 lua_setglobal(L, "FilterChannels");
360 setfield_integer(L, "RED", TARGET_RED_CHANNEL);
361 setfield_integer(L, "GREEN", TARGET_GREEN_CHANNEL);
362 setfield_integer(L, "BLUE", TARGET_BLUE_CHANNEL);
363 setfield_integer(L, "ALPHA", TARGET_ALPHA_CHANNEL);
364 setfield_integer(L, "GRAY", TARGET_GRAY_CHANNEL);
365 setfield_integer(L, "INDEX", TARGET_INDEX_CHANNEL);
366 setfield_integer(L, "RGB", TARGET_RED_CHANNEL | TARGET_GREEN_CHANNEL | TARGET_BLUE_CHANNEL);
367 setfield_integer(L, "RGBA", TARGET_RED_CHANNEL | TARGET_GREEN_CHANNEL | TARGET_BLUE_CHANNEL | TARGET_ALPHA_CHANNEL);
368 setfield_integer(L, "GRAYA", TARGET_GRAY_CHANNEL | TARGET_ALPHA_CHANNEL);
369 lua_pop(L, 1);
370
371 lua_newtable(L);
372 lua_pushvalue(L, -1);
373 lua_setglobal(L, "MouseButton");
374 setfield_integer(L, "NONE", (int)ui::kButtonNone);
375 setfield_integer(L, "LEFT", (int)ui::kButtonLeft);
376 setfield_integer(L, "RIGHT", (int)ui::kButtonRight);
377 setfield_integer(L, "MIDDLE", (int)ui::kButtonMiddle);
378 setfield_integer(L, "X1", (int)ui::kButtonX1);
379 setfield_integer(L, "X2", (int)ui::kButtonX2);
380 lua_pop(L, 1);
381
382 lua_newtable(L);
383 lua_pushvalue(L, -1);
384 lua_setglobal(L, "TilemapMode");
385 setfield_integer(L, "PIXELS", TilemapMode::Pixels);
386 setfield_integer(L, "TILES", TilemapMode::Tiles);
387 lua_pop(L, 1);
388
389 lua_newtable(L);
390 lua_pushvalue(L, -1);
391 lua_setglobal(L, "TilesetMode");
392 setfield_integer(L, "MANUAL", TilesetMode::Manual);
393 setfield_integer(L, "AUTO", TilesetMode::Auto);
394 setfield_integer(L, "STACK", TilesetMode::Stack);
395 lua_pop(L, 1);
396
397 lua_newtable(L);
398 lua_pushvalue(L, -1);
399 lua_setglobal(L, "SelectionMode");
400 setfield_integer(L, "REPLACE", (int)gen::SelectionMode::REPLACE);
401 setfield_integer(L, "ADD", (int)gen::SelectionMode::ADD);
402 setfield_integer(L, "SUBTRACT", (int)gen::SelectionMode::SUBTRACT);
403 setfield_integer(L, "INTERSECT", (int)gen::SelectionMode::INTERSECT);
404 lua_pop(L, 1);
405
406 // Register classes/prototypes
407 register_brush_class(L);
408 register_cel_class(L);
409 register_cels_class(L);
410 register_color_class(L);
411 register_color_space_class(L);
412#ifdef ENABLE_UI
413 register_dialog_class(L);
414#endif
415 register_events_class(L);
416 register_frame_class(L);
417 register_frames_class(L);
418 register_grid_class(L);
419 register_image_class(L);
420 register_image_iterator_class(L);
421 register_image_spec_class(L);
422 register_images_class(L);
423 register_layer_class(L);
424 register_layers_class(L);
425 register_palette_class(L);
426 register_palettes_class(L);
427 register_plugin_class(L);
428 register_point_class(L);
429 register_range_class(L);
430 register_rect_class(L);
431 register_selection_class(L);
432 register_site_class(L);
433 register_size_class(L);
434 register_slice_class(L);
435 register_slices_class(L);
436 register_sprite_class(L);
437 register_sprites_class(L);
438 register_tag_class(L);
439 register_tags_class(L);
440 register_tileset_class(L);
441 register_tilesets_class(L);
442 register_tool_class(L);
443 register_version_class(L);
444#if ENABLE_WEBSOCKET
445 register_websocket_class(L);
446 #endif
447
448 // Check that we have a clean start (without dirty in the stack)
449 ASSERT(lua_gettop(L) == top);
450}
451
452Engine::~Engine()
453{
454 ASSERT(L == nullptr);
455}
456
457void Engine::destroy()
458{
459#ifdef ENABLE_UI
460 close_all_dialogs();
461#endif
462 lua_close(L);
463 L = nullptr;
464}
465
466void Engine::printLastResult()
467{
468 m_printLastResult = true;
469}
470
471bool Engine::evalCode(const std::string& code,
472 const std::string& filename)
473{
474 bool ok = true;
475 try {
476 if (luaL_loadbuffer(L, code.c_str(), code.size(), filename.c_str()) ||
477 lua_pcall(L, 0, 1, 0)) {
478 const char* s = lua_tostring(L, -1);
479 if (s)
480 onConsoleError(s);
481 ok = false;
482 m_returnCode = -1;
483 }
484 else {
485 // Return code
486 if (lua_isinteger(L, -1))
487 m_returnCode = lua_tointeger(L, -1);
488 else
489 m_returnCode = 0;
490
491 // Code was executed correctly
492 if (m_printLastResult) {
493 if (!lua_isnone(L, -1)) {
494 const char* result = lua_tostring(L, -1);
495 if (result)
496 onConsolePrint(result);
497 }
498 }
499 }
500 lua_pop(L, 1);
501 }
502 catch (const std::exception& ex) {
503 onConsoleError(ex.what());
504 ok = false;
505 m_returnCode = -1;
506 }
507
508 // Collect script garbage.
509 lua_gc(L, LUA_GCCOLLECT);
510 return ok;
511}
512
513bool Engine::evalFile(const std::string& filename,
514 const Params& params)
515{
516 std::stringstream buf;
517 {
518 std::ifstream s(FSTREAM_PATH(filename));
519 // Returns false if we cannot open the file
520 if (!s)
521 return false;
522 buf << s.rdbuf();
523 }
524 std::string absFilename = base::get_absolute_path(filename);
525
526 AddScriptFilename add(absFilename);
527 set_app_params(L, params);
528
529 if (g_debuggerDelegate)
530 g_debuggerDelegate->startFile(absFilename, buf.str());
531
532 bool result = evalCode(buf.str(), "@" + absFilename);
533
534 if (g_debuggerDelegate)
535 g_debuggerDelegate->endFile(absFilename);
536
537 return result;
538}
539
540void Engine::startDebugger(DebuggerDelegate* debuggerDelegate)
541{
542 g_debuggerDelegate = debuggerDelegate;
543
544 lua_Hook hook = [](lua_State* L, lua_Debug* ar) {
545 int ret = lua_getinfo(L, "l", ar);
546 if (ret == 0 || ar->currentline < 0)
547 return;
548
549 g_debuggerDelegate->hook(L, ar);
550 };
551
552 lua_sethook(L, hook, LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE | LUA_MASKCOUNT, 1);
553}
554
555void Engine::stopDebugger()
556{
557 lua_sethook(L, nullptr, 0, 0);
558}
559
560void Engine::onConsoleError(const char* text)
561{
562 if (text && m_delegate)
563 m_delegate->onConsoleError(text);
564 else
565 onConsolePrint(text);
566}
567
568void Engine::onConsolePrint(const char* text)
569{
570 if (!text)
571 return;
572
573 if (m_delegate)
574 m_delegate->onConsolePrint(text);
575 else {
576 std::printf("%s\n", text);
577 std::fflush(stdout);
578 }
579}
580
581} // namespace script
582} // namespace app
583