官术网_书友最值得收藏!

2.3 對資源的加密解密

對資源進行加密可以很好地防止資源被盜用,一般需要對游戲的圖片、模型、配置、腳本等資源進行加密,對于圖片和腳本的加密,Cocos2d-x提供了比較便捷的加密解密方法,當然也可以使用DES、3DES、AES等常用的加密算法,甚至自己設計的加密算法來對資源進行加密。

2.3.1 使用TexturePacker加密紋理

TexturePacker是非常強大的圖片打包工具,提供了強大的加密功能,在Cocos2d-x中可以通過一行簡單的代碼設置密鑰,在加載TexturePacker加密過的圖片時會自動解密,TexturePacker使用的是安全高效的xxtea算法,但美中不足的是目前只支持.pvr.ccz格式,這個格式并不建議在iOS之外的平臺使用。首先來了解一下如何加密,可以通過TexturePacker的界面工具和命令行工具進行加密,需要設置一個32位十六進制值的密鑰。在TexturePacker左側的輸出設置面板中設置紋理格式為.pvr.ccz,然后單擊Content protection旁邊的小鎖按鈕,就會彈出密鑰設置窗口(如圖2-4所示),可以在編輯框中輸入密鑰,或者單擊Create new key按鈕自動生成一個新的密鑰,Clear/Disable按鈕可以清除密碼。

圖2-4 TexturePacker加密

通過TexturePacker的命令行工具,在命令行中添加一個選項-content-protection <key>即可,使用命令行工具可以很方便地在腳本中對圖片進行批量處理。在TexturePacker的官網https://www.codeandweb.com/texturepacker/documentation有命令行工具使用的詳細介紹。

在代碼中只需要添加一行代碼,把密鑰設置進去即可。

        ZipUtils::ccSetPvrEncryptionKey(0xd8479b9f, 0xd8961025, 0x419da14a, 0x81e5d801);

2.3.2 對Lua腳本進行加密

Quick提供了一個簡單的腳本加密工具,可以在Windows和Mac系統下使用,它可以將Lua腳本編譯、加密并壓縮成一個zip包,在Cocos2d-x中也可以很方便地使用加密后的腳本,可以在github上面獲取Quick的源碼https://github.com/chukong/quick-cocos2d-x

在Quick的bin目錄下可以找到compile_scripts腳本,在Windows下是compile_scripts.bat,在Mac系統下則是compile_scripts.sh,在控制臺中運行該腳本,傳入對應的參數即可。例如,執行compile_scripts -i ..\welcome\src -o welcome.zip -e xxtea_zip -ek mykey,即可將指定目錄下的所有腳本編譯打包為zip文檔,并進行加密,如圖2-5所示。

圖2-5 加密Lua腳本

compile_scripts的選項有很多,直接輸入compile_scripts或compile_scripts -h命令即可顯示幫助說明,如圖2-6所示。常用選項的含義如下。

圖2-6 編譯腳本幫助說明

? i:指定源文件路徑。

? -o:指定輸出文件路徑。

? -p:包前綴。

? -x:指定要排除的目錄(不打包)。

? -m:編譯模式。

? -e:加密模式。

? -ek:加密密鑰,設置了加密模式之后必須設置密鑰。

? -es:加密簽名,默認值為XXTEA,意義不大。

? -ex:加密文件的擴展名(默認是.lua)。

? -c:使用指定的配置來編譯。

? -q:靜默編譯,不輸出任何信息。

編譯有以下3種模式:

? zip模式為默認模式,即將所有源碼編譯后打包成一個zip壓縮包。

? c模式會將所有源碼編譯后生成一對C的源文件和頭文件,文件中定義了存儲字節碼的數組以及相關的接口,使用生成的接口可以加載這些Lua腳本。

? files模式會將所有源碼編譯之后不進行打包,編譯后的文件會被輸出到-o選項所指定的路徑下。

加密有以下兩種模式:

? xxtea_zip模式會使用XXTEA算法加密整個zip包,需要配合zip編譯模式使用。

? xxtea_chunk模式會使用XXTEA算法加密每一個編譯后的腳本文件,默認簽名為XXTEA。

