feat: Added support for writing scripts in Python

This commit is contained in:
WerWolv
2024-03-24 11:06:01 +01:00
parent e984fde966
commit 3144d79605
17 changed files with 436 additions and 82 deletions

View File

@@ -0,0 +1,105 @@
#if defined(__declspec)
#undef __declspec
#define __declspec(x)
#endif
#include <Python.h>
#include <loaders/loader.hpp>
#define FUNCTION_DEFINITION(ret, name, args1, args2) \
decltype(name) *name##Func = nullptr; \
extern "C" ret name args1 { \
return name##Func args2; \
}
#define INIT_FUNCTION(name) name##Func = hex::script::loader::getExport<decltype(name##Func)>(pythonLibrary, #name)
FUNCTION_DEFINITION(void, PyPreConfig_InitPythonConfig, (PyPreConfig *config), (config))
FUNCTION_DEFINITION(PyStatus, Py_PreInitialize, (const PyPreConfig *src_config), (src_config))
FUNCTION_DEFINITION(int, PyStatus_Exception, (PyStatus err), (err))
FUNCTION_DEFINITION(void, Py_Initialize, (), ())
FUNCTION_DEFINITION(void, Py_Finalize, (), ())
FUNCTION_DEFINITION(PyInterpreterState *, PyInterpreterState_Get, (), ())
FUNCTION_DEFINITION(PyThreadState *, PyEval_SaveThread, (), ())
FUNCTION_DEFINITION(void, PyEval_RestoreThread, (PyThreadState * state), (state))
FUNCTION_DEFINITION(void, PyErr_Fetch, (PyObject **ptype, PyObject **pvalue, PyObject **ptraceback), (ptype, pvalue, ptraceback))
FUNCTION_DEFINITION(void, PyErr_NormalizeException, (PyObject **ptype, PyObject **pvalue, PyObject **ptraceback), (ptype, pvalue, ptraceback))
FUNCTION_DEFINITION(PyObject *, PyUnicode_FromString, (const char *u), (u))
FUNCTION_DEFINITION(PyObject *, PyImport_Import, (PyObject *name), (name))
FUNCTION_DEFINITION(void, _Py_Dealloc, (PyObject *pobj), (pobj))
FUNCTION_DEFINITION(PyObject *, PyModule_GetDict, (PyObject *pobj), (pobj))
FUNCTION_DEFINITION(PyObject *, PyDict_GetItemString, (PyObject *dp, const char *key), (dp, key))
FUNCTION_DEFINITION(int, PyCallable_Check, (PyObject *dp), (dp))
FUNCTION_DEFINITION(PyObject *, PyTuple_New, (Py_ssize_t len), (len))
FUNCTION_DEFINITION(int, PyTuple_SetItem, (PyObject *p, Py_ssize_t pos, PyObject *o), (p, pos, o))
FUNCTION_DEFINITION(PyObject *, PyObject_CallObject, (PyObject *callable, PyObject *args), (callable, args))
FUNCTION_DEFINITION(PyObject *, PyUnicode_Join, (PyObject *separator, PyObject *iterable), (separator, iterable))
FUNCTION_DEFINITION(const char *, PyUnicode_AsUTF8, (PyObject *unicode), (unicode))
FUNCTION_DEFINITION(void, PyErr_Clear, (), ())
FUNCTION_DEFINITION(int, PyModule_AddStringConstant, (PyObject *module, const char *name, const char *value), (module, name, value))
FUNCTION_DEFINITION(PyObject *, PyEval_GetBuiltins, (), ())
FUNCTION_DEFINITION(int, PyDict_SetItemString, (PyObject *dp, const char *key, PyObject *item), (dp, key, item))
FUNCTION_DEFINITION(PyObject *, PyRun_StringFlags, (const char *str, int start, PyObject *globals, PyObject *locals, PyCompilerFlags *flags), (str, start, globals, locals, flags))
FUNCTION_DEFINITION(void, PyThreadState_Clear, (PyThreadState *tstate), (tstate))
FUNCTION_DEFINITION(void, PyThreadState_DeleteCurrent, (), ())
FUNCTION_DEFINITION(PyThreadState *, PyThreadState_New, (PyInterpreterState *interp), (interp))
FUNCTION_DEFINITION(PyObject *, PyImport_AddModule, (const char *name), (name))
FUNCTION_DEFINITION(PyObject *, PyModule_New, (const char *name), (name))
bool initPythonLoader() {
void *pythonLibrary = nullptr;
for (const std::fs::path &path : { std::fs::path(PYTHON_LIBRARY_PATH), std::fs::path(PYTHON_LIBRARY_PATH).filename() }) {
pythonLibrary = hex::script::loader::loadLibrary(path.c_str());
if (pythonLibrary != nullptr) {
break;
}
}
if (pythonLibrary == nullptr)
return false;
INIT_FUNCTION(PyPreConfig_InitPythonConfig);
INIT_FUNCTION(Py_PreInitialize);
INIT_FUNCTION(Py_Initialize);
INIT_FUNCTION(Py_Finalize);
INIT_FUNCTION(PyEval_SaveThread);
INIT_FUNCTION(PyEval_RestoreThread);
INIT_FUNCTION(PyEval_GetBuiltins);
INIT_FUNCTION(PyRun_StringFlags);
INIT_FUNCTION(PyErr_Fetch);
INIT_FUNCTION(PyErr_NormalizeException);
INIT_FUNCTION(PyErr_Clear);
INIT_FUNCTION(PyStatus_Exception);
INIT_FUNCTION(PyThreadState_Clear);
INIT_FUNCTION(PyThreadState_DeleteCurrent);
INIT_FUNCTION(PyThreadState_New);
INIT_FUNCTION(PyInterpreterState_Get);
INIT_FUNCTION(PyUnicode_FromString);
INIT_FUNCTION(PyUnicode_Join);
INIT_FUNCTION(PyUnicode_AsUTF8);
INIT_FUNCTION(PyTuple_New);
INIT_FUNCTION(PyTuple_SetItem);
INIT_FUNCTION(PyImport_Import);
INIT_FUNCTION(PyImport_AddModule);
INIT_FUNCTION(PyModule_New);
INIT_FUNCTION(PyModule_AddStringConstant);
INIT_FUNCTION(PyModule_GetDict);
INIT_FUNCTION(PyDict_GetItemString);
INIT_FUNCTION(PyDict_SetItemString);
INIT_FUNCTION(PyCallable_Check);
INIT_FUNCTION(PyObject_CallObject);
INIT_FUNCTION(_Py_Dealloc);
return true;
}

