[RELEASE] Pack Module Fix

01/06/2015 06:17 .Alpha.#1
Hello,

You want to stop people from extracting your client with Python?

It's actually pretty simple but this is a tutorial for advanced users ...

First we need a new pack module:
Code:
bool packExist(char* strFileName)
{
	// ÆÄÀ̽㿡¼* Àоîµå¸®´Â ÆÐÅ· ÆÄÀÏÀº python ÆÄÀϰú txt ÆÄÀÏ¿¡ ÇÑÁ¤ÇÑ´Ù
	const char* pcExt = strrchr(strFileName, '.');
	if (pcExt) // È®ÀåÀÚ°¡ ÀÖ°í
	{
		if ((stricmp(pcExt, ".py") == 0) ||
			//(stricmp(pcExt, ".pyc") == 0) ||
			(stricmp(pcExt, ".txt") == 0))
		{
			return CEterPackManager::Instance().isExist(strFileName);
		}
	}
	return false;
}

PyObject * packImportAsModule(PyObject* poSelf, PyObject * poArgs)
{
	char* strFileName;

	if (!PyTuple_GetString(poArgs, 0, &strFileName))
		return Py_BuildException();

	if (packExist(strFileName))
	{
		CMappedFile file;
		const void * pData = NULL;
		if (CEterPackManager::Instance().Get(file, strFileName, &pData))
		{
			PyObject* co = Py_CompileString((const char*)pData, strFileName, Py_file_input);
			std::string moduleName = CFileNameHelper::NoExtension(std::string(strFileName));
			PyObject* newCo = PyImport_ExecCodeModule(const_cast<char*>(moduleName.c_str()), co);
			return Py_BuildValue("S", newCo);
		}

	}
	return Py_BuildValue("b", 0);

}

PyObject * packExist(PyObject * poSelf, PyObject * poArgs)
{
	char * strFileName;

	if (!PyTuple_GetString(poArgs, 0, &strFileName))
		return Py_BuildException();

	return Py_BuildValue("i", packExist(strFileName));
}

PyObject * packGet(PyObject * poSelf, PyObject * poArgs)
{
	char * strFileName;

	if (!PyTuple_GetString(poArgs, 0, &strFileName))
		return Py_BuildException();

	if (packExist(strFileName))
	{
		CMappedFile file;
		const void * pData = NULL;
		if (CEterPackManager::Instance().Get(file, strFileName, &pData) && strlen((const char *)pData) == file.Size())
			return Py_BuildValue("s#",pData, file.Size());
	}

	return Py_BuildException();
}

void initpack()
{
	static PyMethodDef s_methods[] =
	{
		{ "Exist", packExist, METH_VARARGS },
		{ "Get", packGet, METH_VARARGS }, // remove this after you implemented everything to load npclist and other files with the binary
		{ "ImportAsModule", packImportAsModule, METH_VARARGS },
		{ NULL, NULL },		
	};

	Py_InitModule("VFSImporter", s_methods);
}
Now we need to replace

Code:
void CPythonCharacterManager::LoadNPCList()
{
	const VOID* pvData;
	CMappedFile kFile;
	if (!CEterPackManager::Instance().Get(kFile, "npclist.txt", &pvData))
		return;

	CMemoryTextFileLoader textFileLoader;
	textFileLoader.Bind(kFile.Size(), pvData);

	CTokenVector TokenVector;
	for (uint32_t i = 0; i < textFileLoader.GetLineCount(); ++i)
	{
		if (!textFileLoader.SplitLineByTab(i, &TokenVector))
			continue;
		
		if (TokenVector.size() == 0)
			continue;

		if (is_number(TokenVector[0]))
		{
			uint32_t vnum = atoi(TokenVector[0].c_str());
			if (vnum > 0)
			{
				CRaceManager::Instance().RegisterRaceName(vnum, TokenVector[1].c_str());
			}
			else
			{
				CRaceManager::Instance().RegisterRaceSrcName(TokenVector[1].c_str(), TokenVector[2].c_str());
			}
		}
		else
		{
			CRaceManager::Instance().RegisterRaceSrcName(TokenVector[1].c_str(), TokenVector[2].c_str());
		}


	}


}
CharacterManagerModule:

