注:本文基於Quick-cocos2dx-3.3版本編寫php
在前一篇文章中介紹了代碼加密,加密方式是XXTEA。對於資源文件來講,一樣可使用XXTEA來加密,所以以前那套加密模塊能夠通用了。python
加密腳本:compile_pack_files.bat、compile_pack_files.sh
使用方法和前一篇的腳本差很少android
考慮到不是否是全部的資源都須要加密,因此這裏不使用這個腳本。ios
涉及文件目錄:項目根目錄git
首先咱們定義一個php文件,裏面定義了加密所須要的參數github
<?php ini_set('memory_limit','1024M'); return array( 'src' => 'res', 'output' => 'packres', 'prefix' => '', 'excludes' => '', 'pack' => 'files',//files or zip 'key' => 'ilovecocos2dx', 'sign' => 'XXTEA', 'whitelists' => 'jpg,png,tmx,plist', ); ?>
注意,whitelists這個參數是官方版本沒有的shell
將文件保存爲PackRes.php,放在項目根目錄(res、src同級目錄)windows
涉及文件目錄:引擎目錄/quick/bin/lib、引擎目錄/quick/bin/lib/quickxcode
由於增長了參數whitelists,因此這裏須要修改加密的腳本。
涉及的文件有兩個,/quick/bin/lib/pack_files.php和/quick/bin/lib/quick/FilesPacker.phpapp
能夠看到array裏面定義了全部參數,往array的最後加上新增的參數whitelists
//pack_files.php <?php //... $options = array( //... //... array('c', 'config', 1, null, 'load options from config file'), array('q', 'quiet', 0, false, 'quiet'), array('w', 'whitelists', 1, null, 'whitelists extension, use "," to splite array, example "jpg,png"'), ); //...
定位到validateConfig方法,加入對whitelist的解析
//FilesPacker.php <?php //... class FilesPacker { //... function validateConfig() { //... if (!empty($this->config['excludes'])) { $excludes = explode(',', $this->config['excludes']); array_walk($excludes, function($value) { return trim($value); }); $this->config['excludes'] = array_filter($excludes, function($value) { return !empty($value); }); } else { $this->config['excludes'] = array(); } //--------------add code begin-------------- if (!empty($this->config['whitelists'])) { $whitelists = explode(',', $this->config['whitelists']); array_walk($whitelists, function($value) { return trim($value); }); $this->config['whitelists'] = array_filter($whitelists, function($value) { return !empty($value); }); } //----------add code end-------------- if ($this->config['pack'] != self::COMPILE_ZIP && $this->config['pack'] != self::COMPILE_FILES && $this->config['pack'] != self::COMPILE_C ) { printf("ERR: invalid pack mode %s\n", $this->config['pack']); return false; }
定位到prepareForPack方法,加入whitelist的文件過濾
protected function prepareForPack(array $files) { //... foreach ($this->config['excludes'] as $key => $exclude) { if (substr($moduleName, 0, strlen($exclude)) == $exclude) { unset($files[$key]); $skip = true; break; } } if ($skip) continue; //--------------add code begin-------------- if (!empty($this->config['whitelists'])) { $isDirty = false; foreach ($this->config['whitelists'] as $key => $whitelist) { if (end(explode('SPLIT_CHAR', $moduleName)) == $whitelist) { $isDirty = true; break; } } if (!$isDirty) { unset($files[$key]); continue; } } //--------------add code end-------------- $bytesName = 'lua_m_' . strtolower(str_replace(array('.', '-'), '_', $moduleName)); //...
仍是上個教程的腳本,此次加了packRes方法
#coding=utf-8 #!/usr/bin/python import os import os.path import sys, getopt import subprocess import shutil import time, datetime import platform from hashlib import md5 import hashlib import binascii def removeDir(dirName): if not os.path.isdir(dirName): return filelist=[] filelist=os.listdir(dirName) for f in filelist: filepath = os.path.join( dirName, f ) if os.path.isfile(filepath): os.remove(filepath) elif os.path.isdir(filepath): shutil.rmtree(filepath,True) def initEnvironment(): global APP_ROOT #工程根目錄 global APP_ANDROID_ROOT #安卓根目錄 global QUICK_ROOT #引擎根目錄 global QUICK_BIN_DIR #引擎bin目錄 global APP_RESOURCE_ROOT #生成app的資源目錄 global APP_RESOURCE_RES_DIR #資源目錄 global APP_BUILD_USE_JIT #是否使用jit global PHP_NAME #php global SCRIPT_NAME #執行腳本文件名 global BUILD_PLATFORM #生成app對應的平臺 SYSTEM_TYPE = platform.system() #當前操做系統 APP_ROOT = os.getcwd() #當前目錄 APP_ANDROID_ROOT = APP_ROOT + "/frameworks/runtime-src/proj.android" QUICK_ROOT = os.getenv('QUICK_V3_ROOT') if QUICK_ROOT == None: #quick引擎目錄未指定,可手動指定路徑或者運行引擎目錄下相應腳本 print "QUICK_V3_ROOT not set, please run setup_win.bat/setup_mac.sh in engine root or set QUICK_ROOT path" return False if(SYSTEM_TYPE =="Windows"): QUICK_BIN_DIR = QUICK_ROOT + "quick/bin" PHP_NAME = QUICK_BIN_DIR + "/win32/php.exe" #windows BUILD_PLATFORM = "android" #windows dafault build android SCRIPT_NAME = "/compile_scripts.bat" else: PHP_NAME = "php" BUILD_PLATFORM = "ios" #mac default build ios QUICK_BIN_DIR = QUICK_ROOT + "/quick/bin" #mac add '/' SCRIPT_NAME = "/compile_scripts.sh" if(BUILD_PLATFORM =="ios"): APP_BUILD_USE_JIT = False APP_RESOURCE_ROOT = APP_ROOT + "/Resources" APP_RESOURCE_RES_DIR = APP_RESOURCE_ROOT + "/res" else: APP_BUILD_USE_JIT = True APP_RESOURCE_ROOT = APP_ANDROID_ROOT + "/assets" #default build android APP_RESOURCE_RES_DIR = APP_RESOURCE_ROOT + "/res" print 'App root: %s' %(APP_ROOT) print 'App resource root: %s' %(APP_RESOURCE_ROOT) return True def compileScriptFile(compileFileName, srcName, compileMode): scriptDir = APP_RESOURCE_RES_DIR + "/code/" if not os.path.exists(scriptDir): os.makedirs(scriptDir) try: scriptsName = QUICK_BIN_DIR + SCRIPT_NAME srcName = APP_ROOT + "/" + srcName outputName = scriptDir + compileFileName args = [scriptsName,'-i',srcName,'-o',outputName,'-e',compileMode,'-es','XXTEA','-ek','ilovecocos2dx'] if APP_BUILD_USE_JIT: args.append('-jit') proc = subprocess.Popen(args, shell=False, stdout = subprocess.PIPE, stderr=subprocess.STDOUT) while proc.poll() == None: outputStr = proc.stdout.readline() print outputStr, print proc.stdout.read(), except Exception,e: print Exception,":",e def packRes(): removeDir(APP_ROOT + "/packres/") #--->刪除舊加密資源 scriptName = QUICK_BIN_DIR + "/lib/pack_files.php" try: args = [PHP_NAME, scriptName, '-c', 'PackRes.php'] proc = subprocess.Popen(args, shell=False, stdout = subprocess.PIPE, stderr=subprocess.STDOUT) while proc.poll() == None: print proc.stdout.readline(), print proc.stdout.read() except Exception,e: print Exception,":",e if __name__ == '__main__': isInit = initEnvironment() if isInit == True: #加密資源 packRes() #將src目錄下的全部腳本加密打包成game.zip compileScriptFile("game.zip", "src", "xxtea_zip")
使用方法:
因爲涉及到的資源格式比較多,每一個涉及到的類修改讀取文件的方式太麻煩。而如今cocos2dx3.x版本統一了資源讀取的接口,因此只須要修改讀取文件的接口便可。
具體步驟:
二、引用文件,也就是讓編譯器知道xxtea文件在哪
打開vs,打開lubcocos2d項目,展開base,把xxtea兩個文件拖到base裏面便可
打開frameworks/cocos2d-x/cocos/Android.mk文件
在LOCAL_SRC_FILES中加入xxtea.cpp文件
#Android.mk #... LOCAL_SRC_FILES := \ cocos2d.cpp \ #... base/ObjectFactory.cpp \ base/xxtea.cpp \ #加入此項 renderer/CCBatchCommand.cpp \ #...
打開xcode,打開cocos2dlib.xcodeproj,展開base,把xxtea兩個文件拖到base裏面便可
由於避免和引擎舊的接口混淆,因此這裏是新增兩個方法而不是修改舊的方法
頭文件CCFileUtils.h加入兩個public方法聲明
//CCFileUtils.h //... class CC_DLL FileUtils { public: //... static unsigned char* getDecryptFileData(const char* pszFileName, const char* pszMode, unsigned long * pSize); static Data getDecryptDataFromFile(const std::string& filename); protected: //...
Cpp文件CCFileUtils.cpp中加入頭文件和方法定義
//CCFileUtils.cpp //... #include "xxtea/xxtea.h" //.. Data FileUtils::getDecryptDataFromFile(const std::string &filename) { unsigned long sz; unsigned char * buf = FileUtils::getDecryptFileData(filename.c_str(), "rb", &sz); if (!buf) { return Data::Null; } Data data; data.fastSet(buf, sz); return data; } unsigned char* FileUtils::getDecryptFileData(const char* pszFileName, const char* pszMode, unsigned long * pSize) { ssize_t size; unsigned char* buf = FileUtils::getInstance()->getFileData(pszFileName, pszMode, &size); if (NULL == buf || size<1) return NULL; const char *xxteaKey = "ilovecocos2dx"; int xxteaKeyLen = strlen(xxteaKey); const char *xxteaSign = "XXTEA"; int xxteaSignLen = strlen(xxteaSign); unsigned char* buffer = NULL; bool isXXTEA = true; for (int i = 0; isXXTEA && i<xxteaSignLen && i<size; ++i) { isXXTEA = buf[i] == xxteaSign[i]; } if (isXXTEA) { // decrypt XXTEA xxtea_long len = 0; buffer = xxtea_decrypt( buf + xxteaSignLen, (xxtea_long)size - (xxtea_long)xxteaSignLen, (unsigned char*)xxteaKey, (xxtea_long)xxteaKeyLen, &len); delete[]buf; buf = NULL; size = len; } else { buffer = buf; } if (pSize) *pSize = size; return buffer; }
圖片文件
//CCImage.cpp //目錄:frameworks/cocos2d-x/cocos/platform/ //... bool Image::initWithImageFile(const std::string& path) { //... SDL_FreeSurface(iSurf); #else //修改此處便可 Data data = FileUtils::getInstance()->getDecryptDataFromFile(_filePath); if (!data.isNull()) { //... }
xml文件
在windows/android平臺下,xml解析使用CCSAXParser,在ios平臺下則是用系統類NSDictionary來解析,因此這裏要修改兩處地方
對於windows/android平臺
//CCSAXParser.cpp //目錄:frameworks/cocos2d-x/cocos/platform/ //... bool SAXParser::parse(const std::string& filename) { bool ret = false; //修改此處便可 Data data = FileUtils::getInstance()->getDecryptDataFromFile(filename); if (!data.isNull()) { ret = parse((const char*)data.getBytes(), data.getSize()); } return ret; } //...
對於ios平臺
//CCFileUtils-apple.mm //frameworks/cocos2d-x/cocos/platform/apple //... ValueMap FileUtilsApple::getValueMapFromFile(const std::string& filename) { std::string fullPath = fullPathForFilename(filename); //註釋下面兩句代碼 // NSString* path = [NSString stringWithUTF8String:fullPath.c_str()]; // NSDictionary* dict = [NSDictionary dictionaryWithContentsOfFile:path]; //------add code begin------- unsigned long fileSize = 0; unsigned char* pFileData = FileUtils::getDecryptFileData(fullPath.c_str(), "rb", &fileSize); NSData *data = [[[NSData alloc] initWithBytes:pFileData length:fileSize] autorelease]; delete []pFileData; NSPropertyListFormat format; NSString *error; NSMutableDictionary *dict = (NSMutableDictionary *)[ NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&error]; //------add code end----------- ValueMap ret; if (dict != nil) { for (id key in [dict allKeys]) { id value = [dict objectForKey:key]; addValueToDict(key, value, ret); } } return ret; }
至此資源加密已經完成,整體來講分兩個步驟,一個是資源加密生成,另外一個是資源加密讀取。因爲涉及跨平臺文件讀取,因此修改後須要在各個平臺測試資源是否能正確讀取
資源文件見:https://github.com/chenquanjun/Cocos2dxResourceEncrypt