一、前言
Cocos Creator 打包后的素材資源寄啼,如:圖片逮光,聲音等。默認(rèn)是保持原始格式墩划,只要遇到破解黨涕刚,那么他們極有可能很簡(jiǎn)單就直接獲取到這部分素材資源。
針對(duì)這個(gè)問(wèn)題乙帮,大部分同學(xué)都會(huì)有一種資源加密的需求杜漠,即對(duì)打包后的資源進(jìn)行加密,讓破解黨 不那么容易 獲取到資源。
由此驾茴,引出了很多關(guān)于資源加密一些訴求盼樟,包括但不限于:
- https://forum.cocos.org/t/creator/46017
- https://forum.cocos.org/t/topic/90094
- https://forum.cocos.org/t/cocos-creator/58620
- https://forum.cocos.org/t/web/89123
- ...
但是,你可能發(fā)現(xiàn)锈至,通篇下來(lái)晨缴,可能并沒(méi)有一個(gè)完整的流程方案。
- 或是過(guò)于注重怎么加解密
- 或是僅支持圖片資源裹赴,其他資源沒(méi)涉及
- 或是僅涉及到部分平臺(tái)喜庞,比如:只關(guān)注了原生平臺(tái)
- 或是需要侵入到項(xiàng)目代碼,需要使用指定的資源加載器
(鑒于目前的搜索結(jié)果)總結(jié)下來(lái)棋返,開(kāi)源出來(lái)的好像還沒(méi)有一整套Cocos Creator資源加解密流程方案的樣子延都。
二、Cocos Creator Build Encrypt 方案介紹
這次為大家?guī)?lái)一種 Cocos Creator 「無(wú)侵入」 「全資源支持」 「跨平臺(tái)」 「資源處理流」 方案睛竣。
以下為我關(guān)于上述四個(gè)加粗特性的定義:
- 「無(wú)侵入」:使用此方案晰房,開(kāi)發(fā)者只需要針對(duì) Cocos Creator 構(gòu)建后的輸出目錄 進(jìn)行執(zhí)行指定命令,即完成資源加密射沟。 實(shí)際游戲項(xiàng)目不需要加入/刪除/修改代碼等其他操作殊者,全程無(wú)侵入。
- 「全資源支持」:此方案可以對(duì) Cocos Creator 引擎本身所支持的 所有資源文件(如:.txt验夯,.json, .png, .mp3, .fnt等等)進(jìn)行加密 猖吴,并且依舊「無(wú)侵入」。
- 「跨平臺(tái)」:此方案可以針對(duì)不同版本的 Cocos Creator 進(jìn)行單獨(dú)適配挥转,并且可以對(duì)每個(gè) Cocos Creator 支持發(fā)布的所有平臺(tái)進(jìn)行單獨(dú)適配海蔽。
- 「資源處理流」:使用此方案,你可以對(duì)資源進(jìn)行包括但不限于 加密 绑谣、 壓縮 等處理流党窜。
當(dāng)然,作為一種方案借宵,目前上述四個(gè)特性可能還過(guò)于抽象幌衣,因此,為了支撐整個(gè)方案壤玫,我準(zhǔn)備了一個(gè)示例 CocosCreator-Build-Encrypt 豁护,作為大家的參考 :
三、Cocos Creator Build Encrypt 方案實(shí)現(xiàn)原理
3.1 原始資源加載 VS 加密資源加載
原始資源的加載欲间,其實(shí)可以簡(jiǎn)化為如下步驟:
運(yùn)行時(shí):讀取原始資源 -> 生成資源對(duì)象
而加密資源的加載择镇,則是在上述基礎(chǔ)上,加入額外的步驟括改,整個(gè)流程大概如下:
運(yùn)行前:讀取原始資源 -> 生成加密內(nèi)容 -> 保存到加密文件
運(yùn)行時(shí):讀取加密文件 -> 解密加密內(nèi)容 -> 生成資源對(duì)象
原理大家可能都有所了解,但是怎么做呢?具體一點(diǎn)嘱能,在 Cocos Creator 上應(yīng)該怎么做呢吝梅?
為此,我們需要先大概了解一下 Cocos Creator 的資源加載流程惹骂。
3.2 Cocos Creator 的資源加載流程
- 后續(xù)說(shuō)明將以 Cocos Creator 2.3.3 為例進(jìn)行說(shuō)明苏携,2.3.3 和 2.4.0 的資源加載是不一樣的
- 建議大家下載 Cocos Creator 2.3.3 的 引擎源碼去使用VSCode輔助理解
git clone git@github.com:cocos-creator/engine.git cd engine git checkout 2.3.3 code ../engine
事實(shí)上,Cocos Creator 的所有資源加載都是通過(guò) cc.loader
去進(jìn)行的对粪,無(wú)論是動(dòng)態(tài)加載右冻,靜態(tài)加載(場(chǎng)景)等,都是通過(guò) cc.loader
去進(jìn)行加載的著拭。
而 cc.loader
內(nèi)部的加載實(shí)現(xiàn)是一個(gè) pipe 管道工作流纱扭。每次加載一份資源由最基礎(chǔ)的 3 個(gè)工作流去負(fù)責(zé)整個(gè)加載流程。
- assetLoader
- downloader
- loader
這部分理解儡遮,可以參閱官方文檔 loader 的說(shuō)明
3.3 Cocos Creator Build Encrypt 「全資源支持」「資源處理流」原理
從上述文檔中乳蛾,我們知道有 3 個(gè)管道工作流,但是每個(gè) loader 具體是干什么的鄙币,我們并不知曉肃叶,為了知道這 3 個(gè)工作流的實(shí)際作用,我們需要翻閱源碼
- 再次建議大家下載 Cocos Creator 2.3.3 的 引擎源碼去使用VSCode輔助理解
git clone git@github.com:cocos-creator/engine.git cd engine git checkout 2.3.3 code ../engine
經(jīng)過(guò)翻閱十嘿,可以發(fā)現(xiàn)整個(gè)資源加載的內(nèi)容都在引擎源碼的 cocos2d/core/load-pipeline
目錄下:
通過(guò)上面的圖因惭,我們找到了3個(gè) loader 的初始化入口了,在依次翻閱 assetloader绩衷,downloader蹦魔,loader 的相關(guān)實(shí)現(xiàn)之后,我們可以發(fā)現(xiàn)突破口 downloader.js
總結(jié)一下:
在 cc.loader 的 pipe 管道流的第二個(gè) loader 唇聘,即 downloader 中版姑,Cocos 會(huì)根據(jù)不同資源文件的后綴名,去調(diào)用不同的 download 函數(shù)迟郎,并生成內(nèi)存對(duì)象剥险。
比如:
上述截圖中,png 后綴的文件會(huì)調(diào)用 downloadImage
的函數(shù)宪肖,該函數(shù)最終會(huì)通過(guò) callback(error, data)
函數(shù)返回結(jié)果
- 如果圖片加載成功過(guò)表制,那么會(huì)返回一個(gè) Image 對(duì)象
callback(null, img);
- 如果圖片加載失敗,那么會(huì)返回 Error 對(duì)象
callback(error);
再總結(jié)一下:
首先控乾,defaultMap
對(duì)象中么介,羅列了很多資源文件后綴名,并且都有對(duì)應(yīng)的 download 函數(shù)蜕衡。而我們通過(guò)了解 png 的加載邏輯壤短,更是了解到,實(shí)際上 downloadImage
函數(shù)的作用是 將文件下載并讀取轉(zhuǎn)換為內(nèi)存對(duì)象 。
那如果我們的文件是加密后的文件久脯,那么我們只需要在 downloadImage
函數(shù)中纳胧,讀取文件之后,先進(jìn)行解密帘撰,然后在生成 Image 對(duì)象返回跑慕,那就可以 解決運(yùn)行時(shí)解密的問(wèn)題(讀取加密文件->解密加密內(nèi)容 -> 生成內(nèi)存對(duì)象) 了。
再往外看一下 defaultMap
對(duì)象 摧找,感覺(jué) Cocos Creator 支持的所有的資源核行,我們都可以這樣子來(lái)弄。而這就是我們 Cocos Creator Build Encrypt
方案的特性—— 「全資源支持」 的理論支撐蹬耘!
那么芝雪,新的問(wèn)題來(lái)了,我們應(yīng)該怎么替換不同資源的 download 函數(shù)呢婆赠?
在搜索過(guò)程中绵脯,發(fā)現(xiàn)了很久以前的一個(gè) 帖子 ,帖子中 panda 大大就已經(jīng)羅列了一個(gè)范例:
cc.loader
已經(jīng)為我們提供了 addDownloadHandlers
了休里,因此蛆挫,我們可以很方便地替換我們的資源加載方式。
我們以 Cocos Creator 2.3.3 原生平臺(tái)加載的 png 資源為例:
- 假設(shè)我們對(duì) Cocos Creator 2.3.3 構(gòu)建原生平臺(tái)后的輸出目錄的所有 png 圖片進(jìn)行加密妙黍,僅僅是轉(zhuǎn)換為 Base64字符串悴侵,并且存放到原png圖片所在目錄的話,參考代碼如下:(具體代碼見(jiàn) CocosCreator-Build-Encrypt 倉(cāng)庫(kù))
handle(): void {
// 獲取圖片文件
let imgFilePaths: string[] = [];
// 獲取指定目錄的所有圖片文件路徑
this.collectImageFilePaths(buildOutputResDirPath, imgFilePaths);
// 加密圖片文件
console.log(`圖片處理:找到 ${imgFilePaths.length} 張?jiān)紙D片`);
imgFilePaths.forEach((filePath: string) => {
// 讀取原圖片內(nèi)容
let fileBuffer: Buffer = fs.readFileSync(filePath);
// 刪除原圖片資源
fs.unlinkSync(filePath);
// 原圖片文件內(nèi)容轉(zhuǎn)換為 Base64 字符串拭嫁,寫(xiě)入到同目錄文件名的 .xxpng 文件中
fs.writeFileSync(filePath.replace(".png", ".xxpng"), fileBuffer.toString("base64"));
});
console.log(`圖片處理:${imgFilePaths.length} 張?jiān)紙D片已加密完成`);
}
- 那么可免,對(duì)應(yīng)的解密代碼就可以這樣子寫(xiě):先讀取 .xxxpng 文本,然后將文本解密轉(zhuǎn)換為 Image 對(duì)象做粤,返回 Image 對(duì)象即可:
if (CC_JSB) {
function downloadText(item) {
var url = item.url;
var result = jsb.fileUtils.getStringFromFile(url);
if (typeof result === "string" && result) {
return result;
} else {
return new Error("Download text failed: " + url);
}
}
cc.loader.addDownloadHandlers({
png: function (item, callback) {
// 從定向原圖片地址 為 加密后的文件
item.url = item.url.replace(".png", ".xxpng");
let text = downloadText(item);
if (text instanceof Error) {
callback(text, null);
} else {
// 將圖片文件的文本轉(zhuǎn)換為Image;
let img = new Image();
img.src = "data:image/png;base64," + text;
img.onload = function (info) {
callback(null, img);
};
img.onerror = function (event) {
callback(new Error("load image fail:" + img.src), null);
}; // Don't return anything to use async loading.
}
},
});
}
當(dāng)然浇借,細(xì)品上面代碼之后,你可能會(huì)有一些問(wèn)題怕品,比如:
Q:第2步中妇垢, downloadText 和 處理 Image 函數(shù),你是怎么知道這樣子寫(xiě)的肉康,我看的源碼中(cocos2d/core/load-pipeline
)文本和圖片的處理并不是這樣子的闯估!
A:在 Cocos Creator 2.3.3 打包原生平臺(tái)后,會(huì)生成一個(gè) jsb-adapter
文件夾吼和,在 jsb-adapter/jsb-engine.js
中涨薪,可以看到在原生平臺(tái)上,引擎是重寫(xiě)了資源加載的方式以適配原生平臺(tái)炫乓,參考這里面 png 和 txt 的加載方式刚夺,就可以寫(xiě)出第2步中的代碼了—— 在原生平臺(tái)上讀取文件献丑,并轉(zhuǎn)換為內(nèi)存對(duì)象。
至此光督,整個(gè)流程上阳距,我們已經(jīng)實(shí)現(xiàn)了資源加解密的流程了,并且理論上可以支持所有的資源哦~
當(dāng)然结借,你完全也可以處理png圖片的時(shí)候
- 先進(jìn)行加密
- 對(duì)加密內(nèi)容壓縮在寫(xiě)入文件(此壓縮主要用于減少文件體積)
解密時(shí)
- 先解壓文件
- 在解密內(nèi)容
流式處理,你想怎么處理就怎么處理卒茬。
而這個(gè)也是 Cocos Creator Build Encrypt 的「資源處理流」原理——不僅僅可以加密船老,還可以壓縮等等。
3.4 Cocos Creator Build Encrypt 「跨平臺(tái)」原理
有了上個(gè)章節(jié)的流程后圃酵,我們可以開(kāi)始考慮這個(gè)方案的適用性柳畔,比如:這個(gè)方案能跨平臺(tái)嗎?能跨平臺(tái)到什么程度呢郭赐?
先回顧一下整個(gè)運(yùn)行時(shí)解密的過(guò)程:
運(yùn)行時(shí):讀取加密文件 -> 解密加密內(nèi)容 -> 生成資源對(duì)象
這個(gè)過(guò)程里面的難點(diǎn)在哪里呢薪韩?
- 解密加密內(nèi)容:這一步不是難點(diǎn)所在,那怕是在不同平臺(tái)上捌锭,我們也能比較好處理俘陷,畢竟加密代碼是我們自己寫(xiě)的加密代碼,對(duì)應(yīng)的解密代碼不是問(wèn)題
- 生成資源對(duì)象:在不同平臺(tái)上观谦,也不難拉盾,可以參考源碼
那么剩下的就是 讀取加密文件 這一步了,在跨平臺(tái)上豁状,它可能難在哪里呢捉偏?
- 文件可能存放在(手機(jī),PC)設(shè)備本地泻红,可以能存放在網(wǎng)絡(luò)上
- 在不同平臺(tái)上夭禽,讀取本地文件和網(wǎng)絡(luò)文件的方式可能是不同。比如:
- 原生Android谊路,iOS上可能是調(diào)用不同的原生讀本地文件接口去讀取文件內(nèi)容讹躯,調(diào)用不同的請(qǐng)求接口去加載網(wǎng)絡(luò)文件
- 微信小游戲上可能又是不一樣的微信接口方式
- ...
那么,這些可能的難點(diǎn)要怎么解決呢凶异?
實(shí)際上蜀撑,在上個(gè)章節(jié)的問(wèn)題環(huán)節(jié)中,我們已經(jīng)初步涉及到這問(wèn)題:怎么讀取不同平臺(tái)(Android剩彬,iOS酷麦,微信小游戲等等)的資源?
在上個(gè)章節(jié)中喉恋,我們提及到一個(gè)關(guān)鍵點(diǎn):
在 Cocos Creator 2.3.3 打包原生平臺(tái)后沃饶,會(huì)生成一個(gè)
jsb-adapter
文件夾
在這個(gè)理解下母廷,只要我們?cè)谑褂?Cocos Creator 打包指定平臺(tái)后,檢查一下其是否存在相應(yīng)的適配代碼(一般是 jsb-adapter 文件夾)糊肤,參考其中的適配代碼的實(shí)現(xiàn)就可以完成適配該平臺(tái)的讀取文件的實(shí)現(xiàn)了琴昆。
而這就是 Cocos Creator Build Encrypt 「跨平臺(tái)」 原理的理論支撐!
當(dāng)然馆揉,對(duì)應(yīng)到不同版本的 Cocos Creator 业舍,你也可以這樣子參考其輸出的構(gòu)建目錄去進(jìn)行適配。
3.5 Cocos Creator Build Encrypt 「無(wú)侵入」原理
到這里升酣,我們?cè)诨仡櫼幌律厦娴牧鞒滔夏海€缺了一個(gè)很重要的點(diǎn)沒(méi)講:
cc.loader.addDownloadHandlers({
png: function (item, callback) {
// ...
}
}
在什么時(shí)候執(zhí)行上面這段代碼注入呢?
- 不能在引擎代碼之前注入噩茄,否則腳本不生效
- 不能在場(chǎng)景加載后才能注入下面,否則可能沒(méi)有覆蓋所有資源加載
那么,剩下答案就是 插件腳本 了 绩聘,在 Cocos Creato 中沥割,存在幾種類型腳本,它們之間的加載順序 如下:
那么凿菩,我們只需要將解密的代碼放入到我們的項(xiàng)目机杜,并弄成插件腳本就可以了,Easy~
至此蓄髓,我們整個(gè)加解密流程是已經(jīng)跑起來(lái)了叉庐,但是完美了嗎?還沒(méi)有会喝。
在將解密代碼導(dǎo)入為插件腳本這一步上陡叠,我們并沒(méi)有做得很好,理由有幾個(gè)點(diǎn):
- 每個(gè)項(xiàng)目都需要導(dǎo)入同一份插件腳本肢执,侵入了項(xiàng)目枉阵,并且重復(fù)的工作很乏味
- 換個(gè)同學(xué)來(lái)開(kāi)發(fā),肯定會(huì)對(duì)這份插件腳本的代碼有疑問(wèn)预茄,因?yàn)槔锩嬷挥薪饷艽a兴溜,沒(méi)有加密部分(加密部分我們另外寫(xiě)了),代碼不完整耻陕,容易帶來(lái)一些疑問(wèn)拙徽,溝通成本
那么,對(duì)應(yīng)這些問(wèn)題诗宣,最好的解決方案就是「無(wú)侵入」膘怕,讓開(kāi)發(fā)的依舊按照他的開(kāi)發(fā),他看不到也不需要看到整個(gè)資源加解密過(guò)程召庞,只需要提供構(gòu)建目錄岛心,我們對(duì)構(gòu)建目錄進(jìn)行一次加密即可来破。那么又該如何做?
這里需要我們?nèi)チ私庖恍┗镜膯?wèn)題:
Q1. 插件腳本在構(gòu)建后放在哪里忘古?
Q2. 引擎是怎么控制腳本加載順序呢徘禁?具體一點(diǎn)侧到,插件腳本是怎么控制在項(xiàng)目代碼之前加載的呢黔宛?
帶著這兩個(gè)疑問(wèn),我們依舊以 Cocos Creator 2.3.3 打包原生這個(gè)作為例子去了解搔谴。
A1:從下圖可以看到我們構(gòu)建后的插件腳本是放在 src/assets/scripts
目錄下
A2:在打包的輸出目錄中
- 入口是
main.js
(這一點(diǎn)不解釋了) - 在
require
一堆 js 后旦袋,進(jìn)入到window.boot
函數(shù)骤菠,這里注意一下,jsb-engine.js
是先于window.boot
reqiure 的 - 在
window.boot
函數(shù)中疤孕,所有的腳本都在jsList
數(shù)組變量中,并且插件腳本是先于我們的項(xiàng)目腳本的
當(dāng)我們了解到這些之后央拖,我們就可以自己去對(duì) Cocos Creator 構(gòu)建輸出后的目錄加入額外的插件腳本了祭阀,步驟如下:
- 復(fù)制解密插件腳本到項(xiàng)目構(gòu)建輸出目錄的
src/assets
中 - 修改
main.js
,讓我們的插件腳本先加入到jsList
變量的前面即可
當(dāng)然鲜戒,這兩步操作专控,其實(shí)我們也可以通過(guò)額外的腳本自動(dòng)化,釋放人力遏餐,具體自動(dòng)化代碼可以參考我的示例倉(cāng)庫(kù) CocosCreator-Build-Encrypt
而這伦腐,就是我們 Cocos Creator Build Encrypt 方案的 「無(wú)侵入」 原理了。
三失都、總結(jié)
好了柏蘑,方案完了,這應(yīng)該是一篇長(zhǎng)文粹庞,需要反復(fù)琢磨咳焚,細(xì)節(jié)的地方可能需要在復(fù)習(xí)一下。
需要強(qiáng)調(diào)一下庞溜,這依舊是一個(gè)方案革半。作為一個(gè)方案,它可以改動(dòng)的點(diǎn)太多了流码,比如:
- 上述例子又官,我們只是以 Cocos Creator 2.3.3 進(jìn)行說(shuō)明,并沒(méi)有對(duì)大改后的 2.4.0 進(jìn)行說(shuō)明漫试,但是根據(jù) 2.4.0 loader
的文檔和上述理論六敬,但是相信你現(xiàn)在已經(jīng)可以自行適配了 - 上述例子,我們將 .png 改為了 .xxpng 的密文格式商虐,其實(shí)你完全可以直接覆蓋在 .png 上觉阅,無(wú)需額外弄 .xxpng 后綴(畢竟小游戲平臺(tái)上支持的文件后綴是有限制的)
- 上述例子崖疤,加解密部分只是為了方便講解,大家完全可以自己重寫(xiě)這部分
- ...
而我們的例子 CocosCreator-Build-Encrypt 支持的還比較少典勇,但是作為一種方案劫哼,它理論上能支持我上面所說(shuō)的特性:
- 「無(wú)侵入」
- 「全資源支持」
- 「跨平臺(tái)」
- 「資源處理流」
它依舊需要大家去根據(jù)自己的需求去做完善,但是相信現(xiàn)在的你已經(jīng)知道怎么去弄了割笙。