Add:
Code:
PyObject * chrmgrLoadNPCList(PyObject* poSelf, PyObject* poArgs)
{

	CPythonCharacterManager & rkChrMgr = CPythonCharacterManager::Instance();
	rkChrMgr.LoadNPCList();

	return Py_BuildNone();;
}
and add also:
Code:
	{ "LoadNPCList", chrmgrLoadNPCList, METH_VARARGS },
and the system.py
Code:
import sys
import app
import dbg
sys.path.append("lib")

class TraceFile:
	def write(self, msg):
		dbg.Trace(msg)

class TraceErrorFile:
	def write(self, msg):
		dbg.TraceError(msg)
		dbg.RegisterExceptionString(msg)

class LogBoxFile:
	def __init__(self):
		self.stderrSave = sys.stderr
		self.msg = ""

	def __del__(self):
		self.restore()

	def restore(self):
		sys.stderr = self.stderrSave

	def write(self, msg):
		self.msg = self.msg + msg

	def show(self):
		dbg.LogBox(self.msg,"Error")

sys.stdout = TraceFile()
sys.stderr = TraceErrorFile()
#
# pack file support (must move to system.py, systemrelease.pyc)
#
import marshal
import imp
import VFSImporter
class pack_file_iterator(object):
	def __init__(self, packfile):
		self.pack_file = packfile
		
	def next(self):
		tmp = self.pack_file.readline()
		if tmp:
			return tmp
		raise StopIteration

_chr = __builtins__.chr

class pack_file(object):
	def __init__(self, filename, mode = 'rb'):
		assert mode in ('r', 'rb')
		if not VFSImporter.Exist(filename):
			raise IOError, 'No file or directory'
		self.data = VFSImporter.Get(filename)
		if mode == 'r':
			self.data=_chr(10).join(self.data.split(_chr(13)+_chr(10)))

	def __iter__(self):
		return pack_file_iterator(self)

	def read(self, len = None):
		if not self.data:
			return ''
		if len:
			tmp = self.data[:len]
			self.data = self.data[len:]
			return tmp
		else:
			tmp = self.data
			self.data = ''
			return tmp

	def readline(self):
		return self.read(self.data.find(_chr(10))+1)

	def readlines(self):
		return [x for x in self]

old_open = open
def open(filename, mode = 'rb'):
	if VFSImporter.Exist(filename) and mode in ('r', 'rb'):
		return pack_file(filename, mode)
	else:
		return old_open(filename, mode)

__builtins__.open = open
__builtins__.pack_open = pack_open = pack_file

_ModuleType = type(sys)

old_import = __import__

module_do = lambda x:None



def __hybrid_import(name,globals=None,locals=None,fromlist=None, level=-1):
	filename = name + '.py'

	if VFSImporter.Exist(filename):
		if name in sys.modules:
			dbg.Tracen('importing from sys.modules %s' % name)
			return sys.modules[name]

		dbg.Tracen('importing from pack %s' % name)

		my_module = VFSImporter.ImportAsModule(filename)
		
		if not my_module:
			dbg.TraceError('importing from pack %s failed!' % name)
		
		return my_module
	else:
		dbg.Tracen('importing from lib %s' % name)
		return old_import(name,globals,locals,fromlist, level)

def splitext(p):
	root, ext = '', ''
	for c in p:
		if c in ['/']:
			root, ext = root + ext + c, ''
		elif c == '.':
			if ext:
				root, ext = root + ext, c
			else:
				ext = c
		elif ext:
			ext = ext + c
		else:
			root = root + c
	return root, ext