View File

@@ -0,0 +1,147 @@
#include <loaders/python/python_loader.hpp>
#include <hex/helpers/fs.hpp>
#include <hex/helpers/logger.hpp>
#include <wolv/io/file.hpp>
#include <wolv/utils/guards.hpp>
#include <wolv/utils/string.hpp>
#include <hex/helpers/utils.hpp>
#include <romfs/romfs.hpp>
#if defined(__declspec)
#undef __declspec
#define __declspec(x)
#endif
#include <Python.h>
bool initPythonLoader();
namespace hex::script::loader {
static PyInterpreterState *mainThreadState;
bool PythonLoader::initialize() {
if (!initPythonLoader())
return false;
PyPreConfig preconfig;
PyPreConfig_InitPythonConfig(&preconfig);
preconfig.utf8_mode = 1;
auto status = Py_PreInitialize(&preconfig);
if (PyStatus_Exception(status)) {
return false;
}
Py_Initialize();
mainThreadState = PyInterpreterState_Get();
PyEval_SaveThread();
return true;
}
std::string getCurrentTraceback() {
PyObject *ptype, *pvalue, *ptraceback;
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
PyErr_NormalizeException(&ptype, &pvalue, &ptraceback);
PyObject *pModuleName = PyUnicode_FromString("traceback");
PyObject *pModule = PyImport_Import(pModuleName);
Py_DECREF(pModuleName);
if (pModule != nullptr) {
PyObject *pDict = PyModule_GetDict(pModule);
PyObject *pFunc = PyDict_GetItemString(pDict, "format_exception");
if (pFunc && PyCallable_Check(pFunc)) {
PyObject *pArgs = PyTuple_New(3);
PyTuple_SetItem(pArgs, 0, ptype);
PyTuple_SetItem(pArgs, 1, pvalue);
PyTuple_SetItem(pArgs, 2, ptraceback);
PyObject *pResult = PyObject_CallObject(pFunc, pArgs);
Py_DECREF(pArgs);
if (pResult != NULL) {
const char *errorMessage = PyUnicode_AsUTF8(PyUnicode_Join(PyUnicode_FromString(""), pResult));
Py_DECREF(pResult);
Py_DECREF(pModule);
return errorMessage;
}
}
Py_DECREF(pModule);
}
PyErr_Clear();
return "";
}
void populateModule(PyObject *pyModule, const std::string &sourceCode) {
PyModule_AddStringConstant(pyModule, "__file__", "");
PyObject *localDict = PyModule_GetDict(pyModule);
PyObject *builtins = PyEval_GetBuiltins();
PyDict_SetItemString(localDict, "__builtins__", builtins);
PyErr_Clear();
PyObject *pyValue = PyRun_String(sourceCode.c_str(), Py_file_input, localDict, localDict);
if (pyValue != nullptr) {
Py_DECREF(pyValue);
} else {
log::error("{}", getCurrentTraceback());
}
}
bool PythonLoader::loadAll() {
this->clearScripts();
for (const auto &imhexPath : hex::fs::getDefaultPaths(hex::fs::ImHexPath::Scripts)) {
auto directoryPath = imhexPath / "custom" / "python";
if (!wolv::io::fs::exists(directoryPath))
wolv::io::fs::createDirectories(directoryPath);
if (!wolv::io::fs::exists(directoryPath))
continue;
for (const auto &entry : std::fs::directory_iterator(directoryPath)) {
if (!entry.is_directory())
continue;
const auto scriptPath = entry.path() / "main.py";
if (!std::fs::exists(scriptPath))
continue;
auto pathString = wolv::util::toUTF8String(scriptPath);
wolv::io::File scriptFile(pathString, wolv::io::File::Mode::Read);
if (!scriptFile.isValid())
continue;
this->addScript(entry.path().stem().string(), [pathString, scriptContent = scriptFile.readString()] {
PyThreadState* ts = PyThreadState_New(mainThreadState);
PyEval_RestoreThread(ts);
ON_SCOPE_EXIT {
PyThreadState_Clear(ts);
PyThreadState_DeleteCurrent();
};
PyObject *libraryModule = PyImport_AddModule("imhex");
PyModule_AddStringConstant(libraryModule, "__script_loader__", hex::format("{}", (u64)hex::getContainingModule((void*)&getCurrentTraceback)).c_str());
populateModule(libraryModule, romfs::get("python/imhex.py").data<const char>());
PyObject *mainModule = PyModule_New(pathString.c_str());
populateModule(mainModule, scriptContent);
Py_DECREF(mainModule);
});
}
}
return true;
}
}