1. 程式人生 > >cocos2dxandroid執行Luac編譯後的lua程式碼

cocos2dxandroid執行Luac編譯後的lua程式碼

執行環境

win7 64

cocos2d-2.1rc0-x-2.1.2

lua 5.1

通常我們編寫好的lua程式碼都是明文形式,誰都可以檢視修改,為了防止自己的勞動成果不被別人輕易的盜取,可以使用luac(lua庫中自帶)對其進行加密,轉換為二進位制檔案。這樣lua程式碼就無法直接檢視,但是這裡會有一個問題:在windows下能夠很好的執行,在android上就會黑屏,提示錯誤:
[LUA ERROR] binary string: unexpected end in precompiled chunk


追根溯源


  在bool AppDelegate::applicationDidFinishLaunching()中檢視lua載入程式碼:

01.#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) 02.CCString* pstrFileContent = CCString::createWithContentsOfFile( "program/main.lua" ); 03.if (pstrFileContent) 04.{ 05.pEngine->executeString(pstrFileContent->getCString()); 06.} 07.#else        08.std::string path = CCFileUtils::sharedFileUtils()->fullPathForFilename(
"program/main.lua" );
09.pEngine->addSearchPath( path.substr( 0, path.find_last_of( "/" ) ).c_str() ); 10.pEngine->executeScriptFile( path.c_str() ); 11.#endif
這裡executeString()載入lua檔案,繼續檢視原始碼發現真正幹活的是:
1.LUALIB_API int luaL_loadbuffer (lua_State *L, const char *buff, size_t size, 2.const char
*name) {
3.LoadS ls; 4.ls.s = buff; 5.ls.size = size; 6.return lua_load(L, getS, &ls, name); 7.}
在回頭看看函式executeScriptFile()的執行過程: 01.LUALIB_API int luaL_loadfile (lua_State *L, const char *filename) { 02.LoadF lf; 03.int status, readstatus; 04.int c; 05.int fnameindex = lua_gettop(L) + 1/* index of filename on the stack */ 06.lf.extraline = 0; 07.if (filename == NULL) { 08.lua_pushliteral(L, "=stdin"); 09.lf.f = stdin; 10.} 11.else { 12.lua_pushfstring(L, "@%s", filename); 13.lf.f = fopen(filename, "r"); 14.if (lf.f == NULL) return errfile(L, "open", fnameindex); 15.} 16.c = getc(lf.f); 17.if (c == '#') {  /* Unix exec. file? */ 18.lf.extraline = 1; 19.while ((c = getc(lf.f)) != EOF && c != ' 20.') ;  /* skip first line */ 21.if (c == ' 22.') c = getc(lf.f); 23.} 24.if (c == LUA_SIGNATURE[0] && filename) {  /* binary file? */ 25.lf.f = freopen(filename, "rb", lf.f);  /* reopen in binary mode */ 26.if (lf.f == NULL) return errfile(L, "reopen", fnameindex); 27./* skip eventual `#!...' */ 28.while ((c = getc(lf.f)) != EOF && c != LUA_SIGNATURE[0]) ; 29.lf.extraline = 0; 30.} 31.ungetc(c, lf.f); 32.status = lua_load(L, getF, &lf, lua_tostring(L, -1)); // 關鍵,與luaL_loadbuffer()中的一樣 33.readstatus = ferror(lf.f); 34.if (filename) fclose(lf.f);  /* close file (even in case of errors) */ 35.if (readstatus) { 36.lua_settop(L, fnameindex);  /* ignore results from `lua_load' */ 37.return errfile(L, "read", fnameindex); 38.} 39.lua_remove(L, fnameindex); 40.return status; 41.} 注意看程式碼的中文註釋部分,這裡就能解釋為什麼用luac編譯的檔案在window下面可以好好地執行(IOS上面也OK),而android上就不能。luaL_loadfile()中通過c == LUA_SIGNATURE[0]判斷lua檔案是否加密,如果是,則重新以“rb”方式開啟。

解決辦法

修改scriptingluacocos2dx_supportCocos2dxLuaLoader.cpp中的loader_Android()函式

修改前:

01.int loader_<a href= target="_blank" class="keylink">Android</a>(lua_State *L) 02.{ 03.std::string filename(luaL_checkstring(L, 1)); 04.filename.append(".lua"); 05. 06.CCString* pFileContent = CCString::createWithContentsOfFile(filename.c_str()); 07. 08.if (pFileContent) 09.{ 10.if (luaL_loadstring(L, pFileContent->getCString()) != 0) 11.{ 12.luaL_error(L, "error loading module %s from file %s : 13.%s", 14.lua_tostring(L, 1), filename.c_str(), lua_tostring(L, -1)); 15.} 16.} 17.else 18.{ 19.CCLog("can not get file data of %s", filename.c_str()); 20.} 21. 22.return 1; 23.}
修改後: 01.int loader_Android(lua_State* L) 02.{ 03.unsigned char* pData = nullptr; 04.unsigned long size = 0; 05. 06./* modify for read lua script from bytecode */ 07.std::string filename(luaL_checkstring(L, 1)); 08.std::string rel_name = filename.append(".lua"); 09. 10.pData = CCFileUtils::sharedFileUtils()->getFileData(rel_name.c_str(), "rb", &size); 11.if (pData) { 12.if (luaL_loadbuffer(L, (char*)pData,size,rel_name.c_str()) != 0 ) { 13.luaL_error(L, "error loading module s from file s :s", 14.lua_tostring(L, 1), rel_name.c_str(), lua_tostring(L, -1)); 15.} 16.} 17.else { 18.CCLog("can not get file data of %s", filename.c_str()); 19.} 20. 21.CC_SAFE_DELETE_ARRAY(pData); 22. 23.return 1; 24.}
另外,由於在cpp中第一次載入lua並沒有呼叫loader_Android(),這是因為uaL_loadbuffer只會把檔案載入成為一個chunk,而不會執行該chunk,所以還要在加一條呼叫語名lua_pcall,如下: 01.int loader_Android(lua_State* L) 02.{ 03.unsigned char* pData = nullptr; 04.unsigned long size = 0; 05. 06./* modify for read lua script from bytecode */ 07.std::string filename(luaL_checkstring(L, 1)); 08.std::string rel_name = filename.append(".lua"); 09. 10.pData = CCFileUtils::sharedFileUtils()->getFileData(rel_name.c_str(), "rb", &size); 11.if (pData) { 12.if (luaL_loadbuffer(L, (char*)pData,size,rel_name.c_str()) != 0 || lua_pcall(L, 0, LUA_MULTRET, 0) ) { // 修改處 13.luaL_error(L, "error loading module s from file s :s", 14.lua_tostring(L, 1), rel_name.c_str(), lua_tostring(L, -1)); 15.} 16.} 17.else { 18.CCLog("can not get file data of %s", filename.c_str()); 19.} 20. 21.CC_SAFE_DELETE_ARRAY(pData); 22. 23.return 1; 24.} 或者我們可以偷雞一下, 1.pEngine->executeString("require "program/main""); // 注意這裡的"

最後我們的AppDelegate::applicationDidFinishLaunching()大概就是這樣的,

01.const char* luaFile = "program/main.lua"; 02.#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) 03.std::string path = CCFileUtils::sharedFileUtils()->fullPathForFilename(luaFile); 04.CCLog("path = %s", path.c_str()); 05.std::string subPath = path.substr(0, path.find_last_of("/")); 06.CCLog("sub path = %s", subPath.c_str()); 07. 08.pEngine->addSearchPath(subPath.c_str()); 09. 10.std::vector<std::string> searchPaths = CCFileUtils::sharedFileUtils()->getSearchPaths(); 11.searchPaths.insert(searchPaths.begin(), subPath); 12.CCFileUtils::sharedFileUtils()->setSearchPaths(searchPaths); 13. 14.pEngine->executeString("require "program/main""); // 注意這裡的" 15.#else        16.std::string path = CCFileUtils::sharedFileUtils()->fullPathForFilename(luaFile); 17.pEngine->addSearchPath( path.substr( 0, path.find_last_of( "/" ) ).c_str() ); 18.pEngine->executeScriptFile( path.c_str() ); 19.#endif

注:這裡筆者偷雞了一下。