加密之后只需要在程序初始化時,調用LuaStack的setXXTEAKeyAndSign()方法設置密鑰和簽名,即可使用加密后的腳本,如果將腳本編譯后打包成一個zip壓縮包,需要調用LuaStack的loadChunksFromZIP()方法來加載壓縮包中的腳本。在loadChunksFromZIP()方法中會判斷zip包是否經過了XXTEA加密,如果是則進行解密,并取出里面的文件,逐個調用luaLoadBuffer()方法加載腳本文件。在luaLoadBuffer()方法中會判斷要加載的腳本是否經過了XXTEA加密,如是則進行解密,然后載入Lua虛擬機中。

        bool AppDelegate::applicationDidFinishLaunching()
        {
            …
            LuaStack *pStack = pEngine->getLuaStack();
            //如果設置了 -e和 -ek需要調用setXXTEAKeyAndSign設置密鑰
            //pStack->setXXTEAKeyAndSign("mypassword", strlen("mypassword"));
            //如果設置了 -e和 -ek -es需要調用setXXTEAKeyAndSign設置密鑰和簽名
            pStack->setXXTEAKeyAndSign("mypassword", strlen("mypassword"),
            "mysign", strlen("mysign"));
            pStack->loadChunksFromZip("res/game.zip");
            pStack->executeString("require 'main'");
            return true;
        }

在某些情況下,將Lua腳本編譯會導致一些問題,如iOS下的兼容性問題,在另外一些情況下將腳本編譯好打包成zip也會導致一些其他的問題,如無法使用熱更新。

這種情況下希望能夠不編譯腳本、不打包成zip,只是加密腳本,那么應該怎么做呢?可以使用cocos.py來打包,它支持在打包的時候加密且不編譯Lua腳本,可以輸入cocos compile-h命令來查看cocos.py編譯相關的幫助信息,如圖2-7所示。

圖2-7 cocos.py的幫助信息

在編譯的時候使用--compile-script選項,指定參數為0可以關閉Lua和JS腳本的編譯,而使用--lua-encrypt選項可以開啟Lua腳本的加密,然后結合--lua-encrypt-key選項可以設置密鑰。

在打包時加密可以大大簡化操作流程,正常而言每次打包都需要手動將腳本加密,然后將源碼刪除,只保留加密后的腳本,打包結束之后又要撤銷回來,因為需要繼續開發,所以在開發時需要對Lua源碼進行編輯。而cocos.py則將我們從這個煩瑣的流程中解放了出來,只需要在打包的時候指定一下參數就可以了。

2.3.3 自定義Lua腳本加密解密

前面介紹的兩種都是用通用的方法進行加密,然后使用Cocos2d-x內置的方法進行解密,而且有一定的局限性,接下來介紹如何在Cocos2d-x中進行自定義的加密解密。在Cocos2d-x中自定義加密解密最關鍵的并不是使用何種方法來加密解密,而是在什么地方執行解密操作,我們需要盡量讓業務邏輯層不知道解密操作的存在,以及盡量不修改引擎。對配置文件等資源,可以對加載配置操作進行一個簡單的封裝,在FileUtils的getData之后執行解密,再解析配置。大部分的資源都可以通過簡單的封裝之后,實現自動解密。