def __IsCompiledFile__(sFileName): 
	sBase, sExt = splitext(sFileName) 
	sExt=sExt.lower()
	if sExt==".pyc" or sExt==".pyo": 
		return 1 
	else: 
		return 0 

def __LoadTextFile__(sFileName): 
	sText=open(sFileName,'r').read() 
	return compile(sText, sFileName, "exec") 

def __LoadCompiledFile__(sFileName): 
	kFile=open(sFileName)
	if kFile.read(4)!=imp.get_magic(): 
		raise 

	kFile.read(4) 

	kData=kFile.read() 
	return marshal.loads(kData) 

def execfile(fileName, dict):
	if __IsCompiledFile__(fileName):
		code=__LoadCompiledFile__(fileName)
	else:
		code=__LoadTextFile__(fileName)
		exec code in dict

import __builtin__
__builtin__.__import__ = __hybrid_import

# cython의 버그로... 외부 모듈의 __dict__를 수정하여, 모듈에 원래 있는 object를 수정해봐야 해당 모듈 내에서 바뀐 object가 적용되지 않는다.
#	module_do는 외부 모듈의 execfile object를 __builtin__.execfile에서 system.execfile로 치환하는 작업을 하는데 공염불이다...(외부 모듈은 __builtin__.execfile을 쓸 뿐이다.)
#	따라서 __builtin__.execfile을 system.execfile로 치환해버림...
#module_do = exec_add_module_do 
__builtin__.execfile = execfile

def GetExceptionString(excTitle):
	(excType, excMsg, excTraceBack)=sys.exc_info()
	excText=""
	excText+=_chr(10)

	import traceback
	traceLineList=traceback.extract_tb(excTraceBack)

	for traceLine in traceLineList:
		if traceLine[3]:
			excText+="%s(line:%d) %s - %s" % (traceLine[0], traceLine[1], traceLine[2], traceLine[3])
		else:
			excText+="%s(line:%d) %s"  % (traceLine[0], traceLine[1], traceLine[2])

		excText+=_chr(10)
	
	excText+=_chr(10)
	excText+="%s - %s:%s" % (excTitle, excType, excMsg)		
	excText+=_chr(10)

	return excText

def ShowException(excTitle):
	excText=GetExceptionString(excTitle)
	dbg.TraceError(excText)
	app.Abort()

	return 0

def RunMainScript(name):
	try:		
		execfile(name, __main__.__dict__)
	except RuntimeError, msg:
		msg = str(msg)

		import localeInfo
		if localeInfo.error:
			msg = localeInfo.error.get(msg, msg)

		dbg.LogBox(msg)
		app.Abort()

	except:	
		msg = GetExceptionString("Run")
		dbg.LogBox(msg)
		app.Abort()
	
import debugInfo
debugInfo.SetDebugMode(__DEBUG__)

loginMark = "-cs"

app.__COMMAND_LINE__ = __COMMAND_LINE__

RunMainScript("prototype.py")
Search:

Code:
def __LoadGameNPC():
Replace the whole function:
Code:
def __LoadGameNPC():
	chrmgr.LoadNPCList()
If your python code does not load any files with pack_open or pack.Get you can remove the pack.Get function.


:rolleyes:
01/06/2015 08:49 #Saiirex#2
Das ist der Fix für den Stuff den xGr33n und GayFox released haben?
01/06/2015 08:55 .Alpha.#3
Im Grunde schon genau genommen wenn man es richtig macht ein Fix für das entpacken über Python Allgemein
01/06/2015 09:01 grαyfox#4
Habs mir nicht genau angeguckt, aber die pack_open Funktion und das pack modul sind 2 verschiedene paar Schuhe, solltest du dich also nicht an die open Funktion gesetzt haben (habs mir ehrlich gesagt nicht angeguckt) bringt das ganze hier nichts
01/06/2015 09:26 .Alpha.#5
Quote:
Originally Posted by grαyfox View Post
Habs mir nicht genau angeguckt, aber die pack_open Funktion und das pack modul sind 2 verschiedene paar Schuhe, solltest du dich also nicht an die open Funktion gesetzt haben (habs mir ehrlich gesagt nicht angeguckt) bringt das ganze hier nichts
pack_open benutzt lustigerweise pack.Get oder wie die Funktion halt im Client des nennen wir es mal Ziels heißt ... also ist deine Aussage schon mal im Kern falsch.

