Hi,
da ich nichts public gefunden habe zum Thema Metin2 Client und Python 3 habe ich mir mal den Spaß erlaubt etwas zu experimentieren. Meine Fortschritte will ich hier gerne festhalten, dass auch andere einen Nutzen davon haben.
Updates werden wohl eher unregelmäßig kommen, da ich auch nicht mehr ganz so viel Zeit habe. Neuer Job, viele private Veränderungen, der ein oder andere kann das vielleicht nachvollziehen.
Ohne jetzt viel als Einleitung schreiben zu wollen starte ich aber gleich mal mit dem eigentlichen Thema.
Insgesamt gibt es drei nennenswerte und viele kleine Herausforderungen denen ich bis jetzt begegnet bin.
- Update der CPython Implementierung im Client Source
- Konvertierung der vorhandenen bytecode files im lib Verzeichnis vom Client
- Konvertierung der Python Scripts aus dem Pack Verzeichnis
Auf die drei Punkte will ich jetzt im Detail eingehen, ich packe die jeweiligen Inhalte in Spoiler, sodass sich jeder das raussuchen kann was ihn interessiert ohne zu viel scrollen zu müssen.
Wie bekannt sein dürfte werden die Python Scripts aus dem M2 Client mittels CPython im Client Source verarbeitet. CPython ist open-source auf github verfügbar, was das ganze sehr freundlich gestaltet das Update an sich zu machen.
Ich habe mich dazu entschieden mit CPython 3.10 zu arbeiten, da die Version 3.11 noch ein paar Einschränkungen hatte (als ich mit dem Projekt gestartet habe) und version 3.12 war noch kein stable release.
Schritt 1 war es also das repo zu klonen und die entsprechenden Schritte aus den Docs durchzuführen.
Bitte melden Sie sich an, um diesen Link zu sehen.
Die Vorbereitung zum Kompilieren der entsprechenden libs für den Client sind einfach. Das repo gibt ein batch Script für Windows Hosts, welches alles für einen erledigt.
Alle nötigen Infos sind auch in einer Bitte melden Sie sich an, um diesen Link zu sehen. zu finden, dieses file war mein bester Freund um die Solution zum laufen zu bekommen.
Um die passenden Libraries für den M2 Client zu kompilieren habe ich jeweils im Debug und Release Mode für Win32 Konfiguration folgende Projekte der Solution kompiliert:
- python
- python3dll
Die daraus resultierenden Libraries sind dann im Ordner PCBuild\win32 zu finden.
Für den Client an sich, nicht die Executable, wird die Python310.dll benötigt.
Als Include im Source (Extern\Python3\libs) die python3.lib, python310.lib und python311.lib. Wenn man Funktionen aus der CPython library debugen will, dann auch die entsprechenden *_d.lib Libraries.
Warum die python311.lib benötigt wird ist mir nicht ganz klar, diese ist jedoch im repo unter "externals\pythonx86\tools\libs" zu finden.
Kommen wir generell zum Include im Metin2 Source. In dem EXTERN Ordner, wie er bei den meisten von euch wahrscheinlich auch heißt, habe ich einfach einen neuen Unterordner "Python3" erstellt. Darin sind alle nötigen C/C++ Header und die entsprechenden libraries enthalten. Den Ordner, so wie ich ihn habe, lade ich hier als Anhang hoch.
Der Python3\libs Ordner muss noch in den Einstellungen von dem UserInterface Projekt mit in die Configs(Debug/Distribute*/Release*) aufgenommen werden:
- UserInterface -> Linker -> Alle Optionen -> Zusätzliche Bibliotheksverzeichnisse -> $(ProjectDir)../../Extern\Python3\libs
* Welche Konfiguration ihr benutzt ist hier entscheidend
Nun konnte ich auch anfangen richtigen Code zu schreiben und den M2 Client Source anpassen. Erster Schritt war es ein neues Präprozesser Symbol in der locale_inc.h zu definieren
Alle meine Änderungen habe ich immer über das Symbol bedingt, sodass ich auch ein Fallback auf Python2 jederzeit möglich ist.
Erste Aufgabe für mich war es die Initalisierungsfunktionen für die Python Module wie app, player, item usw. zu korrigieren. Mit dem Upgrade von Python2.X auf Python3 hat sich da grundsätzlich etwas geändert.
Die Funktionen zum Initialisieren der Extension Modules müssen 1. vor dem Initialisieren des eigentlichen Python Interpreters ausgerufen werden und 2. auch anders als Modul registriert werden.
Als Einstiegspunkt dafür nehmen wir den Kontruktor der CPythonLauncher Klasse -> ScriptLib\PythonLauncher.cpp -> CPythonLauncher::CPythonLauncher()
Bevor Py_Initialize(); aufgerufen wird müssen alle Module initialisiert werden. Die Liste der benötigten Module ist in UserInterface.cpp zu finden:
bool RunMainScript(CPythonLauncher& pyLauncher, const char* lpCmdLine)
Hierbei sind alle Funktionen mit folgendem Schema zu berücksichtigen:
initpack();
initdbg();
initime();
initgrp();
initgrpImage();
initgrpText();
Ich bin jeweils so vorgegangen, dass ich mir zu jeder der Funktionen die Deklaration angesehen habe, das war meistens in den StdAfx.h files von den entspechenden Projekten, und dort erstmal die Deklaration verändert habe. Beispiel aus der StdAfx.h aus UserInterface
- #ifdef PYTHON_3
- PyMODINIT_FUNC PyInit_udp(void);
- PyMODINIT_FUNC PyInit_app(void);
- PyMODINIT_FUNC PyInit_ime(void);
- PyMODINIT_FUNC PyInit_systemSetting(void);
- PyMODINIT_FUNC PyInit_chr(void);
- PyMODINIT_FUNC PyInit_chrmgr(void);
- PyMODINIT_FUNC PyInit_Chat(void);
- PyMODINIT_FUNC PyInit_TextTail(void);
- PyMODINIT_FUNC PyInit_Item(void);
- PyMODINIT_FUNC PyInit_NonPlayer(void);
- PyMODINIT_FUNC PyInit_net(void);
- PyMODINIT_FUNC PyInit_Player(void);
- PyMODINIT_FUNC PyInit_SectionDisplayer(void);
- PyMODINIT_FUNC PyInit_ServerStateChecker(void);
- #ifdef ENABLE_RENDER_TARGET
- PyMODINIT_FUNC PyInit_RenderTarget(void);
- #endif
- PyMODINIT_FUNC PyInit_Trade(void);
- PyMODINIT_FUNC PyInit_MiniMap(void);
- PyMODINIT_FUNC PyInit_Profiler(void);
- PyMODINIT_FUNC PyInit_Event(void);
- PyMODINIT_FUNC PyInit_effect(void);
- PyMODINIT_FUNC PyInit_snd(void);
- PyMODINIT_FUNC PyInit_eventMgr(void);
- PyMODINIT_FUNC PyInit_Background(void);
- PyMODINIT_FUNC PyInit_wndMgr(void);
- PyMODINIT_FUNC PyInit_shop(void);
- PyMODINIT_FUNC PyInit_pack(void);
- PyMODINIT_FUNC PyInit_skill(void);
- PyMODINIT_FUNC PyInit_fly(void);
- PyMODINIT_FUNC PyInit_quest(void);
- PyMODINIT_FUNC PyInit_safebox(void);
- PyMODINIT_FUNC PyInit_guild(void);
- PyMODINIT_FUNC PyInit_Messenger(void);
- #ifdef ENABLE_ACCE_SYSTEM
- PyMODINIT_FUNC PyInit_Acce(void);
- #endif
- #ifdef __USE_PYTHON_SHINING__
- PyMODINIT_FUNC PyInit_Shining(void);
- #endif
- #else
- void initudp();
- void initapp();
- void initime();
- void initsystemSetting();
- void initchr();
- void initchrmgr();
- void initChat();
- void initTextTail();
- void initime();
- void initItem();
- void initNonPlayer();
- void initnet();
- void initPlayer();
- void initSectionDisplayer();
- void initServerStateChecker();
- #ifdef ENABLE_RENDER_TARGET
- void initRenderTarget();
- #endif
- void initTrade();
- void initMiniMap();
- void initProfiler();
- void initEvent();
- void initeffect();
- void initsnd();
- void initeventmgr();
- void initBackground();
- void initwndMgr();
- void initshop();
- void initpack();
- void initskill();
- void initfly();
- void initquest();
- void initsafebox();
- void initguild();
- void initMessenger();
- #ifdef ENABLE_ACCE_SYSTEM
- void initAcce();
- #endif
- #ifdef __USE_PYTHON_SHINING__
- void initShining();
- #endif
- #endif
Dieses Vorgehen habe ich für alle Funktionsaufrufe aus der o.g. Funktion RunMainScript angewandt.
Hierbei ist unbedingt darauf zu achten, dass die Init Funktionen zu 100% nach dem Schema benannt werden:
PyMODINITFUNC PyInit_<ModuleName>(void)
Die Dokumentation weißt dort explizit drauf hin.
Nun müssen noch die Funktionen an sich verändert werden.
Die jeweiligen Implementierungen müssen angepasst werden, z.B. wie hier
Nun kommt der erste etwas komplizierte Teil. jede der Init Funktionen muss zwangsweise ein PyObject zurückgeben, dieses wird in der Funktion selbst erstellt. Dazu wurde in der alten Python Version die Funktion Py_InitModule() benutzt. Diese Funktion ist mittlerweile aber nicht mehr verfügbar. Es muss nun wie folgt vorgegangen werden:
- #ifdef PYTHON_3
- static struct PyModuleDef app =
- {
- PyModuleDef_HEAD_INIT, //base -> always has to be PyModuleDef_HEAD_INIT
- "app", //Name of the module
- "app interface", //Documentation, can be an empty string
- -1, //size of the module -> -1 means that the module does not support sub-interpreters, because it has global state. required for multi-phase init
- s_methods //pointer to a table of module-level functions, described by PyMethodDef values. Can be NULL if no functions are present.
- };
- PyObject * poModule = PyModule_Create(&app);
- #else
- PyObject * poModule = Py_InitModule("app", s_methods);
- #endif
Wir erstellen ein struct PyModuleDef mit dem Namen des Moduls. Die Parameter des structs sind als Kommentar im Code beschrieben. Mit diesem struct wird die PyModule_Create Funktion aufgerufen und wir bekommen ein PyObject zurück.
Nun ist es wichtig zu unterscheiden ob mit diesem PyObject (Module) noch etwas passiert, also ob noch Konstanten zugewiesen werden wie z.B. in dem app Module
- PyModule_AddIntConstant(poModule, "INFO_ITEM", CPythonApplication::INFO_ITEM);
- PyModule_AddIntConstant(poModule, "INFO_ACTOR", CPythonApplication::INFO_ACTOR);
- PyModule_AddIntConstant(poModule, "INFO_EFFECT", CPythonApplication::INFO_EFFECT);
- PyModule_AddIntConstant(poModule, "INFO_TEXTTAIL", CPythonApplication::INFO_TEXTTAIL);
- PyModule_AddIntConstant(poModule, "DEGREE_DIRECTION_SAME", DEGREE_DIRECTION_SAME);
- PyModule_AddIntConstant(poModule, "DEGREE_DIRECTION_RIGHT", DEGREE_DIRECTION_RIGHT);
- PyModule_AddIntConstant(poModule, "DEGREE_DIRECTION_LEFT", DEGREE_DIRECTION_LEFT);
Wenn dies der Fall ist, dann dürfen wir das PyObject erst ganz am Ende der Funktion zurückgeben. Wenn mit dem Module, also dem Object, nichts mehr passiert, dann könne wir direkt ein return programmieren wie in diesem Beispiel.
Nachdem ich alle vorhandenen Init Funktionen umgeschrieben habe, habe ich diese im Konstruktur des PythonLaunchers augerufen und die Module entsprechend registriert.
- CPythonLauncher::CPythonLauncher()
- {
- #ifdef PYTHON_3
- PyImport_AppendInittab("pack", PyInit_pack);
- PyImport_AppendInittab("dbg", PyInit_dbg);
- PyImport_AppendInittab("ime", PyInit_ime);
- PyImport_AppendInittab("grp", PyInit_grp);
- PyImport_AppendInittab("grpImage", PyInit_grpImage);
- PyImport_AppendInittab("grpText", PyInit_grpText);
- PyImport_AppendInittab("wndMgr", PyInit_wndMgr);
- PyImport_AppendInittab("udp", PyInit_udp);
- PyImport_AppendInittab("app", PyInit_app);
- PyImport_AppendInittab("systemSetting", PyInit_systemSetting);
- PyImport_AppendInittab("chr", PyInit_chr);
- PyImport_AppendInittab("chrmgr", PyInit_chrmgr);
- PyImport_AppendInittab("Player", PyInit_Player);
- PyImport_AppendInittab("Item", PyInit_Item);
- PyImport_AppendInittab("NonPlayer", PyInit_NonPlayer);
- PyImport_AppendInittab("Trade", PyInit_Trade);
- PyImport_AppendInittab("Chat", PyInit_Chat);
- PyImport_AppendInittab("TextTail", PyInit_TextTail);
- PyImport_AppendInittab("net", PyInit_net);
- PyImport_AppendInittab("MiniMap", PyInit_MiniMap);
- PyImport_AppendInittab("Profiler", PyInit_Profiler);
- PyImport_AppendInittab("Event", PyInit_Event);
- PyImport_AppendInittab("effect", PyInit_effect);
- PyImport_AppendInittab("fly", PyInit_fly);
- PyImport_AppendInittab("snd", PyInit_snd);
- PyImport_AppendInittab("eventMgr", PyInit_eventMgr);
- PyImport_AppendInittab("shop", PyInit_shop);
- PyImport_AppendInittab("skill", PyInit_skill);
- PyImport_AppendInittab("quest", PyInit_quest);
- PyImport_AppendInittab("Background", PyInit_Background);
- PyImport_AppendInittab("Messenger", PyInit_Messenger);
- #ifdef ENABLE_ACCE_SYSTEM
- PyImport_AppendInittab("Acce", PyInit_Acce);
- #endif
- PyImport_AppendInittab("safebox", PyInit_safebox);
- PyImport_AppendInittab("guild", PyInit_guild);
- PyImport_AppendInittab("ServerStateChecker", PyInit_ServerStateChecker);
- #ifdef ENABLE_RENDER_TARGET
- PyImport_AppendInittab("RenderTarget", PyInit_RenderTarget);
- #endif
- #ifdef __USE_PYTHON_SHINING__
- PyImport_AppendInittab("Shining", PyInit_Shining);
- #endif
- #endif
- Py_Initialize();
- }
Gleichzeitig musste ich die "alten" Funktionsaufrufe ungültig machen, bzw. durch das Preprocessor Symbol ausschließen
Direkt in dem selben Source File musste ich feststellen, dass sich der Python Modulimport für "__builtin__" verändert hat. Dieses Modul ist nun mit "builtins" zu importieren.
Zusätzlich muss direkt am Anfang der UserInterface.cpp noch auf die neue Python lib verwiesen werden. Auch hier kann wieder unterschieden werden zwischen Debug und nicht Debug, das habe ich mir aber geschenkt da ich keinen Sinn darin sehe die CPython Funktionen aus der Lib zu debuggen.
So richtig trial & error war es heruaszufinden welcher der bytecode libs nun wirklich benötigt werden und welche nicht. Ich bin mit dieser Anpassung in UserInterface bis jetzt ganz gut klargekommen.
Generell müssen sämtliche Includes von Python2 Header Files durch das preprocessor symbol bedingt werden. Ich hier bin ich mir noch nicht zu 100% sicher ob ich etwas Wichtiges ausgelassen habe, so weit konnte ich leider noch nicht testen.
Das erste Include befindet sich in scriptLib/PythonLauncher.cpp sowie in dem entspechenden Header File
- #ifdef _DEBUG
- #undef _DEBUG
- #ifdef PYTHON_3
- #include "../../Extern/Python3/Python.h"
- #else
- #include "../../Extern/Python2/Python.h"
- #endif
- #define _DEBUG
- #else
- #ifdef PYTHON_3
- #include "../../Extern/Python3/Python.h"
- #else
- #include "../../Extern/Python2/Python.h"
- #endif
- #endif
- #ifdef PYTHON_3
- //#include "../../Extern/Python3/node.h"
- //#include "../../Extern/Python3/grammar.h"
- #include "../../Extern/Python3/token.h"
- //#include "../../Extern/Python3/parsetok.h"
- #include "../../Extern/Python3/errcode.h"
- #include "../../Extern/Python3/compile.h"
- //#include "../../Extern/Python3/symtable.h"
- //#include "../../Extern/Python3/eval.h"
- #include "../../Extern/Python3/marshal.h"
- #else
- #include "../../Extern/Python2/node.h"
- #include "../../Extern/Python2/grammar.h"
- #include "../../Extern/Python2/token.h"
- #include "../../Extern/Python2/parsetok.h"
- #include "../../Extern/Python2/errcode.h"
- #include "../../Extern/Python2/compile.h"
- #include "../../Extern/Python2/symtable.h"
- #include "../../Extern/Python2/eval.h"
- #include "../../Extern/Python2/marshal.h"
- #endif
Ein Include musste tatsächlich ausgebaut werden, hier habe ich kein Replacement zu gefunden bzw. keine wirkliche verwendung im Quellcode
Ein weiteres Problem, welches ich irgendwie auflösen musste, war das Update von PyString_* zu PyUnicode_*, neue Funktionen/getter-setter von PyFrameObjects sowie Update von PyInt_* zu PyLong_*. Diese Themen gab es glücklicherweise nicht allzu oft, den ganzen Kack aufzulösen hat ewig gedauert. Ich komprimiere hier mal als Code-Ausschnitte was ich alles geändert habe.
- void CWindow::OnUpdate()
- {
- if (!m_poHandler)
- return;
- if (!IsShow())
- return;
- #ifdef PYTHON_3
- static PyObject* poFuncName_OnUpdate = PyUnicode_InternFromString("OnUpdate");
- #else
- static PyObject* poFuncName_OnUpdate = PyString_InternFromString("OnUpdate");
- #endif
- //PyCallClassMemberFunc(m_poHandler, "OnUpdate", BuildEmptyTuple());
- PyCallClassMemberFunc_ByPyString(m_poHandler, poFuncName_OnUpdate, BuildEmptyTuple());
- }
- void Traceback()
- {
- std::string str;
- for (int i = 0; i < g_nCurTraceN; ++i)
- {
- str.append(g_stTraceBuffer[i]);
- str.append("\n");
- }
- PyObject * exc;
- PyObject * v;
- PyObject * tb;
- const char * errStr;
- PyErr_Fetch(&exc, &v, &tb);
- #ifdef PYTHON_3
- if (PyUnicode_Check(v))
- {
- errStr = (const char*)PyUnicode_2BYTE_DATA(v);
- str.append("Error: ");
- str.append(errStr);
- Tracef("%s\n", errStr);
- }
- #else
- if (PyString_Check(v))
- {
- errStr = PyString_AS_STRING(v);
- str.append("Error: ");
- str.append(errStr);
- Tracef("%s\n", errStr);
- }
- #endif
- Py_XDECREF(exc); //changed to XDECREF to allow NULL parameters
- Py_XDECREF(v); //changed to XDECREF to allow NULL parameters
- Py_XDECREF(tb); //changed to XDECREF to allow NULL parameters
- LogBoxf("Traceback:\n\n%s\n", str.c_str());
- }
- int TraceFunc(PyObject * obj, PyFrameObject * f, int what, PyObject *arg)
- {
- const char * funcname;
- char szTraceBuffer[128];
- #ifdef PYTHON_3
- int lineNo = 0;
- #endif
- switch (what)
- {
- case PyTrace_CALL:
- if (g_nCurTraceN >= 512)
- return 0;
- if (Py_OptimizeFlag)
- #ifdef PYTHON_3
- lineNo = PyCode_Addr2Line(PyFrame_GetCode(f), f->f_lasti); //Use "PyFrame_GetLasti(f)" instead of "f->f_lasti" in Python 3.11 and above
- #else
- f->f_lineno = PyCode_Addr2Line(f->f_code, f->lasti);
- #endif
- #ifdef PYTHON_3
- if (PyFrame_GetCode(f) != NULL)
- {
- funcname = PyUnicode_AS_DATA(PyFrame_GetCode(f)->co_name);
- _snprintf(szTraceBuffer, sizeof(szTraceBuffer), "Call: File \"%s\", line %d, in %s",
- PyUnicode_AS_DATA(PyFrame_GetCode(f)->co_filename),
- PyFrame_GetLineNumber(f),
- funcname);
- }
- #else
- funcname = PyString_AsString(f->f_code->co_name);
- _snprintf(szTraceBuffer, sizeof(szTraceBuffer), "Call: File \"%s\", line %d, in %s",
- PyString_AsString(f->f_code->co_filename),
- f->f_lineno,
- funcname);
- #endif
- g_stTraceBuffer[g_nCurTraceN++]=szTraceBuffer;
- break;
- case PyTrace_RETURN:
- if (g_nCurTraceN > 0)
- --g_nCurTraceN;
- break;
- case PyTrace_EXCEPTION:
- if (g_nCurTraceN >= 512)
- return 0;
- PyObject * exc_type, * exc_value, * exc_traceback;
- PyTuple_GetObject(arg, 0, &exc_type);
- PyTuple_GetObject(arg, 1, &exc_value);
- PyTuple_GetObject(arg, 2, &exc_traceback);
- int len;
- const char * exc_str;
- PyObject_AsCharBuffer(exc_type, &exc_str, &len);
- #ifdef PYTHON_3
- if (PyFrame_GetCode(f) != nullptr)
- {
- _snprintf(szTraceBuffer, sizeof(szTraceBuffer), "Exception: File \"%s\", line %d, in %s",
- PyUnicode_AS_DATA(PyFrame_GetCode(f)->co_filename),
- PyFrame_GetLineNumber(f),
- PyUnicode_AS_DATA(PyFrame_GetCode(f)->co_name));
- }
- #else
- _snprintf(szTraceBuffer, sizeof(szTraceBuffer), "Exception: File \"%s\", line %d, in %s",
- PyString_AS_STRING(f->f_code->co_filename),
- f->f_lineno,
- PyString_AS_STRING(f->f_code->co_name));
- #endif
- g_stTraceBuffer[g_nCurTraceN++]=szTraceBuffer;
- break;
- }
- return 0;
- }
- bool CPythonLauncher::Create(const char* c_szProgramName)
- {
- NANOBEGIN
- #ifdef PYTHON_3
- Py_SetProgramName((const wchar_t*)c_szProgramName);
- #else
- Py_SetProgramName((char*)c_szProgramName);
- #endif
- #ifdef _DEBUG
- //PyEval_SetTrace(TraceFunc, NULL);
- #endif
- m_poModule = PyImport_AddModule((char *) "__main__");
- if (!m_poModule)
- return false;
- m_poDic = PyModule_GetDict(m_poModule);
- #ifdef PYTHON_3
- PyObject* builtins = PyImport_ImportModule("builtins");
- #else
- PyObject * builtins = PyImport_ImportModule("__builtin__");
- #endif
- PyModule_AddIntConstant(builtins, "TRUE", 1);
- PyModule_AddIntConstant(builtins, "FALSE", 0);
- #ifdef PYTHON_3
- PyDict_SetItemString(m_poDic, "builtins", builtins);
- #else
- PyDict_SetItemString(m_poDic, "__builtins__", builtins);
- #endif
- Py_DECREF(builtins);
- if (!RunLine("import __main__"))
- return false;
- if (!RunLine("import sys"))
- return false;
- NANOEND
- return true;
- }
- bool CPythonLauncher::RunCompiledFile(const char* c_szFileName)
- {
- NANOBEGIN
- FILE * fp = fopen(c_szFileName, "rb");
- if (!fp)
- return false;
- PyCodeObject *co;
- PyObject *v;
- long magic;
- long PyImport_GetMagicNumber(void);
- magic = _PyMarshal_ReadLongFromFile(fp);
- if (magic != PyImport_GetMagicNumber())
- {
- PyErr_SetString(PyExc_RuntimeError, "Bad magic number in .pyc file");
- fclose(fp);
- return false;
- }
- _PyMarshal_ReadLongFromFile(fp);
- v = _PyMarshal_ReadLastObjectFromFile(fp);
- fclose(fp);
- if (!v || !PyCode_Check(v))
- {
- Py_XDECREF(v);
- PyErr_SetString(PyExc_RuntimeError, "Bad code object in .pyc file");
- return false;
- }
- #ifdef PYTHON_3
- v = PyEval_EvalCode(v, m_poDic, m_poDic);
- #else
- co = (PyCodeObject*)v;
- v = PyEval_EvalCode(co, m_poDic, m_poDic);
- #endif
- /* if (v && flags)
- flags->cf_flags |= (co->co_flags & PyCF_MASK);*/
- Py_DECREF(co);
- if (!v)
- {
- Traceback();
- return false;
- }
- Py_DECREF(v);
- #ifndef PYTHON_3
- if (Py_FlushLine())
- #endif
- PyErr_Clear();
- NANOEND
- return true;
- }
- bool CPythonLauncher::RunLine(const char* c_szSrc)
- {
- #ifdef PYTHON_3
- PyObject* v = PyRun_String(c_szSrc, Py_file_input, m_poDic, m_poDic);
- #else
- PyObject * v = PyRun_String((char *) c_szSrc, Py_file_input, m_poDic, m_poDic);
- #endif
- if (!v)
- {
- Traceback();
- return false;
- }
- Py_DECREF(v);
- return true;
- }
- const char* CPythonLauncher::GetError()
- {
- PyObject* exc;
- PyObject* v;
- PyObject* tb;
- PyErr_Fetch(&exc, &v, &tb);
- #ifdef PYTHON_3
- if (PyUnicode_Check(v))
- return PyUnicode_AS_DATA(v);
- #else
- if (PyString_Check(v))
- return PyString_AS_STRING(v);
- #endif
- return "";
- }
- static PyObject * r_object(RFILE *p)
- {
- PyObject *v, *v2;
- long i, n;
- int type = r_byte(p);
- switch (type) {
- case EOF:
- PyErr_SetString(PyExc_EOFError,
- "EOF read where object expected");
- return NULL;
- case TYPE_NULL:
- return NULL;
- case TYPE_NONE:
- Py_INCREF(Py_None);
- return Py_None;
- case TYPE_STOPITER:
- Py_INCREF(PyExc_StopIteration);
- return PyExc_StopIteration;
- case TYPE_ELLIPSIS:
- Py_INCREF(Py_Ellipsis);
- return Py_Ellipsis;
- case TYPE_INT:
- #ifdef PYTHON_3
- return PyLong_FromLong(r_long(p));
- #else
- return PyInt_FromLong(r_long(p));
- #endif
- case TYPE_INT64:
- return r_long64(p);
- case TYPE_LONG:
- {
- int size;
- PyLongObject* ob;
- n = r_long(p);
- size = n<0 ? -n : n;
- ob = _PyLong_New(size);
- if (ob == NULL)
- return NULL;
- #ifndef PYTHON_3
- ob->ob_size = n;
- #endif
- for (i = 0; i < size; i++)
- ob->ob_digit[i] = (short) r_short(p);
- return (PyObject *) ob;
- }
- case TYPE_FLOAT:
- {
- char buf[256];
- double dx;
- n = r_byte(p);
- if (r_string(buf, (int)n, p) != n) {
- PyErr_SetString(PyExc_EOFError,
- "EOF read where object expected");
- return NULL;
- }
- buf[n] = '\0';
- PyFPE_START_PROTECT("atof", return 0)
- dx = atof(buf);
- PyFPE_END_PROTECT(dx)
- return PyFloat_FromDouble(dx);
- }
- #ifndef WITHOUT_COMPLEX
- case TYPE_COMPLEX:
- {
- char buf[256];
- Py_complex c;
- n = r_byte(p);
- if (r_string(buf, (int)n, p) != n) {
- PyErr_SetString(PyExc_EOFError,
- "EOF read where object expected");
- return NULL;
- }
- buf[n] = '\0';
- PyFPE_START_PROTECT("atof", return 0)
- c.real = atof(buf);
- PyFPE_END_PROTECT(c)
- n = r_byte(p);
- if (r_string(buf, (int)n, p) != n) {
- PyErr_SetString(PyExc_EOFError,
- "EOF read where object expected");
- return NULL;
- }
- buf[n] = '\0';
- PyFPE_START_PROTECT("atof", return 0)
- c.imag = atof(buf);
- PyFPE_END_PROTECT(c)
- return PyComplex_FromCComplex(c);
- }
- #endif
- case TYPE_STRING:
- n = r_long(p);
- if (n < 0) {
- PyErr_SetString(PyExc_ValueError, "bad marshal data");
- return NULL;
- }
- #ifdef PYTHON_3
- v = PyUnicode_FromStringAndSize((char*)NULL, n);
- if (v != NULL) {
- if (r_string(const_cast<char *>(PyUnicode_AS_DATA(v)), (int)n, p) != n) {
- Py_DECREF(v);
- v = NULL;
- PyErr_SetString(PyExc_EOFError,
- "EOF read where object expected");
- }
- }
- #else
- v = PyString_FromStringAndSize((char *)NULL, n);
- if (v != NULL) {
- if (r_string(PyString_AS_STRING(v), (int)n, p) != n) {
- Py_DECREF(v);
- v = NULL;
- PyErr_SetString(PyExc_EOFError,
- "EOF read where object expected");
- }
- }
- #endif
- return v;
- #ifdef Py_USING_UNICODE
- case TYPE_UNICODE:
- {
- char *buffer;
- n = r_long(p);
- if (n < 0) {
- PyErr_SetString(PyExc_ValueError, "bad marshal data");
- return NULL;
- }
- buffer = PyMem_NEW(char, n);
- if (buffer == NULL)
- return PyErr_NoMemory();
- if (r_string(buffer, (int)n, p) != n) {
- PyMem_DEL(buffer);
- PyErr_SetString(PyExc_EOFError,
- "EOF read where object expected");
- return NULL;
- }
- v = PyUnicode_DecodeUTF8(buffer, n, NULL);
- PyMem_DEL(buffer);
- return v;
- }
- #endif
- case TYPE_TUPLE:
- n = r_long(p);
- if (n < 0) {
- PyErr_SetString(PyExc_ValueError, "bad marshal data");
- return NULL;
- }
- v = PyTuple_New((int)n);
- if (v == NULL)
- return v;
- for (i = 0; i < n; i++) {
- v2 = r_object(p);
- if ( v2 == NULL ) {
- Py_DECREF(v);
- v = NULL;
- break;
- }
- PyTuple_SET_ITEM(v, (int)i, v2);
- }
- return v;
- case TYPE_LIST:
- n = r_long(p);
- if (n < 0) {
- PyErr_SetString(PyExc_ValueError, "bad marshal data");
- return NULL;
- }
- v = PyList_New((int)n);
- if (v == NULL)
- return v;
- for (i = 0; i < n; i++) {
- v2 = r_object(p);
- if ( v2 == NULL ) {
- Py_DECREF(v);
- v = NULL;
- break;
- }
- PyList_SetItem(v, (int)i, v2);
- }
- return v;
- case TYPE_DICT:
- v = PyDict_New();
- if (v == NULL)
- return NULL;
- for (;;) {
- PyObject *key, *val;
- key = r_object(p);
- if (key == NULL)
- break; /* XXX Assume TYPE_NULL, not an error */
- val = r_object(p);
- if (val != NULL)
- PyDict_SetItem(v, key, val);
- Py_DECREF(key);
- Py_XDECREF(val);
- }
- return v;
- case TYPE_CODE:
- #ifndef PYTHON_3
- if (PyEval_GetRestricted()) {
- PyErr_SetString(PyExc_RuntimeError,
- "cannot unmarshal code objects in "
- "restricted execution mode");
- return NULL;
- }
- else {
- #else
- {
- #endif
- int argcount = r_short(p);
- int nlocals = r_short(p);
- int stacksize = r_short(p);
- int flags = r_short(p);
- PyObject *code = NULL;
- PyObject *consts = NULL;
- PyObject *names = NULL;
- PyObject *varnames = NULL;
- PyObject *freevars = NULL;
- PyObject *cellvars = NULL;
- PyObject *filename = NULL;
- PyObject *name = NULL;
- int firstlineno = 0;
- PyObject *lnotab = NULL;
- #ifdef PYTHON_3
- PyObject* exceptiontable = NULL;
- PyObject* whatever = NULL;
- #endif
- code = r_object(p);
- if (code) consts = r_object(p);
- if (consts) names = r_object(p);
- if (names) varnames = r_object(p);
- if (varnames) freevars = r_object(p);
- if (freevars) cellvars = r_object(p);
- if (cellvars) filename = r_object(p);
- if (filename) name = r_object(p);
- if (name) {
- firstlineno = r_short(p);
- lnotab = r_object(p);
- }
- #ifdef PYTHON_3
- if (!PyErr_Occurred()) {
- //Use this code for Python 3.11 and above
- /*v = (PyObject*)PyCode_New(
- argcount, 0, nlocals, stacksize, flags,
- code, consts, names, varnames,
- freevars, cellvars, filename, name, whatever,
- firstlineno, lnotab, exceptiontable);
- */
- v = (PyObject*)PyCode_New(argcount, 0, nlocals, stacksize, flags,
- code, consts, names, varnames,
- freevars, cellvars, filename, name,
- firstlineno, lnotab);
- }
- #else
- if (!PyErr_Occurred()) {
- v = (PyObject *) PyCode_New(
- argcount, nlocals, stacksize, flags,
- code, consts, names, varnames,
- freevars, cellvars, filename, name,
- firstlineno, lnotab);
- }
- #endif
- else
- v = NULL;
- Py_XDECREF(code);
- Py_XDECREF(consts);
- Py_XDECREF(names);
- Py_XDECREF(varnames);
- Py_XDECREF(freevars);
- Py_XDECREF(cellvars);
- Py_XDECREF(filename);
- Py_XDECREF(name);
- Py_XDECREF(lnotab);
- }
- return v;
- default:
- /* Bogus data got written, which isn't ideal.
- This will let you keep working and recover. */
- PyErr_SetString(PyExc_ValueError, "bad marshal data");
- return NULL;
- }
- }
- bool PyTuple_GetString(PyObject* poArgs, int pos, char** ret)
- {
- if (pos >= PyTuple_Size(poArgs))
- return false;
- PyObject* poItem = PyTuple_GetItem(poArgs, pos);
- if (!poItem)
- return false;
- #ifdef PYTHON_3
- if (!PyUnicode_Check(poItem))
- return false;
- *ret = const_cast<char*>(PyUnicode_AS_DATA(poItem));
- #else
- if (!PyString_Check(poItem))
- return false;
- *ret = PyString_AsString(poItem);
- #endif
- return true;
- }
- PyObject* appShowWebPage(PyObject* poSelf, PyObject* poArgs)
- {
- char* szWebPage;
- if (!PyTuple_GetString(poArgs, 0, &szWebPage))
- return Py_BuildException();
- PyObject* poRect=PyTuple_GetItem(poArgs, 1);
- if (!PyTuple_Check(poRect))
- return Py_BuildException();
- RECT rcWebPage;
- #ifdef PYTHON_3
- rcWebPage.left = PyLong_AsLong(PyTuple_GetItem(poRect, 0));
- rcWebPage.top = PyLong_AsLong(PyTuple_GetItem(poRect, 1));
- rcWebPage.right = PyLong_AsLong(PyTuple_GetItem(poRect, 2));
- rcWebPage.bottom = PyLong_AsLong(PyTuple_GetItem(poRect, 3));
- #else
- rcWebPage.left=PyInt_AsLong(PyTuple_GetItem(poRect, 0));
- rcWebPage.top=PyInt_AsLong(PyTuple_GetItem(poRect, 1));
- rcWebPage.right=PyInt_AsLong(PyTuple_GetItem(poRect, 2));
- rcWebPage.bottom=PyInt_AsLong(PyTuple_GetItem(poRect, 3));
- #endif
- CPythonApplication::Instance().ShowWebPage(
- szWebPage,
- rcWebPage
- );
- return Py_BuildNone();
- }
- PyObject* appMoveWebPage(PyObject* poSelf, PyObject* poArgs)
- {
- PyObject* poRect=PyTuple_GetItem(poArgs, 0);
- if (!PyTuple_Check(poRect))
- return Py_BuildException();
- RECT rcWebPage;
- #ifdef PYTHON_3
- rcWebPage.left = PyLong_AsLong(PyTuple_GetItem(poRect, 0));
- rcWebPage.top = PyLong_AsLong(PyTuple_GetItem(poRect, 1));
- rcWebPage.right = PyLong_AsLong(PyTuple_GetItem(poRect, 2));
- rcWebPage.bottom = PyLong_AsLong(PyTuple_GetItem(poRect, 3));
- #else
- rcWebPage.left=PyInt_AsLong(PyTuple_GetItem(poRect, 0));
- rcWebPage.top=PyInt_AsLong(PyTuple_GetItem(poRect, 1));
- rcWebPage.right=PyInt_AsLong(PyTuple_GetItem(poRect, 2));
- rcWebPage.bottom=PyInt_AsLong(PyTuple_GetItem(poRect, 3));
- #endif
- CPythonApplication::Instance().MoveWebPage(rcWebPage);
- return Py_BuildNone();
- }
- PyObject* appGetFileList(PyObject* poSelf, PyObject* poArgs)
- {
- char* szFilter;
- if (!PyTuple_GetString(poArgs, 0, &szFilter))
- return Py_BuildException();
- PyObject* poList=PyList_New(0);
- WIN32_FIND_DATA wfd;
- memset(&wfd, 0, sizeof(wfd));
- HANDLE hFind = FindFirstFile(szFilter, &wfd);
- if (hFind != INVALID_HANDLE_VALUE)
- {
- do
- {
- #ifdef PYTHON_3
- PyObject* poFileName = PyUnicode_FromString(wfd.cFileName);
- #else
- PyObject* poFileName=PyString_FromString(wfd.cFileName);
- #endif
- PyList_Append(poList, poFileName);
- }
- while (FindNextFile(hFind, &wfd));
- FindClose(hFind);
- }
- return poList;
- }
- PyObject* playerSendDragonSoulRefine(PyObject* poSelf, PyObject* poArgs)
- {
- BYTE bSubHeader;
- PyObject* pDic;
- TItemPos RefineItemPoses[DS_REFINE_WINDOW_MAX_NUM];
- if (!PyTuple_GetByte(poArgs, 0, &bSubHeader))
- return Py_BuildException();
- switch (bSubHeader)
- {
- case DS_SUB_HEADER_CLOSE:
- break;
- case DS_SUB_HEADER_DO_UPGRADE:
- case DS_SUB_HEADER_DO_IMPROVEMENT:
- case DS_SUB_HEADER_DO_REFINE:
- {
- if (!PyTuple_GetObject(poArgs, 1, &pDic))
- return Py_BuildException();
- int pos = 0;
- PyObject* key, *value;
- int size = PyDict_Size(pDic);
- while (PyDict_Next(pDic, &pos, &key, &value))
- {
- #ifdef PYTHON_3
- int i = PyLong_AsLong(key);
- #else
- int i = PyInt_AsLong(key);
- #endif
- if (i > DS_REFINE_WINDOW_MAX_NUM)
- return Py_BuildException();
- if (!PyTuple_GetByte(value, 0, &RefineItemPoses[i].window_type)
- || !PyTuple_GetInteger(value, 1, &RefineItemPoses[i].cell))
- {
- return Py_BuildException();
- }
- }
- }
- break;
- }
- CPythonNetworkStream& rns=CPythonNetworkStream::Instance();
- rns.SendDragonSoulRefinePacket(bSubHeader, RefineItemPoses);
- return Py_BuildNone();
- }
Die erste Frage die es zu beantworten gilt: Was ist Python bytecode und wofür brauche ich das? -> Bitte melden Sie sich an, um diesen Link zu sehen.
Nachdem wir das geklärt haben ist die Frage, wie bekomme ich Python bytecode? Und welche Script Files will ich in bytecode kompilieren?
Ich habe mir dazu aus dem CPython 3.10 repo im Ordner Lib die entsprechenden Python Scripts raus gesucht, welche bereits in meinem Lib ordner als alter bytecode vorhanden waren.
Den alten bytecode bitte als Backup speichern!
Einige Module/Scripts wurden umbenannt oder verschoben/verschmolzen. Wenn ihr etwas nicht findet, dann ist Google da der beste Freund.
Alle diese Basis Scripts habe ich mir dann in ein separates Verzeichnis kopiert und mit dem Python CLI allesamt in bytecode kompiliert. Siehe hierfür die Bitte melden Sie sich an, um diesen Link zu sehen.
Mit dem daraus resultierenden bytecode ist der Client erstmal zufrieden. Wie es später aussieht, wenn ich auch die Python Scripts des Clients konvertiert habe, ist eine ganz andere Frage.
Jedes "Kapitel" wird hier dann aber aktualisiert sobald ich einen neueren Stand habe.
Aktueller Stand:
29.11.2022 - 23:55