對Lua腳本,可以在LuaEngine中設置一個lua_loader回調函數來實現Lua腳本的加載規則,當Lua每次require一個腳本時,就會調用設置的lua_loader回調方法,在lua_loader回調中需要執行加載腳本以及腳本的功能,可以在加載腳本之后,執行腳本之前對加密后的腳本進行解密。Cocos2d-x默認的lua_loader回調是cocos2dx_lua_loader()函數,位于Cocos2dxLuaLoader.cpp中,可以定義一個my_lua_loader()函數,在函數中的stack->luaLoadBuffer之前實現解密的功能,把解密后的腳本內容傳入,代碼大致如下。

        extern "C"
        {
          int cocos2dx_lua_loader(lua_State *L)
          {
              static const std::string BYTECODE_FILE_EXT   = ".luac";
              static const std::string NOT_BYTECODE_FILE_EXT = ".lua";

              std::string filename(luaL_checkstring(L, 1));
              size_t pos = filename.rfind(BYTECODE_FILE_EXT);
              if (pos ! = std::string::npos)
              {
                  filename = filename.substr(0, pos);
              }
              else
              {
                  pos = filename.rfind(NOT_BYTECODE_FILE_EXT);
                  if (pos == filename.length() - NOT_BYTECODE_FILE_EXT.length())
                  {
                    filename = filename.substr(0, pos);
                  }
              }

              pos = filename.find_first_of(".");
              while (pos ! = std::string::npos)
              {
                  filename.replace(pos, 1, "/");
                  pos = filename.find_first_of(".");
              }

              //search file in package.path
              unsigned char* chunk = nullptr;
              ssize_t chunkSize = 0;
              std::string chunkName;
              FileUtils* utils = FileUtils::getInstance();

              lua_getglobal(L, "package");
              lua_getfield(L, -1, "path");
              std::string searchpath(lua_tostring(L, -1));
              lua_pop(L, 1);
              size_t begin = 0;
              size_t next = searchpath.find_first_of("; ", 0);
              do
              {
                  if (next == std::string::npos)
                    next = searchpath.length();
                  std::string prefix = searchpath.substr(begin, next);
                  if (prefix[0] == '.' && prefix[1] == '/')
                  {
                    prefix = prefix.substr(2);
                  }

                  pos = prefix.find("? .lua");
                  chunkName=prefix.substr(0, pos) +filename+BYTECODE_FILE_EXT;
                  if (utils->isFileExist(chunkName))
                  {
                    chunk = utils->getFileData(chunkName.c_str(), "rb", &chunkSize);
                    break;
                  }
                  else
                  {
                    chunkName = prefix.substr(0, pos) + filename + NOT_BYTECODE_
                    FILE_EXT;
                    if (utils->isFileExist(chunkName))
                    {
                        chunk = utils->getFileData(chunkName.c_str(), "rb",
                        &chunkSize);
                        break;
                    }
                  }

                  begin = next + 1;
                  next = searchpath.find_first_of("; ", begin);
              } while (begin < (int)searchpath.length());

              if (chunk)
              {
                  LuaStack* stack = LuaEngine::getInstance()->getLuaStack();
                  //在這里添加解密的代碼
                  my_decrypt_fun(chunk, chunkSize);
                  stack->luaLoadBuffer(L, (char*)chunk, (int)chunkSize,
                  chunkName.c_str());
                  free(chunk);
              }
              else
              {
                  CCLOG("can not get file data of %s", chunkName.c_str());
                  return 0;
              }

              return 1;
          }
        }

需要注意的是,只有在Lua中執行require,才會回調到設置的lua-Loader函數,如果在C++中直接調用executeScriptFile是不會執行到lua-Loader回調的

2.3.4 自定義圖片加密解密

對圖片資源的解密要稍微麻煩一些,由于Cocos2d-x中所有的紋理都緩存在TextureCache中,所以可以在使用紋理之前手動將紋理加載并放到TextureCache中,這樣后面所有使用紋理的地方都不需要有任何改動,大部分游戲在進入場景之前都會預加載場景中的資源,將這個操作放在預加載這里是最合適的。具體的方法是先調用FileUtils的getData,獲取加密后的圖片,然后對內容進行解密,創建一個Image對象,將解密后的內容傳入到Image的initWithImageData()方法中,最后調用TextureCache的addImage()方法將Image對象添加到TextureCache中(缺點是不能使用TextureCache的異步加載,但是可以自己編寫多線程進行異步加載),代碼大致如下。

        bool loadEncryptTexture(const std::string& file)
        {
            auto fullPath = FileUtils::getInstance()->fullPathForFilename(file);
            auto data = FileUtils::getInstance()->getDataFromFile(fullPath);
            //使用自己的解密函數進行解密
            my_decrypt_fun(data.getBytes(), data.getSize());
            Image* img = new Image();
            if (! img->initWithImageData(data.getBytes(), data.getSize()))
            {
              img->release();
              return false;
            }
            TextureCache::getInstance()->addImage(img, fullPath);
            return true;
        }

由于所有的文件都要通過FileUtils的getDataFromFile()方法加載(筆者曾嘗試了各種方法,都難以在不修改引擎源碼的前提下改寫getDataFromFile()方法,就算實現了也比直接修改FileUtils的源碼更加難以維護),所以可以在FileUtils中添加少量代碼來實現,這樣就需要修改FileUtils、FileUtilsWin32以及FileUtilsAndroid的getDataFromFile()方法。

首先在FileUtils的頭文件中定義一個接口類FileDelegate,接口類中提供一個文件處理函數,傳入打開的文件以及文件的Data對象,可以在處理函數中對Data執行解密處理,處理完之后返回給FileUtils。

        class CC_DLL FileDelegate : public Ref
        {
        public:
            FileDelegate() {}
            virtual ~FileDelegate() {}

            virtual Data fileProcess(const std::string& file, Data& data) = 0;
        };