Ich habe einen weg aufgezeigt bei dem keine absolut pack.Get Methode mehr benötigt wird wodurch auch die pack_open Funktion nicht mehr benötigt wird wodurch wie bereits erwähnt es nicht mehr möglich ist Dateien über Python zu entpacken.

Python Dateien werden mit dem Weg oben direkt importiert und nicht erst später in Python verarbeitet.

Die Open Funktion ist im anderen Thema sollte ein funktionierender Fix sein
01/06/2015 11:35 xGr33n#6
Nana Nova... wenn das in AoZ schon drin war, denn dort hieß das packmodul genauso, hat es nichts genuetzt, wenn nicht, nettes release :) Achso für die Person die gefragt hat ob das der fix für beides ist: Nö
€: Ich bin mir nicht ganz sicher aber soweit ich das gesehen hab' wird der Client nicht funktionieren wenn ihr die .Get Funktion rausnehmt :D
01/06/2015 12:57 .Lol#7
Quote:
€: Ich bin mir nicht ganz sicher aber soweit ich das gesehen hab' wird der Client nicht funktionieren wenn ihr die .Get Funktion rausnehmt
Deshalb hat er ja dazu geschrieben:
Quote:
If your python code does not load any files with pack_open or pack.Get you can remove the pack.Get function.
Aber ja. mit der npclist allein ist das nicht erledigt. Dazu kommen noch die locale's (locale_game, locale_interface) und vor allem die UIScripts und die loginInfo.

Die locales sollte man von der bin laden lassen(wie oben die npclist). Außerdem sollte noch execfile() komplett in die bin verlagert werden.
01/06/2015 17:12 xCPx#8
Quote:
Originally Posted by xGr33n View Post
Nana Nova... wenn das in AoZ schon drin war, denn dort hieß das packmodul genauso, hat es nichts genuetzt, wenn nicht, nettes release :) Achso für die Person die gefragt hat ob das der fix für beides ist: Nö
€: Ich bin mir nicht ganz sicher aber soweit ich das gesehen hab' wird der Client nicht funktionieren wenn ihr die .Get Funktion rausnehmt :D
Also erstmal laberst du Müll.
Sorry, aber die pack_open function an sich ist wenn man es genau nimmt ne Class in der System.py.

Code:
__builtins__.pack_open = pack_open = pack_file
Da wird das ganze definiert.
pack_file ist ne Python Class:
Code:
class pack_file(object):

	def __init__(self, filename, mode = 'rb'):
		assert mode in ('r', 'rb')
		if not pack.Exist(filename):
			raise IOError, 'No file or directory'
		self.data = pack.Get(filename)
		if mode == 'r':
			self.data=_chr(10).join(self.data.split(_chr(13)+_chr(10)))

	def __iter__(self):
		return pack_file_iterator(self)

	def read(self, len = None):
		if not self.data:
			return ''
		if len:
			tmp = self.data[:len]
			self.data = self.data[len:]
			return tmp
		else:
			tmp = self.data
			self.data = ''
			return tmp

	def readline(self):
		return self.read(self.data.find(_chr(10))+1)

	def readlines(self):
		return [x for x in self]
und darin läuft alles über die oben genannten pack.Exists und pack.Get.

nimmt man das oben genannte Beispiel und ersetzt die pack functions mit den obrigen, dann hat man keine Probleme mehr mit eurem Tool.
01/06/2015 17:54 xGr33n#9
"Achso für die Person die gefragt hat ob das der fix für beides ist: Nö"
"beides"
Das Release ändert rein gar nichts, was für das andere Script relevant ist:
[Only registered and activated users can see links. Click Here To Register...]

