diff --git a/Source/Core/Common/Common.vcxproj b/Source/Core/Common/Common.vcxproj
index 2a38dd721c57..0a5da07c6430 100644
--- a/Source/Core/Common/Common.vcxproj
+++ b/Source/Core/Common/Common.vcxproj
@@ -157,6 +157,7 @@
+
diff --git a/Source/Core/Common/Common.vcxproj.filters b/Source/Core/Common/Common.vcxproj.filters
index 76793bd9ae4d..c8c43777ac57 100644
--- a/Source/Core/Common/Common.vcxproj.filters
+++ b/Source/Core/Common/Common.vcxproj.filters
@@ -1,4 +1,4 @@
-
+
@@ -274,6 +274,7 @@
Debug
+
diff --git a/Source/Core/Common/TupleUtil.h b/Source/Core/Common/TupleUtil.h
new file mode 100644
index 000000000000..9a7139813a4f
--- /dev/null
+++ b/Source/Core/Common/TupleUtil.h
@@ -0,0 +1,36 @@
+// Copyright 2018 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include
+
+template
+auto _TupleMapImpl(Tuple& t, F f, std::index_sequence)
+{
+ return std::make_tuple(f(std::get(t))...);
+}
+
+// returns a new tuple with each tuple item mapped by the given function.
+// The tuple is passed by reference, so the mapping function may take references to the values.
+template
+auto TupleMap(std::tuple& t, F f)
+{
+ return _TupleMapImpl(t, f, std::make_index_sequence{});
+}
+
+
+template
+static auto _TupleToArrayImpl(Tuple& t, std::index_sequence)
+{
+ return std::array, sizeof...(Is)>{std::get(t)...};
+}
+
+// turns a tuple consisting of 1 or more elements of the same type into a std::array
+template
+static auto TupleToArray(std::tuple& t)
+{
+ return _TupleToArrayImpl(t, std::make_index_sequence{});
+}
+
diff --git a/Source/Core/DolphinQt2/Main.cpp b/Source/Core/DolphinQt2/Main.cpp
index c98b5c99c936..3fb684bfc177 100644
--- a/Source/Core/DolphinQt2/Main.cpp
+++ b/Source/Core/DolphinQt2/Main.cpp
@@ -31,6 +31,7 @@
#include "DolphinQt2/Updater.h"
#include "UICommon/CommandLineParse.h"
#include "UICommon/UICommon.h"
+#include "Scripting/ScriptingEngine.h"
static bool QtMsgAlertHandler(const char* caption, const char* text, bool yes_no, MsgType style)
{
@@ -135,6 +136,7 @@ int main(int argc, char* argv[])
UICommon::Init();
Resources::Init();
Settings::Instance().SetBatchModeEnabled(options.is_set("batch"));
+ Scripting::Init();
// Hook up alerts from core
RegisterMsgAlertHandler(QtMsgAlertHandler);
@@ -216,6 +218,7 @@ int main(int argc, char* argv[])
retval = app.exec();
}
+ Scripting::Shutdown();
Core::Shutdown();
UICommon::Shutdown();
Host::GetInstance()->deleteLater();
diff --git a/Source/Core/Scripting/Python/PyFramework/as_py_function.h b/Source/Core/Scripting/Python/PyFramework/as_py_function.h
new file mode 100644
index 000000000000..c327c60b1396
--- /dev/null
+++ b/Source/Core/Scripting/Python/PyFramework/as_py_function.h
@@ -0,0 +1,64 @@
+// Copyright 2018 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include
+#include
+#include
+
+#include "Common/TupleUtil.h"
+
+#include "Scripting/Python/PyFramework/fmt.h"
+
+
+namespace Py
+{
+
+
+template
+using Fun = TRet (*)(TsArgs...);
+
+template
+struct WrapHelper;
+
+template TFun>
+struct WrapHelper, TFun>
+{
+ static PyObject* func(PyObject* self, PyObject* args)
+ {
+ std::tuple args_tpl;
+
+ std::tuple py_args_and_fmt =
+ std::make_tuple(args, Py::fmts.c_str());
+ std::tuple args_pointers =
+ TupleMap(args_tpl, [](const auto& obj) { return &obj; });
+ auto invoke_args = std::tuple_cat(py_args_and_fmt, args_pointers);
+ if (!std::apply(PyArg_ParseTuple, invoke_args))
+ return nullptr;
+
+ if constexpr (std::is_same_v)
+ {
+ std::apply(TFun, args_tpl);
+ Py_RETURN_NONE;
+ }
+ else
+ {
+ TRet result = std::apply(TFun, args_tpl);
+ return Py_BuildValue(Py::fmt, result);
+ }
+
+ }
+};
+
+template
+struct Wrap : WrapHelper
+{
+};
+
+template
+static PyCFunction as_py_function = Wrap::func;
+
+
+} // namespace Py
diff --git a/Source/Core/Scripting/Python/PyFramework/fmt.h b/Source/Core/Scripting/Python/PyFramework/fmt.h
new file mode 100644
index 000000000000..386ffe03f649
--- /dev/null
+++ b/Source/Core/Scripting/Python/PyFramework/fmt.h
@@ -0,0 +1,115 @@
+// Copyright 2018 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include
+#include
+
+
+namespace Py
+{
+
+
+// translates types to format strings according to the documentation:
+// https://docs.python.org/3/c-api/arg.html
+template
+constexpr const char* GetPyFmt()
+{
+ static_assert(sizeof(T) != sizeof(T), "no python format string known for given type");
+ return nullptr;
+}
+#pragma region template specializations for each type
+template <>
+constexpr const char* GetPyFmt()
+{
+ return "b";
+}
+template <>
+constexpr const char* GetPyFmt()
+{
+ return "h";
+}
+template <>
+constexpr const char* GetPyFmt()
+{
+ return "i";
+}
+template <>
+constexpr const char* GetPyFmt()
+{
+ return "l";
+}
+template <>
+constexpr const char* GetPyFmt()
+{
+ return "L";
+}
+template <>
+constexpr const char* GetPyFmt()
+{
+ return "B";
+}
+template <>
+constexpr const char* GetPyFmt()
+{
+ return "H";
+}
+template <>
+constexpr const char* GetPyFmt()
+{
+ return "I";
+}
+template <>
+constexpr const char* GetPyFmt()
+{
+ return "k";
+}
+template <>
+constexpr const char* GetPyFmt()
+{
+ return "K";
+}
+template <>
+constexpr const char* GetPyFmt()
+{
+ return "f";
+}
+template <>
+constexpr const char* GetPyFmt()
+{
+ return "d";
+}
+template <>
+constexpr const char* GetPyFmt()
+{
+ return "O";
+}
+template <>
+constexpr const char* GetPyFmt()
+{
+ return "s";
+}
+#pragma endregion
+
+template
+constexpr const char* fmt = GetPyFmt();
+
+template
+std::string GetPyFmts()
+{
+ return std::string{fmt};
+}
+
+template
+std::string GetPyFmts()
+{
+ return fmt + GetPyFmts();
+}
+
+template
+const std::string fmts = GetPyFmts();
+
+
+} // namespace Py
diff --git a/Source/Core/Scripting/Python/PyFramework/module.h b/Source/Core/Scripting/Python/PyFramework/module.h
new file mode 100644
index 000000000000..1af137f83092
--- /dev/null
+++ b/Source/Core/Scripting/Python/PyFramework/module.h
@@ -0,0 +1,48 @@
+// Copyright 2018 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include
+#include "Scripting/Python/PyFramework/as_py_function.h"
+
+namespace Py
+{
+
+
+/*template
+struct FuncDef
+{
+ char* func_name;
+ T func;
+};*/
+
+template
+constexpr PyMethodDef MakeMethodDef(const char* name)
+{
+ const auto x = as_py_function;
+ return {name, x, METH_VARARGS, ""};
+}
+
+/*constexpr PyModuleDef MakeModule(const char* module_name, PyMethodDef func_defs[])
+{
+ return {PyModuleDef_HEAD_INIT, module_name, nullptr, -1, func_defs};
+}*/
+
+/*template
+PyModuleDef MakeModule(const char* module_name, FuncDef... func_defs)
+{
+ std::tuple func_tpl{func_defs...};
+
+ auto py_funcs_tpl = TupleMap(func_tpl, [](auto& x) {
+ return {x.func_name, as_py_function, METH_VARARGS, ""};
+ });
+ std::array py_funcs = TupleToArray(py_funcs_tpl);
+
+ PyMethodDef Methods[] = {py_funcs};
+ return {PyModuleDef_HEAD_INIT, module_name, nullptr, -1, Methods};
+}*/
+
+
+} // namespace Py
diff --git a/Source/Core/Scripting/Python/PyScriptingBackend.cpp b/Source/Core/Scripting/Python/PyScriptingBackend.cpp
new file mode 100644
index 000000000000..47a6e0c62b9d
--- /dev/null
+++ b/Source/Core/Scripting/Python/PyScriptingBackend.cpp
@@ -0,0 +1,94 @@
+
+#include
+#include
+#include "Common/FileUtil.h"
+#include "Common/Logging/Log.h"
+#include "Common/StringUtil.h"
+#include "Core/API/Events.h"
+#include "Scripting/Python/PyScriptingBackend.h"
+#include "Scripting/Python/Pyr.h"
+#include "Scripting/Python/memorymodule.h"
+#include "DiscIO/Filesystem.h"
+#include "aeventmodule.h"
+#include "doliomodule.h"
+#include "eventmodule.h"
+
+namespace PyScripting
+{
+
+static const std::wstring python_home = UTF8ToUTF16(File::GetExeDirectory()) + L"/python36-embed";
+static const std::wstring python_path =
+ UTF8ToUTF16(File::GetExeDirectory()) + L"/python-embed/python36.zip;" + // TODO make this version-robust
+ UTF8ToUTF16(File::GetExeDirectory()) + L"/python-embed;" +
+ UTF8ToUTF16(File::GetExeDirectory()); // TODO can this be done more elegantly?
+
+static std::deque s_tasks;
+static CoreTiming::EventType* s_scripting_event = nullptr;
+
+API::ListenerID listener_frameadvance;
+API::ListenerID listener_memory;
+API::ListenerID listener_interrupt;
+
+static void ScriptingEvent(u64, s64)
+{
+ while (!s_tasks.empty())
+ {
+ const Task task = s_tasks.front();
+ s_tasks.pop_front();
+ task();
+ }
+}
+
+void Init()
+{
+ listener_frameadvance = API::GetEventHub().ListenEvent(OnFrameAdvance);
+ listener_memory = API::GetEventHub().ListenEvent(OnMemory);
+ listener_interrupt = API::GetEventHub().ListenEvent(OnInterrupt);
+
+ s_scripting_event = CoreTiming::RegisterEvent("", ScriptingEvent);
+
+ PyImport_AppendInittab("dolio_stdout", PyInit_dolio_stdout);
+ PyImport_AppendInittab("dolio_stderr", PyInit_dolio_stderr);
+ PyImport_AppendInittab("dolphin_memory", PyInit_memory);
+ PyImport_AppendInittab("dolphin_event", PyInit_event);
+ PyImport_AppendInittab("dolphin_aevent", PyInit_aevent);
+
+ Py_SetPythonHome(const_cast(python_home.c_str()));
+ Py_SetPath(python_path.c_str());
+ Py_InitializeEx(0);
+ auto result_stdout = PyrImport::ImportModule("dolio_stdout");
+ if (result_stdout.IsNull())
+ ERROR_LOG(SCRIPTING, "Error auto-importing dolphin io module for stdout");
+ auto result_stderr = PyrImport::ImportModule("dolio_stderr");
+ if (result_stderr.IsNull())
+ ERROR_LOG(SCRIPTING, "Error auto-importing dolphin io module for stderr");
+
+ // TODO check file exists
+ std::string filepath = File::GetExeDirectory() + "/testscript.py";
+ const int result =
+ PyRun_SimpleFileExFlags(fopen(filepath.c_str(), "r"), filepath.c_str(), true, nullptr);
+ if (result != 0)
+ ERROR_LOG(SCRIPTING, "Error during initialization of script.");
+}
+
+void Shutdown()
+{
+ API::GetEventHub().UnlistenEvent(listener_frameadvance);
+ API::GetEventHub().UnlistenEvent(listener_memory);
+ API::GetEventHub().UnlistenEvent(listener_interrupt);
+
+ CoreTiming::RemoveEvent(s_scripting_event);
+ s_scripting_event = nullptr;
+
+ PyGILState_Ensure();
+ if (Py_FinalizeEx() != 0)
+ ERROR_LOG(SCRIPTING, "Error during python finalization: %s", PyrErr::GetException());
+}
+
+void RunFromCPUThreadAsync(Task task)
+{
+ s_tasks.push_back(task);
+ CoreTiming::ScheduleEvent(0, s_scripting_event, 0, CoreTiming::FromThread::NON_CPU);
+}
+
+} // namespace PyScripting
diff --git a/Source/Core/Scripting/Python/PyScriptingBackend.h b/Source/Core/Scripting/Python/PyScriptingBackend.h
new file mode 100644
index 000000000000..243fc5a16faa
--- /dev/null
+++ b/Source/Core/Scripting/Python/PyScriptingBackend.h
@@ -0,0 +1,13 @@
+#pragma once
+#include
+
+namespace PyScripting
+{
+
+void Init();
+void Shutdown();
+
+using Task = std::function;
+void RunFromCPUThreadAsync(Task task);
+
+} // namespace PyScripting
diff --git a/Source/Core/Scripting/Python/Pyr.h b/Source/Core/Scripting/Python/Pyr.h
new file mode 100644
index 000000000000..7a1028e61dfe
--- /dev/null
+++ b/Source/Core/Scripting/Python/Pyr.h
@@ -0,0 +1,142 @@
+#pragma once
+
+#include
+#include "PyFramework/fmt.h"
+
+namespace Pyr
+{
+class Object
+{
+ PyObject* m_py_object_;
+ Object(PyObject* py_object) : m_py_object_(py_object) {}
+
+public:
+ Object& operator=(Object&&) = default;
+ Object& operator=(const Object& rhs)
+ {
+ Py_XINCREF(rhs.Unwrap());
+ Py_XDECREF(m_py_object_);
+ m_py_object_ = rhs.Unwrap();
+ return *this;
+ }
+ Object(Object&&) = default;
+ Object(Object& rhs)
+ {
+ m_py_object_ = rhs.Unwrap();
+ Py_XINCREF(rhs.Unwrap());
+ }
+
+ ~Object() { Py_XDECREF(m_py_object_); }
+ PyObject* Unwrap() const { return m_py_object_; }
+ bool IsNull() const { return m_py_object_ == NULL; }
+ static Object WrapExisting(PyObject* py_object) { return Object(py_object); }
+};
+
+inline Object Wrap(PyObject* py_object)
+{
+ return Object::WrapExisting(py_object);
+}
+inline Object Take(PyObject* py_object)
+{
+ Py_XINCREF(py_object);
+ return Object::WrapExisting(py_object);
+}
+
+inline Object& Null()
+{
+ static Object null_obj = Wrap(nullptr);
+ return null_obj;
+}
+
+
+}
+
+namespace PyrArg
+{
+template
+bool ParseTupleSimple(PyObject* args, T1* arg1)
+{
+ return PyArg_ParseTuple(args, Py::fmt, arg1);
+}
+
+template
+bool ParseTupleSimple(PyObject* args, T1* arg1, T2* arg2)
+{
+ return PyArg_ParseTuple(args, Py::fmts.c_str(), arg1, arg2);
+}
+}
+
+namespace PyrImport
+{
+inline Pyr::Object ImportModule(const char* module)
+{
+ return Pyr::Wrap(PyImport_ImportModule(module));
+}
+}
+
+namespace PyrObject
+{
+inline Pyr::Object GetAttrString(const Pyr::Object& py_object, const char* attr)
+{
+ return Pyr::Wrap(PyObject_GetAttrString(py_object.Unwrap(), attr));
+}
+inline int SetAttrString(const Pyr::Object& py_object, const char* attr, const Pyr::Object& value)
+{
+ return PyObject_SetAttrString(py_object.Unwrap(), attr, value.Unwrap());
+}
+inline Pyr::Object CallFunction(const Pyr::Object& callable_object, const char* format)
+{
+ return Pyr::Wrap(PyObject_CallFunction(callable_object.Unwrap(), format));
+}
+inline Pyr::Object GetIter(const Pyr::Object& iterable_object)
+{
+ return Pyr::Wrap(PyObject_GetIter(iterable_object.Unwrap()));
+}
+}
+
+namespace PyrIter
+{
+inline Pyr::Object Next(const Pyr::Object& iterable_object)
+{
+ return Pyr::Wrap(PyIter_Next(iterable_object.Unwrap()));
+}
+}
+
+namespace PyrErr
+{
+inline const char* GetException()
+{
+ PyObject *ptype, *pvalue, *ptraceback;
+ PyErr_Fetch(&ptype, &pvalue, &ptraceback);
+ PyObject* repr = PyObject_Repr(pvalue);
+ PyObject* py_str = PyUnicode_AsEncodedString(repr, "utf-8", "Error ~");
+ return PyBytes_AS_STRING(py_str);
+}
+}
+
+namespace PyrRun
+{
+inline Pyr::Object File(std::string file, PyObject* globals, PyObject* locals)
+{
+ return Pyr::Wrap(PyRun_FileExFlags(fopen(file.c_str(), "r"), file.c_str(), Py_file_input, globals,
+ locals, true, nullptr));
+}
+}
+
+namespace PyrGILState
+{
+class GIL
+{
+ PyGILState_STATE gstate;
+public:
+ GIL()
+ {
+ gstate = PyGILState_Ensure();
+ }
+ ~GIL()
+ {
+ PyGILState_Release(gstate);
+ }
+};
+}
+
diff --git a/Source/Core/Scripting/Python/aeventmodule.h b/Source/Core/Scripting/Python/aeventmodule.h
new file mode 100644
index 000000000000..416010f29926
--- /dev/null
+++ b/Source/Core/Scripting/Python/aeventmodule.h
@@ -0,0 +1,57 @@
+#pragma once
+
+#include "Pyr.h"
+#include "Python.h"
+
+namespace PyScripting
+{
+inline const char* pycode = R"(
+
+@asyncio.coroutine
+def aevent(event_name, *args):
+ return (yield (get_event_id(event_name), args))
+
+@asyncio.coroutine
+def frameadvance():
+ return aevent("frameadvance")
+
+)";
+
+inline PyObject* get_event_id(PyObject* self, PyObject* args)
+{
+ char* x;
+ if (!PyrArg::ParseTupleSimple(args, &x))
+ return nullptr;
+ if (std::string(x) == "frameadvance")
+ return Py_BuildValue("l", 1);
+ return Py_BuildValue("l", 0);
+}
+
+static PyMethodDef AEventMethods[] = {
+ {"get_event_id", get_event_id, METH_VARARGS, "gets the event id by event name"},
+ {nullptr, nullptr, 0, nullptr} // Sentinel
+};
+
+static struct PyModuleDef aeventmodule = {PyModuleDef_HEAD_INIT, "aevent", nullptr, -1,
+ AEventMethods};
+
+PyMODINIT_FUNC PyInit_aevent()
+{
+ PyObject* m = PyModule_Create(&aeventmodule);
+ if (m == nullptr)
+ return nullptr;
+
+ PyObject* globals = PyModule_GetDict(m);
+ if (globals == nullptr)
+ return nullptr;
+ PyObject* asyncio_module = PyImport_ImportModuleEx("asyncio", globals, globals, nullptr);
+ if (asyncio_module == nullptr)
+ return nullptr;
+ if (PyDict_SetItemString(globals, "asyncio", asyncio_module) != 0)
+ return nullptr;
+ if (PyRun_String(pycode, Py_file_input, globals, globals) == nullptr)
+ return nullptr;
+ return m;
+}
+
+} // namespace PyScripting
diff --git a/Source/Core/Scripting/Python/doliomodule.h b/Source/Core/Scripting/Python/doliomodule.h
new file mode 100644
index 000000000000..274002ebb09f
--- /dev/null
+++ b/Source/Core/Scripting/Python/doliomodule.h
@@ -0,0 +1,107 @@
+#pragma once
+
+#include
+
+#include "Common/Logging/Log.h"
+#include "Python.h"
+
+namespace PyScripting
+{
+
+static std::stringstream buffer_stdout;
+static std::stringstream buffer_stderr;
+
+inline void flush_stdout()
+{
+ auto content = buffer_stdout.str();
+ if (content.empty())
+ return;
+ NOTICE_LOG(SCRIPTING, "Script stdout: %s", content.c_str());
+ buffer_stdout.str("");
+}
+
+inline void flush_stderr()
+{
+ auto content = buffer_stderr.str();
+ if (content.empty())
+ return;
+ ERROR_LOG(SCRIPTING, "Script stderr: %s", content.c_str());
+ buffer_stderr.str("");
+}
+
+inline PyObject* dol_write_stdout(PyObject* self, PyObject* args)
+{
+ const char* what;
+ if (!PyArg_ParseTuple(args, "s", &what))
+ return nullptr;
+ for (auto i = 0; what[i] != '\0'; ++i)
+ {
+ if (what[i] == '\n')
+ flush_stdout();
+ else
+ buffer_stdout << what[i];
+ }
+ Py_RETURN_NONE;
+}
+
+inline PyObject* dol_write_stderr(PyObject* self, PyObject* args)
+{
+ const char* what;
+ if (!PyArg_ParseTuple(args, "s", &what))
+ return nullptr;
+ for (auto i = 0; what[i] != '\0'; ++i)
+ {
+ if (what[i] == '\n')
+ flush_stderr();
+ else
+ buffer_stderr << what[i];
+ }
+ Py_RETURN_NONE;
+}
+
+inline PyObject* dol_flush_stdout(PyObject* self, PyObject* args)
+{
+ flush_stdout();
+ Py_RETURN_NONE;
+}
+
+inline PyObject* dol_flush_stderr(PyObject* self, PyObject* args)
+{
+ flush_stderr();
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef IOMethodsStdout[] = {
+ {"write", dol_write_stdout, METH_VARARGS, "Writes to stdout"},
+ {"flush", dol_flush_stdout, METH_VARARGS, "Flushes stdout"},
+ {nullptr, nullptr, 0, nullptr} // Sentinel
+};
+
+static PyMethodDef IOMethodsStderr[] = {
+ {"write", dol_write_stderr, METH_VARARGS, "Writes to stderr"},
+ {"flush", dol_flush_stderr, METH_VARARGS, "Flushes stderr"},
+ {nullptr, nullptr, 0, nullptr} // Sentinel
+};
+
+static struct PyModuleDef iomodule_stdout = {PyModuleDef_HEAD_INIT, "dolio_stdout", nullptr, -1, IOMethodsStdout};
+static struct PyModuleDef iomodule_stderr = {PyModuleDef_HEAD_INIT, "dolio_stderr", nullptr, -1, IOMethodsStderr};
+
+PyMODINIT_FUNC PyInit_dolio_stdout()
+{
+ PyObject* m = PyModule_Create(&iomodule_stdout);
+ if (m == nullptr)
+ return nullptr;
+ PySys_SetObject("stdout", m);
+ return m;
+}
+
+PyMODINIT_FUNC PyInit_dolio_stderr()
+{
+ PyObject* m = PyModule_Create(&iomodule_stderr);
+ if (m == nullptr)
+ return nullptr;
+ PySys_SetObject("stderr", m);
+ return m;
+}
+
+} // namespace PyScripting
diff --git a/Source/Core/Scripting/Python/eventmodule.h b/Source/Core/Scripting/Python/eventmodule.h
new file mode 100644
index 000000000000..a56531d82370
--- /dev/null
+++ b/Source/Core/Scripting/Python/eventmodule.h
@@ -0,0 +1,102 @@
+#pragma once
+
+#include "Core/API/Events.h"
+#include "Pyr.h"
+
+namespace PyScripting
+{
+
+static Pyr::Object callback_onframeadvance = Pyr::Null();
+
+static PyObject* event_onframeadvance(PyObject* self, PyObject* args)
+{
+ PyObject* temp;
+ if (!PyArg_ParseTuple(args, "O", &temp))
+ return nullptr;
+ if (temp == Py_None)
+ {
+ callback_onframeadvance = Pyr::Null();
+ Py_RETURN_NONE;
+ }
+ if (!PyCallable_Check(temp))
+ return nullptr;
+ callback_onframeadvance = Pyr::Take(temp);
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef EventMethods[] = {
+ {"onframeadvance", event_onframeadvance, METH_VARARGS, "TODO"},
+ {nullptr, nullptr, 0, nullptr} /* Sentinel */
+};
+
+static struct PyModuleDef eventmodule = {PyModuleDef_HEAD_INIT, "event", nullptr, -1, EventMethods};
+
+PyMODINIT_FUNC PyInit_event()
+{
+ return PyModule_Create(&eventmodule);
+}
+
+static void OnFrameAdvance(const API::Events::FrameAdvance&)
+{
+ if (callback_onframeadvance.IsNull())
+ return;
+ RunFromCPUThreadAsync([] {
+ PyrGILState::GIL lock;
+ const auto result = PyrObject::CallFunction(callback_onframeadvance, nullptr);
+ if (result.IsNull())
+ PyErr_Print();
+ });
+}
+
+static void OnMemory(const API::Events::MemoryBreakpoint& evt)
+{
+ //INFO_LOG(SCRIPTING, "on memory; write %d, address %u, value %u", evt.write, evt.addr, evt.value);
+}
+
+/*static const char* GetInterruptName(u32 cause_mask)
+{
+ switch (cause_mask)
+ {
+ case ProcessorInterface::INT_CAUSE_PI:
+ return "INT_CAUSE_PI";
+ case ProcessorInterface::INT_CAUSE_DI:
+ return "INT_CAUSE_DI";
+ case ProcessorInterface::INT_CAUSE_RSW:
+ return "INT_CAUSE_RSW";
+ case ProcessorInterface::INT_CAUSE_SI:
+ return "INT_CAUSE_SI";
+ case ProcessorInterface::INT_CAUSE_EXI:
+ return "INT_CAUSE_EXI";
+ case ProcessorInterface::INT_CAUSE_AI:
+ return "INT_CAUSE_AI";
+ case ProcessorInterface::INT_CAUSE_DSP:
+ return "INT_CAUSE_DSP";
+ case ProcessorInterface::INT_CAUSE_MEMORY:
+ return "INT_CAUSE_MEMORY";
+ case ProcessorInterface::INT_CAUSE_VI:
+ return "INT_CAUSE_VI";
+ case ProcessorInterface::INT_CAUSE_PE_TOKEN:
+ return "INT_CAUSE_PE_TOKEN";
+ case ProcessorInterface::INT_CAUSE_PE_FINISH:
+ return "INT_CAUSE_PE_FINISH";
+ case ProcessorInterface::INT_CAUSE_CP:
+ return "INT_CAUSE_CP";
+ case ProcessorInterface::INT_CAUSE_DEBUG:
+ return "INT_CAUSE_DEBUG";
+ case ProcessorInterface::INT_CAUSE_WII_IPC:
+ return "INT_CAUSE_WII_IPC";
+ case ProcessorInterface::INT_CAUSE_HSP:
+ return "INT_CAUSE_HSP";
+ case ProcessorInterface::INT_CAUSE_RST_BUTTON:
+ return "INT_CAUSE_RST_BUTTON";
+ default:
+ return "!!! ERROR-unknown Interrupt !!!";
+ }
+}*/
+
+static void OnInterrupt(const API::Events::SetInterrupt& evt)
+{
+ //INFO_LOG(SCRIPTING, "interrupt %s at frame %d", GetInterruptName(evt.cause_mask), Movie::GetCurrentFrame());
+}
+
+} // namespace PyScripting
diff --git a/Source/Core/Scripting/Python/memorymodule.h b/Source/Core/Scripting/Python/memorymodule.h
new file mode 100644
index 000000000000..a256ec9afe3f
--- /dev/null
+++ b/Source/Core/Scripting/Python/memorymodule.h
@@ -0,0 +1,63 @@
+#pragma once
+
+#include "Core/HLE/HLE_VarArgs.h"
+#include "Core/HW/Memmap.h"
+#include "Core/PowerPC/BreakPoints.h"
+#include "PyFramework/as_py_function.h"
+#include "Python.h"
+
+namespace PyScripting
+{
+
+void add_memcheck(u32 addr, PyObject* callback)
+{
+ INFO_LOG(SCRIPTING, "adding memcheck at address %u", addr);
+ TMemCheck memcheck;
+ memcheck.start_address = addr;
+ memcheck.end_address = addr;
+ PowerPC::memchecks.Add(memcheck);
+}
+
+static PyMethodDef MemoryMethods[] = {
+ {"read_u8", Py::as_py_function, METH_VARARGS, ""},
+ {"read_u16", Py::as_py_function, METH_VARARGS, ""},
+ {"read_u32", Py::as_py_function, METH_VARARGS, ""},
+ {"read_u64", Py::as_py_function, METH_VARARGS, ""},
+
+ {"read_s8", Py::as_py_function, METH_VARARGS, ""},
+ {"read_s16", Py::as_py_function, METH_VARARGS, ""},
+ {"read_s32", Py::as_py_function, METH_VARARGS, ""},
+ {"read_s64", Py::as_py_function, METH_VARARGS, ""},
+
+ {"read_f32", Py::as_py_function, METH_VARARGS, ""},
+ {"read_f64", Py::as_py_function, METH_VARARGS, ""},
+
+ {"write_u8", Py::as_py_function, METH_VARARGS, ""},
+ {"write_u16", Py::as_py_function, METH_VARARGS, ""},
+ {"write_u32", Py::as_py_function, METH_VARARGS, ""},
+ {"write_u64", Py::as_py_function, METH_VARARGS, ""},
+
+ {"write_s8", Py::as_py_function, METH_VARARGS, ""},
+ {"write_s16", Py::as_py_function, METH_VARARGS, ""},
+ {"write_s32", Py::as_py_function, METH_VARARGS, ""},
+ {"write_s64", Py::as_py_function, METH_VARARGS, ""},
+
+ {"write_f32", Py::as_py_function, METH_VARARGS, ""},
+ {"write_f64", Py::as_py_function, METH_VARARGS, ""},
+
+ {"add_memcheck", Py::as_py_function, METH_VARARGS, ""},
+
+ PyMethodDef{nullptr, nullptr, 0, nullptr} // Sentinel
+};
+
+static struct PyModuleDef memorymodule = {PyModuleDef_HEAD_INIT, "memory", nullptr, -1,
+ MemoryMethods};
+
+PyMODINIT_FUNC PyInit_memory()
+{
+ // static PyModuleDef def = Py::MakeModule("memory", Py::ModuleFunc("foo", foo_test));
+
+ return PyModule_Create(&memorymodule);
+}
+
+} // namespace PyScripting
diff --git a/Source/Core/Scripting/Scripting.vcxproj b/Source/Core/Scripting/Scripting.vcxproj
index 7b16b59b4cd6..5787c0cb4f0c 100644
--- a/Source/Core/Scripting/Scripting.vcxproj
+++ b/Source/Core/Scripting/Scripting.vcxproj
@@ -1,49 +1,65 @@
-
-
-
-
- Debug
- x64
-
-
- Release
- x64
-
-
-
- {83794107-D372-4804-B463-E2719B50FB6B}
- 10.0.15063.0
-
-
-
- StaticLibrary
- v141
- Unicode
-
-
- true
-
-
- false
-
-
-
-
-
-
-
-
-
-
-
-
- $(ExternalsDir)zlib;$(ExternalsDir)python\include;%(AdditionalIncludeDirectories)
-
-
-
-
-
-
-
-
+
+
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ {83794107-D372-4804-B463-E2719B50FB6B}
+ 10.0.15063.0
+
+
+
+ StaticLibrary
+ v141
+ Unicode
+
+
+ true
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+ $(ExternalsDir)zlib;$(ExternalsDir)python\include;%(AdditionalIncludeDirectories)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/Core/Scripting/Scripting.vcxproj.filters b/Source/Core/Scripting/Scripting.vcxproj.filters
index e06016e9066f..c89df9f9e729 100644
--- a/Source/Core/Scripting/Scripting.vcxproj.filters
+++ b/Source/Core/Scripting/Scripting.vcxproj.filters
@@ -1,6 +1,50 @@
-
-
-
-
-
+
+
+
+
+
+ Python
+
+
+
+
+
+ Python
+
+
+ Python
+
+
+ Python
+
+
+ Python
+
+
+ Python
+
+
+ Python
+
+
+ Python\PyFramework
+
+
+ Python\PyFramework
+
+
+ Python\PyFramework
+
+
+
+
+ {4139d98d-00f6-4e16-a15b-68d5e3311cba}
+
+
+ {eeff862f-8e3a-4c8b-a5d3-35a5e8c992f2}
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/Core/Scripting/ScriptingEngine.cpp b/Source/Core/Scripting/ScriptingEngine.cpp
new file mode 100644
index 000000000000..59d79c6e86b2
--- /dev/null
+++ b/Source/Core/Scripting/ScriptingEngine.cpp
@@ -0,0 +1,23 @@
+
+#include "../Common/Logging/Log.h"
+
+#include "ScriptingEngine.h"
+#include "Python/PyScriptingBackend.h"
+
+
+namespace Scripting
+{
+
+void Init()
+{
+ INFO_LOG(SCRIPTING, "Initializing scripting engine...");
+ PyScripting::Init();
+}
+
+void Shutdown()
+{
+ INFO_LOG(SCRIPTING, "Shutting down scripting engine...");
+ PyScripting::Shutdown();
+}
+
+} // namespace Scripting
diff --git a/Source/Core/Scripting/ScriptingEngine.h b/Source/Core/Scripting/ScriptingEngine.h
new file mode 100644
index 000000000000..59d60b426f6d
--- /dev/null
+++ b/Source/Core/Scripting/ScriptingEngine.h
@@ -0,0 +1,9 @@
+#pragma once
+
+namespace Scripting
+{
+
+void Init();
+void Shutdown();
+
+} // namespace Scripting
diff --git a/Source/UnitTests/Common/CMakeLists.txt b/Source/UnitTests/Common/CMakeLists.txt
index 120fee6c3308..a2e96a820f98 100644
--- a/Source/UnitTests/Common/CMakeLists.txt
+++ b/Source/UnitTests/Common/CMakeLists.txt
@@ -14,6 +14,7 @@ add_dolphin_test(NandPathsTest NandPathsTest.cpp)
add_dolphin_test(SPSCQueueTest SPSCQueueTest.cpp)
add_dolphin_test(StringUtilTest StringUtilTest.cpp)
add_dolphin_test(SwapTest SwapTest.cpp)
+add_dolphin_test(TupleUtilTest TupleUtilTest.cpp)
if (_M_X86)
add_dolphin_test(x64EmitterTest x64EmitterTest.cpp)
diff --git a/Source/UnitTests/Common/TupleUtilTest.cpp b/Source/UnitTests/Common/TupleUtilTest.cpp
new file mode 100644
index 000000000000..f06a4a30e345
--- /dev/null
+++ b/Source/UnitTests/Common/TupleUtilTest.cpp
@@ -0,0 +1,46 @@
+// Copyright 2016 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include
+
+#include
+
+#include "Common/TupleUtil.h"
+
+TEST(TupleUtil, MapToSize)
+{
+ struct Foo
+ {
+ };
+ std::tuple tuple;
+
+ std::tuple mapped = TupleMap(tuple, [](auto obj) { return sizeof(obj); });
+
+ EXPECT_EQ(std::get<0>(mapped), sizeof(double));
+ EXPECT_EQ(std::get<1>(mapped), sizeof(std::string));
+ EXPECT_EQ(std::get<2>(mapped), sizeof(Foo));
+}
+
+TEST(TupleUtil, MapToPointer)
+{
+ std::tuple tuple;
+
+ std::tuple mapped = TupleMap(tuple, [](auto& obj) { return &obj; });
+
+ EXPECT_EQ(std::get<0>(tuple), 0);
+ *std::get<0>(mapped) = 42;
+ EXPECT_EQ(std::get<0>(tuple), 42);
+}
+
+TEST(TupleUtil, TupleToArray)
+{
+ std::tuple tuple;
+ std::get<0>(tuple) = 14;
+ std::get<1>(tuple) = 42;
+
+ std::array mapped = TupleToArray(tuple);
+
+ EXPECT_EQ(mapped[0], 14);
+ EXPECT_EQ(mapped[1], 42);
+}