接下來將FileDelegate設置為FileUtils的保護成員變量,并為FileUtils添加一個setFileDelegate()方法,然后在FileUtils的構造函數和析構函數中對該變量進行初始化以及釋放。

        //在頭文件中為FileUtils添加setFileDelegate()方法
        inline void setFileDelegate(FileDelegate* fileDelegate)
        {
            CC_SAFE_RELEASE_NULL(_fileDelegate);
            _fileDelegate = fileDelegate;
            CC_SAFE_RETAIN(_fileDelegate);
        }
        //在源文件中調整FileUtils的構造函數和析構函數
        FileUtils::FileUtils()
            : _writablePath("")
            , _fileDelegate(nullptr)
        {
        }

        FileUtils::~FileUtils()
        {
            CC_SAFE_RELEASE_NULL(_fileDelegate);
        }

最后調整所有FileUtils的getDataFromFile()方法,添加一個簡單的判斷,如果_fileDelegate不為空,則將獲取的文件傳給_fileDelegate進行處理,代碼如下。

        Data FileUtils::getDataFromFile(const std::string& filename)
        {
            if (_fileDelegate)
            {
              return   _fileDelegate->fileProcess(filename,   getData(filename,
        false));
            }
            return getData(filename, false);

        }

最后可以在自己的源碼中,繼承FileDelegate實現一個MyFileDelegate,在fileProcess()方法中實現對指定文件的解密處理,將MyFileDelegate設置到FileUtils中即可生效。我們可以使用DES、3DES、AES、XXTEA(位于引擎的external/xxtea目錄下)等常用的加密算法,也可以使用自己實現的簡單加密算法。自己實現加密算法可以靈活地使用異或、交換等手段,天馬行空地制定規則。例如,下面這個自定義的加密算法,會將數據的前256個字節使用指定的Key進行加密,解密也是使用這個方法。

      void myencrypt(char* data, unsigned int len, int key)
      {
          unsigned int maxLen = 256 / sizeof(int);
          len /= sizeof(int);
          for (unsigned int i = 0; i < len && i < maxLen; ++i)
          {
            *(int*)data ^= key;
            data += sizeof(int);
          }
      }

下面這段代碼驗證了這個簡單的加密算法,隨便設置了一個加密密鑰,將一段文本進行加密,然后輸出加密后的密文,接下來解密,并輸出解密后的明文。

        char str[1024];
        memset(str, 0, sizeof(str));
        strcpy(str, "hello world, ~~~~~~~~~~, !!!!!!!");
        int key = 1314666;
        unsigned int len = strlen(str);
        myencrypt(str, len, key);
        CCLOG("%s", str);
        myencrypt(str, len, key);
        CCLOG("%s", str);

運行這段代碼會輸出以下結果:

        jxl/cocp, Jqj~qj~qj, J.5! K.5!
        hello world, ~~~~~~~~~~, !!!!!!!

接下來演示一下如何將這個自定義的加密解密應用到Cocos2d-x中。首先需要編寫一段簡單的程序對要加密的文件進行加密,假設將游戲中所有的png都進行了加密,可以在MyFileDelegate中只對png文件進行解密,代碼如下所示。

        class MyFileDelegate : public FileDelegate
        {
            virtual Data fileProcess(const std::string& file, Data& data)
            {
              if (FileUtils::getInstance()->getFileExtension(file) == ".png")
              {
                  myencrypt((char*)data.getBytes(), data.getSize(), 1314666);
              }
              return data;
            }
        };

然后調用FileUtils的setFileDelegate()方法將MyFileDelegate的對象設置進去即可。

      MyFileDelegate* dlg = new MyFileDelegate();
      dlg->autorelease();
      FileUtils::getInstance()->setFileDelegate(dlg);
主站蜘蛛池模板: 洛川县| 通海县| 白山市| 山东省| 离岛区| 涟水县| 乐昌市| 庐江县| 伊金霍洛旗| 明溪县| 甘德县| 大洼县| 都兰县| 逊克县| 东乡族自治县| 临洮县| 扶余县| 萝北县| 荥经县| 鄂托克前旗| 渝北区| 平泉县| 莱西市| 中牟县| 越西县| 台东县| 平安县| 拜城县| 津南区| 东台市| 兴化市| 镇平县| 樟树市| 信阳市| 滁州市| 海林市| 中西区| 灌阳县| 祁东县| 开封市| 保定市|