"soweit ich das gesehen hab' wird der Client nicht funktionieren wenn ihr die .Get Funktion rausnehmt"
die Get Funktion des Packmodules.
Dass pack_open eine Klasse ist mir bewusst. Davon abgesehen pack_open habe ich NIE angesprochen
01/06/2015 18:04 .Alpha.#10
Code:
PyObject* appLoadLocaleInterface(PyObject* poSelf, PyObject* poArgs)
{
	PyObject* dict;
	PyArg_ParseTuple(poArgs, "O!", &PyDict_Type, &dict);


	const VOID* pvData;
	CMappedFile kFile;
	std::string localePath = std::string(LocaleService_GetLocalePath());

	if (!CEterPackManager::Instance().Get(kFile, (localePath + "locale_interface.txt").c_str(), &pvData))
		return;

	CMemoryTextFileLoader textFileLoader;
	textFileLoader.Bind(kFile.Size(), pvData);

	CTokenVector TokenVector;
	for (DWORD i = 0; i < textFileLoader.GetLineCount(); ++i)
	{
		if (!textFileLoader.SplitLineByTab(i, &TokenVector))
			continue;

		if (TokenVector.size() == 0)
			continue;

		if (TokenVector.size()>=2)
		{
			PyDict_SetItemString(dict, TokenVector[0].c_str(), PyString_FromString(TokenVector[1].c_str()));
		}



	}



	return Py_BuildNone();
}
Next, es ist wie gesagt möglich das keine Dateien über pack_open oder packGet geladen werden. Aber ich gebe euch hier nicht alles was ihr da für braucht fertig nur das Wissen wie man es macht und das es eben möglich ist.

und wie bereits gesagt wegen der anderen Sache kann man immer noch open überschreiben

Code:
def custom_open(name, mode='r', buffering = 0)
    //do stuff to block access to certain files or paths here
    return __builtin__.open(path, mode, buffering)

__builtin__.open = custom_open
01/06/2015 18:54 xCPx#11
Quote:
Originally Posted by .Alpha. View Post
Code:
PyObject* appLoadLocaleInterface(PyObject* poSelf, PyObject* poArgs)
{
	PyObject* dict;
	PyArg_ParseTuple(poArgs, "O!", &PyDict_Type, &dict);


	const VOID* pvData;
	CMappedFile kFile;
	std::string localePath = std::string(LocaleService_GetLocalePath());

	if (!CEterPackManager::Instance().Get(kFile, (localePath + "locale_interface.txt").c_str(), &pvData))
		return;

	CMemoryTextFileLoader textFileLoader;
	textFileLoader.Bind(kFile.Size(), pvData);

	CTokenVector TokenVector;
	for (DWORD i = 0; i < textFileLoader.GetLineCount(); ++i)
	{
		if (!textFileLoader.SplitLineByTab(i, &TokenVector))
			continue;

		if (TokenVector.size() == 0)
			continue;

		if (TokenVector.size()>=2)
		{
			PyDict_SetItemString(dict, TokenVector[0].c_str(), PyString_FromString(TokenVector[1].c_str()));
		}



	}



	return Py_BuildNone();
}
Next, es ist wie gesagt möglich das keine Dateien über pack_open oder packGet geladen werden. Aber ich gebe euch hier nicht alles was ihr da für braucht fertig nur das Wissen wie man es macht und das es eben möglich ist.

und wie bereits gesagt wegen der anderen Sache kann man immer noch open überschreiben

Code:
def custom_open(name, mode='r', buffering = 0)
    //do stuff to block access to certain files or paths here
    return __builtin__.open(path, mode, buffering)

__builtin__.open = custom_open
Alpha das mit dem custom_open bastel ich grade direkt in der exe.

Mit ner tollen Überprüfung, dass nur .txt geladen werden können.