From 7d99ade7547dfa3abccf7631000c43f6191ec580 Mon Sep 17 00:00:00 2001 From: Christian Kaiser Date: Sun, 18 May 2025 09:53:50 -0300 Subject: [PATCH] [WIP] Implement custom format support for extensions, saving and loading --- src/app/commands/cmd_open_file.cpp | 40 +++++- src/app/commands/cmd_open_file.h | 4 + src/app/commands/cmd_save_file.cpp | 72 +++++++++-- src/app/commands/cmd_save_file.h | 10 ++ src/app/extensions.cpp | 192 ++++++++++++++++++++++++++++- src/app/extensions.h | 55 ++++++++- src/app/file/file.cpp | 20 +++ src/app/file/file.h | 4 + src/app/script/plugin_class.cpp | 58 +++++++++ src/app/script/security.cpp | 5 + src/app/script/security.h | 3 + 11 files changed, 450 insertions(+), 13 deletions(-) diff --git a/src/app/commands/cmd_open_file.cpp b/src/app/commands/cmd_open_file.cpp index f8f088371..6265884e3 100644 --- a/src/app/commands/cmd_open_file.cpp +++ b/src/app/commands/cmd_open_file.cpp @@ -16,20 +16,23 @@ #include "app/commands/params.h" #include "app/console.h" #include "app/doc.h" +#include "app/extensions.h" #include "app/file/file.h" #include "app/file_selector.h" #include "app/i18n/strings.h" -#include "app/modules/gui.h" #include "app/pref/preferences.h" #include "app/recent_files.h" #include "app/ui/status_bar.h" #include "app/ui_context.h" #include "app/util/open_file_job.h" #include "base/fs.h" -#include "base/thread.h" #include "doc/sprite.h" #include "ui/ui.h" +#ifdef ENABLE_SCRIPTING + #include "app/script/security.h" +#endif + #include namespace app { @@ -142,6 +145,13 @@ void OpenFileCommand::onExecute(Context* context) return; if (fop->hasError()) { +#ifdef ENABLE_SCRIPTING + if (fop->hasUnknownFormatError() && loadCustomFormat(filename)) { + // If a script was detected and loaded the file without errors, then we can return safely + return; + } +#endif + console.printf(fop->error().c_str()); unrecent = true; } @@ -230,6 +240,32 @@ std::string OpenFileCommand::onGetFriendlyName() const m_filename)))); } +#ifdef ENABLE_SCRIPTING +bool OpenFileCommand::loadCustomFormat(const std::string& filename) +{ + const std::string detectedExtension = base::string_to_lower(base::get_file_extension(filename)); + if (detectedExtension.empty()) + return false; + + for (const auto& customFormatExtension : App::instance()->extensions().customFormatList()) { + if (base::string_to_lower(customFormatExtension) == detectedExtension) { + for (Extension* extension : App::instance()->extensions()) { + if (!extension->isEnabled() || !extension->hasFileFormats()) + continue; + + auto formatId = extension->getCustomFormatIdForExtension(customFormatExtension, + Extension::FileFormat::Load); + if (formatId.has_value()) { + return extension->loadCustomFormat(*formatId, filename); + } + } + } + } + + return false; +} +#endif + Command* CommandFactory::createOpenFileCommand() { return new OpenFileCommand; diff --git a/src/app/commands/cmd_open_file.h b/src/app/commands/cmd_open_file.h index d70a7b163..c39048c4b 100644 --- a/src/app/commands/cmd_open_file.h +++ b/src/app/commands/cmd_open_file.h @@ -32,6 +32,10 @@ protected: std::string onGetFriendlyName() const override; private: +#ifdef ENABLE_SCRIPTING + bool loadCustomFormat(const std::string& filename); +#endif + std::string m_filename; std::string m_folder; bool m_ui; diff --git a/src/app/commands/cmd_save_file.cpp b/src/app/commands/cmd_save_file.cpp index cdebc7e2b..3f754d998 100644 --- a/src/app/commands/cmd_save_file.cpp +++ b/src/app/commands/cmd_save_file.cpp @@ -19,6 +19,7 @@ #include "app/context_access.h" #include "app/doc.h" #include "app/doc_undo.h" +#include "app/extensions.h" #include "app/file/file.h" #include "app/file/gif_format.h" #include "app/file/png_format.h" @@ -207,12 +208,12 @@ void SaveFileBaseCommand::saveDocumentInBackground(const Context* context, bounds = document->sprite()->bounds(); } - FileOpROI roi(document, - bounds, - params().slice(), - params().tag(), - m_framesSeq, - m_adjustFramesByTag); + const FileOpROI roi(document, + bounds, + params().slice(), + params().tag(), + m_framesSeq, + m_adjustFramesByTag); std::unique_ptr fop(FileOp::createSaveDocumentOperation(context, roi, @@ -222,13 +223,23 @@ void SaveFileBaseCommand::saveDocumentInBackground(const Context* context, if (!fop) return; + bool runJob = true; +#ifdef ENABLE_SCRIPTING + if (fop->hasUnknownFormatError() && + saveCustomFormat(context, roi, filename, params().filenameFormat(), params().ignoreEmpty())) { + runJob = false; + } +#endif + if (resizeOnTheFly == ResizeOnTheFly::On) fop->setOnTheFlyScale(scale); - SaveFileJob job(fop.get(), params().ui()); - job.showProgressWindow(); + if (runJob) { + SaveFileJob job(fop.get(), params().ui()); + job.showProgressWindow(); + } - if (fop->hasError()) { + if (fop->hasError() && runJob) { Console console; console.printf(fop->error().c_str()); @@ -259,6 +270,49 @@ void SaveFileBaseCommand::saveDocumentInBackground(const Context* context, } } +#ifdef ENABLE_SCRIPTING +bool SaveFileBaseCommand::saveCustomFormat(const Context* context, + const FileOpROI& roi, + const std::string& filename, + const std::string& filenameFormatArg, + bool ignoreEmptyFrames) +{ + const std::string detectedExtension = base::string_to_lower(base::get_file_extension(filename)); + if (detectedExtension.empty()) + return false; + + for (const auto& customFormatExtension : App::instance()->extensions().customFormatList()) { + if (base::string_to_lower(customFormatExtension) == detectedExtension) { + for (Extension* extension : App::instance()->extensions()) { + if (!extension->isEnabled() || !extension->hasFileFormats()) + continue; + + auto formatId = extension->getCustomFormatIdForExtension(customFormatExtension, + Extension::FileFormat::Save); + if (formatId.has_value()) { + // Generate the options struct + Extension::FileFormatSaveOptions opts; + opts.canvasSize = roi.fileCanvasSize(); + opts.bounds = roi.bounds(); + opts.frames = roi.frames(); + opts.fromFrame = roi.fromFrame(); + opts.toFrame = roi.toFrame(); + opts.ignoreEmptyFrames = ignoreEmptyFrames; + + // TODO: ¿FramesSquence & frameBounds? + return extension->saveCustomFormat(*formatId, + filename, + context->activeDocument()->sprite(), + opts); + } + } + } + } + + return false; +} +#endif + ////////////////////////////////////////////////////////////////////// class SaveFileCommand : public SaveFileBaseCommand { diff --git a/src/app/commands/cmd_save_file.h b/src/app/commands/cmd_save_file.h index eab6837f7..06dcf2bd1 100644 --- a/src/app/commands/cmd_save_file.h +++ b/src/app/commands/cmd_save_file.h @@ -18,6 +18,9 @@ #include +namespace app { +class FileOpROI; +} namespace app { class Doc; @@ -85,6 +88,13 @@ protected: const MarkAsSaved markAsSaved, const ResizeOnTheFly resizeOnTheFly = ResizeOnTheFly::Off, const gfx::PointF& scale = gfx::PointF(1.0, 1.0)); +#ifdef ENABLE_SCRIPTING + bool saveCustomFormat(const Context* context, + const FileOpROI& roi, + const std::string& filename, + const std::string& filenameFormatArg, + bool ignoreEmptyFrames); +#endif doc::FramesSequence m_framesSeq; bool m_adjustFramesByTag; diff --git a/src/app/extensions.cpp b/src/app/extensions.cpp index 1e2e1458d..48261dc89 100644 --- a/src/app/extensions.cpp +++ b/src/app/extensions.cpp @@ -40,15 +40,20 @@ #include "archive.h" #include "archive_entry.h" +#include "doc.h" #include "json11.hpp" #include -#include #include #include #include "base/log.h" +#ifdef ENABLE_SCRIPTING + #include "script/docobj.h" + #include "script/security.h" +#endif + namespace app { const char* Extension::kAsepriteDefaultThemeExtensionName = "aseprite-theme"; @@ -358,6 +363,28 @@ void Extension::addMenuSeparator(ui::Widget* widget) m_plugin.items.push_back(item); } +void Extension::addFileFormat(const FileFormat& format) +{ + // Check for any duplicates in global extension list for both ID and file extensions. + for (const auto& extension : App::instance()->extensions()) { + if (m_fileFormats.count(format.id) > 0) + throw base::Exception("Duplicated format ID: " + format.id); + + for (const auto& [id, other] : extension->m_fileFormats) { + for (const auto& otherExt : other.extensions) { + for (const auto& ourExt : format.extensions) { + if (otherExt == ourExt) + throw base::Exception( + "Extension attempting to register a custom format extension that already exists: " + + format.id + " : " + ourExt); + } + } + } + } + + m_fileFormats.try_emplace(format.id, format); +} + #endif bool Extension::canBeDisabled() const @@ -769,6 +796,147 @@ void Extension::addScript(const std::string& fn) updateCategory(Category::Scripts); } +std::optional Extension::getCustomFormatIdForExtension( + const std::string& ext, + const FileFormat::Support support) const +{ + ASSERT(support > 0); + + if (!hasFileFormats()) + return std::nullopt; + + for (const auto& [id, format] : m_fileFormats) { + for (const auto& formatExtensionString : format.extensions) { + if (formatExtensionString == ext && format.supports(support)) { + return id; + } + } + } + + return std::nullopt; +} + +bool Extension::loadCustomFormat(const std::string& formatId, const std::string& filename) const +{ + const auto& format = m_fileFormats.at(formatId); + ASSERT(format.onsaveRef > -1); + + if (!format.supports(FileFormat::Load)) + return false; + + script::Engine* engine = App::instance()->scriptEngine(); + lua_State* L = engine->luaState(); + + lua_rawgeti(L, LUA_REGISTRYINDEX, format.onloadRef); + lua_pushstring(L, filename.c_str()); + + lua_pushcclosure(L, script::get_original_io_open(), 0); + lua_pushstring(L, filename.c_str()); + lua_pushstring(L, format.binary ? "rb" : "r"); + + if (lua_pcall(L, 2, 1, 0) != LUA_OK) { + Console().printf("Failed to open file for reading"); + return false; + } + + if (lua_pcall(L, 2, 1, 0) != LUA_OK) { + Console().printf("Failed to call onload function, custom format '%s'", format.id.c_str()); + return false; + } + + return lua_toboolean(L, -1) > 0; +} + +bool Extension::saveCustomFormat(const std::string& formatId, + const std::string& filename, + const Sprite* sprite, + const FileFormatSaveOptions& saveOptions) const +{ + const auto& format = m_fileFormats.at(formatId); + ASSERT(format.onsaveRef > -1); + + if (!format.supports(FileFormat::Save)) + return false; + + // TODO: Probably something nice to have in base, with a version that doesn't use rand() and isn't + // just copy-pasted from StackOverflow :^] + auto random_temp_file = []() { + auto randchar = []() -> char { + const char charset[] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + const size_t max_index = (sizeof(charset) - 1); + return charset[rand() % max_index]; + }; + std::string str(12, 0); + std::generate_n(str.begin(), 12, randchar); + return "asecmf_" + str + ".tmp"; + }; + + const std::string destTempFile = base::join_path(base::get_temp_path(), random_temp_file()); + + script::Engine* engine = App::instance()->scriptEngine(); + lua_State* L = engine->luaState(); + + lua_rawgeti(L, LUA_REGISTRYINDEX, format.onsaveRef); + lua_pushstring(L, filename.c_str()); + + lua_pushcclosure(L, script::get_original_io_open(), 0); + lua_pushstring(L, destTempFile.c_str()); + lua_pushstring(L, format.binary ? "wb" : "w"); + + if (lua_pcall(L, 2, 1, 0) != LUA_OK) { + Console().printf("Failed to open file for writing"); + return false; + } + + script::push_docobj(L, sprite); + + // Creating push options table. + lua_newtable(L); + script::push_value_to_lua(L, saveOptions.canvasSize); + lua_setfield(L, -2, "canvasSize"); + script::push_value_to_lua(L, saveOptions.bounds); + lua_setfield(L, -2, "bounds"); + script::setfield_integer(L, "frames", saveOptions.frames); + script::setfield_integer(L, "fromFrame", saveOptions.fromFrame); + script::setfield_integer(L, "toFrame", saveOptions.toFrame); + lua_pushboolean(L, saveOptions.ignoreEmptyFrames); + lua_setfield(L, -2, "ignoreEmptyFrames"); + + if (lua_pcall(L, 4, 1, 0) != LUA_OK) { + Console().printf("Failed to call onload function, custom format '%s'", format.id.c_str()); + return false; + } + + if (lua_toboolean(L, -1) > 0) { + // Transfer the temporary file to the actual file location + try { + // TODO: This is probably *too* cautious? I just don't want a random script we can't control + // to delete a user's files. + // TODO: Should also be moved to a job. + std::string mv; + if (base::is_file(filename)) { + mv = base::join_path(base::get_temp_path(), base::get_file_name(filename) + ".bak"); + base::move_file(filename, mv); + } + + base::move_file(destTempFile, filename); + if (!mv.empty()) + base::delete_file(mv); + + return true; + } + catch (std::exception& e) { + Console().printf("Saving failed: '%s'", e.what()); + return false; + } + } + + // TODO: Do we need to close the Lua file handle ourselves if something breaks? + return false; +} + #endif // ENABLE_SCRIPTING ////////////////////////////////////////////////////////////////////// @@ -923,6 +1091,28 @@ std::vector Extensions::ditheringMatrices() return result; } +#ifdef ENABLE_SCRIPTING +std::vector Extensions::customFormatList( + const Extension::FileFormat::Support support) const +{ + std::vector result; + for (auto* ext : m_extensions) { + if (!ext->isEnabled()) + continue; + + for (const auto& [id, format] : ext->m_fileFormats) { + if (!format.supports(support)) + continue; + + for (const auto& extension : format.extensions) + result.push_back(extension); + } + } + + return result; +} +#endif + void Extensions::enableExtension(Extension* extension, const bool state) { extension->enable(state); diff --git a/src/app/extensions.h b/src/app/extensions.h index 145692fcf..57867f95f 100644 --- a/src/app/extensions.h +++ b/src/app/extensions.h @@ -10,17 +10,24 @@ #pragma once #include "app/i18n/lang_info.h" +#include "doc/frame.h" +#include "gfx/rect.h" +#include "gfx/size.h" #include "obs/signal.h" #include "render/dithering_matrix.h" #include +#include #include +#include #include namespace ui { class Widget; } - +namespace doc { +class Sprite; +} namespace app { // Key=id @@ -28,6 +35,7 @@ namespace app { using ExtensionItems = std::map; class Extensions; +class Doc; struct ExtensionInfo { std::string name; @@ -83,6 +91,34 @@ public: ThemeInfo(const std::string& path, const std::string& variant) : path(path), variant(variant) {} }; +#ifdef ENABLE_SCRIPTING + struct FileFormat { + enum Support : uint8_t { Invalid = 0, Full, Save, Load }; + std::string id; + std::unordered_set extensions; + bool binary = true; + int onloadRef = -1; + int onsaveRef = -1; + + bool supports(const Support support) const + { + if (support == Load && onloadRef > -1) + return true; + if (support == Save && onsaveRef > -1) + return true; + return support == Full && onloadRef > -1 && onsaveRef > -1; + } + }; + struct FileFormatSaveOptions { + gfx::Size canvasSize; + gfx::Rect bounds; + doc::frame_t frames = 0; + doc::frame_t fromFrame = 0; + doc::frame_t toFrame = 0; + bool ignoreEmptyFrames = false; + }; +#endif + using Languages = std::map; using Themes = std::map; using DitheringMatrices = std::map; @@ -122,6 +158,7 @@ public: void removeMenuGroup(const std::string& id); void addMenuSeparator(ui::Widget* widget); + void addFileFormat(const FileFormat& format); #endif bool isEnabled() const { return m_isEnabled; } @@ -137,6 +174,15 @@ public: #ifdef ENABLE_SCRIPTING bool hasScripts() const { return !m_plugin.scripts.empty(); } void addScript(const std::string& fn); + bool hasFileFormats() const { return !m_fileFormats.empty(); } + + std::optional getCustomFormatIdForExtension(const std::string& ext, + FileFormat::Support support) const; + bool loadCustomFormat(const std::string& formatId, const std::string& filename) const; + bool saveCustomFormat(const std::string& formatId, + const std::string& filename, + const doc::Sprite* sprite, + const FileFormatSaveOptions& saveOptions) const; #endif bool isCurrentTheme() const; @@ -174,6 +220,7 @@ private: std::vector scripts; std::vector items; } m_plugin; + std::unordered_map m_fileFormats; #endif std::string m_path; @@ -218,6 +265,12 @@ public: // image file. These pointers cannot be deleted. std::vector ditheringMatrices(); +#ifdef ENABLE_SCRIPTING + // Returns a list of the available custom formats (by extension). + std::vector customFormatList( + Extension::FileFormat::Support support = Extension::FileFormat::Support::Full) const; +#endif + obs::signal NewExtension; obs::signal KeysChange; obs::signal LanguagesChange; diff --git a/src/app/file/file.cpp b/src/app/file/file.cpp index 2a3965526..17e9aaed2 100644 --- a/src/app/file/file.cpp +++ b/src/app/file/file.cpp @@ -17,6 +17,7 @@ #include "app/context.h" #include "app/doc.h" #include "app/drm.h" +#include "app/extensions.h" #include "app/file/file_data.h" #include "app/file/file_format.h" #include "app/file/file_formats_manager.h" @@ -193,6 +194,14 @@ base::paths get_readable_extensions() if (format->support(FILE_SUPPORT_LOAD)) format->getExtensions(paths); } + +#ifdef ENABLE_SCRIPTING + for (const auto& extension : + App::instance()->extensions().customFormatList(Extension::FileFormat::Load)) { + paths.push_back(extension); + } +#endif + return paths; } @@ -204,6 +213,14 @@ base::paths get_writable_extensions(const int requiredFormatFlag) (requiredFormatFlag == 0 || format->support(requiredFormatFlag))) format->getExtensions(paths); } + +#ifdef ENABLE_SCRIPTING + for (const auto& extension : + App::instance()->extensions().customFormatList(Extension::FileFormat::Load)) { + paths.push_back(extension); + } +#endif + return paths; } @@ -366,6 +383,7 @@ FileOp* FileOp::createLoadDocumentOperation(Context* context, // Get the format through the extension of the filename fop->m_format = FileFormatsManager::instance()->getFileFormat(dio::detect_format(filename)); if (!fop->m_format || !fop->m_format->support(FILE_SUPPORT_LOAD)) { + fop->setUnknownFormatError(true); fop->setError("%s can't load \"%s\" file (\"%s\")\n", get_app_name(), filename.c_str(), @@ -535,6 +553,7 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context, fop->m_format = FileFormatsManager::instance()->getFileFormat( dio::detect_format_by_file_extension(filename)); if (!fop->m_format || !fop->m_format->support(FILE_SUPPORT_SAVE)) { + fop->setUnknownFormatError(true); fop->setError("%s can't save \"%s\" file (\"%s\")\n", get_app_name(), filename.c_str(), @@ -1449,6 +1468,7 @@ FileOp::FileOp(FileOpType type, Context* context, const FileOpConfig* config) , m_createPaletteFromRgba(false) , m_ignoreEmpty(false) , m_avoidBackgroundLayer(false) + , m_unknownFormatError(false) , m_embeddedColorProfile(false) , m_embeddedGridBounds(false) { diff --git a/src/app/file/file.h b/src/app/file/file.h index c01c31699..6cf28255f 100644 --- a/src/app/file/file.h +++ b/src/app/file/file.h @@ -81,6 +81,7 @@ public: doc::Tag* tag() const { return m_tag; } doc::frame_t fromFrame() const { return m_framesSeq.firstFrame(); } doc::frame_t toFrame() const { return m_framesSeq.lastFrame(); } + gfx::Rect bounds() const { return m_bounds; } const doc::FramesSequence& framesSequence() const { return m_framesSeq; } doc::frame_t frames() const { return (doc::frame_t)m_framesSeq.size(); } @@ -259,6 +260,8 @@ public: bool hasError() const { return !m_error.empty(); } void setIncompatibilityError(const std::string& msg); bool hasIncompatibilityError() const { return !m_incompatibilityError.empty(); } + void setUnknownFormatError(bool unknownFormatError) { m_unknownFormatError = unknownFormatError; } + bool hasUnknownFormatError() const { return m_unknownFormatError; } double progress() const; void setProgress(double progress); @@ -305,6 +308,7 @@ private: bool m_createPaletteFromRgba; bool m_ignoreEmpty; bool m_avoidBackgroundLayer; + bool m_unknownFormatError; // True if the file contained a color profile when it was loaded. bool m_embeddedColorProfile; diff --git a/src/app/script/plugin_class.cpp b/src/app/script/plugin_class.cpp index 2f62f9d45..391bf15a4 100644 --- a/src/app/script/plugin_class.cpp +++ b/src/app/script/plugin_class.cpp @@ -300,6 +300,63 @@ int Plugin_newMenuSeparator(lua_State* L) return 0; } +int Plugin_newFileFormat(lua_State* L) +{ + auto* plugin = get_obj(L, 1); + if (!lua_istable(L, 2)) + return luaL_error(L, + "plugin:newFileFormat() must be called with a table as its first argument"); + + Extension::FileFormat format; + + int type = lua_getfield(L, 2, "id"); + if (type == LUA_TSTRING) { + format.id = lua_tostring(L, -1); + if (format.id.empty()) + return luaL_error(L, "format id is invalid or empty"); + } + else + return luaL_error(L, "format id must be a string"); + lua_pop(L, 1); + + type = lua_getfield(L, 2, "binary"); + if (type == LUA_TBOOLEAN) { + format.binary = lua_toboolean(L, -1); + lua_pop(L, 1); + } + + type = lua_getfield(L, 2, "extensions"); + if (type == LUA_TTABLE) { + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + format.extensions.emplace(base::string_to_lower(luaL_tolstring(L, -1, nullptr))); + lua_pop(L, 2); + } + if (format.extensions.empty()) + return luaL_error(L, "format extension list is empty"); + } + else + return luaL_error(L, "format extension list must be a table"); + lua_pop(L, 1); + + type = lua_getfield(L, 2, "onload"); + if (type == LUA_TFUNCTION) + format.onloadRef = luaL_ref(L, LUA_REGISTRYINDEX); + else + return luaL_error(L, "format must have an onload function"); // TODO: Make either of these + // optional, so we can only read + // and not write and vice-versa. + + type = lua_getfield(L, 2, "onsave"); + if (type == LUA_TFUNCTION) + format.onsaveRef = luaL_ref(L, LUA_REGISTRYINDEX); + else + return luaL_error(L, "format must have an onsave function"); + + plugin->ext->addFileFormat(format); + return 0; +} + int Plugin_get_name(lua_State* L) { auto* plugin = get_obj(L, 1); @@ -353,6 +410,7 @@ const luaL_Reg Plugin_methods[] = { { "newMenuGroup", Plugin_newMenuGroup }, { "deleteMenuGroup", Plugin_deleteMenuGroup }, { "newMenuSeparator", Plugin_newMenuSeparator }, + { "newFileFormat", Plugin_newFileFormat }, { nullptr, nullptr } }; diff --git a/src/app/script/security.cpp b/src/app/script/security.cpp index c10dd3336..b610c30d4 100644 --- a/src/app/script/security.cpp +++ b/src/app/script/security.cpp @@ -441,4 +441,9 @@ bool ask_access(lua_State* L, return true; } +lua_CFunction get_original_io_open() +{ + return replaced_functions[io_open].origfunc; +} + }} // namespace app::script diff --git a/src/app/script/security.h b/src/app/script/security.h index d75c8cdf0..60814f14a 100644 --- a/src/app/script/security.h +++ b/src/app/script/security.h @@ -14,6 +14,7 @@ #endif #include "app/script/engine.h" +#include "lua.h" namespace app { namespace script { @@ -35,6 +36,8 @@ enum class ResourceType { void overwrite_unsecure_functions(lua_State* L); +lua_CFunction get_original_io_open(); + bool ask_access(lua_State* L, const char* filename, const FileAccessMode mode,