逆向工程这种事情,类似软件破解,不是万不得已不想去做。
最近有个需求,我们要将一个老的cocos2d-x 3.2 + cocos Studio1.6做的游戏进行改造,必须修改ui和特效里面的东东。但是由于种种原因,我们的UI工程已经找不到了。
这就意味着只有两条路可走,要么结合程序中用到的各种层级关系,重建一个新的工程;要么就是根据原来的csb去做逆向工程,重新构建一个可用的工程文件。
先在网上看一看有没有相关的代码或工具,避免重复造轮子。
首先找到的,是一个叫x-studio365的工具,根据它的介绍:
- 支持导入CocosStudio ccs工程及反导入CocosStudio发布的json和csb格式ui, 【文件】
【导入】【CocosStudio(.ccs/.json)】,【文件】【导入】【CocosStudio(*.csb)】需先新建一个空工程
- 支持导入csb(将csb文件拖入编辑器场景即可)和发布到csb给ccoos2d-x引擎直接使用
按道理是可以用它来代替cocosStudio的,但是实测了一下,它只支持cocosStudio2.0以上的版本,而且最不可思议的是,它不支持打包成plist + png这样的图集建立的工程。总之,完全没法用。
这里我要顺便吐槽一下,cocosStudio2.0以上版本,相比cocosStudio1.6功能差太多,触控当年如果没有收编cocosStudio,也许今天我们能用到一个更加强大的动画和UI编辑软件,没spine和dragonBone什么事了。
然后我看到了这篇文章:cocos2d 由导出文件.csb反推出cocosUI工程。作者首先一通貌似强大的分析,然后在百度网盘上分享了源码,活雷锋啊,必须给作者一万个赞!下载"源码"一看:尼玛这是啥?完全是cocos2d-x 3.4 cocos studio部分的源码呀,跟作者有半毛钱关系?然后仔细看作者的分析,牛头不对马嘴,根本没找到要点。嗯,必须给作者一万个踩!假的技术文章也好意思写?浪费大家时间!
那首歌怎么唱的?从来没有什么救世主,一切要靠我们自己。
前面谈过,为了填CocosStudio的坑,我其实多次翻过这部分源码,大致理解它读取csb的机制,现在要重构工程,完全可以利用cocos自身的类来为我们解析。大家可以仔细研究分析一下CCSGUIReader.cpp中的GUIReader::WidgetFromBinaryFile这个类,怎么读出json结构的算法就有了。
由于算法中要用到cocos2d-x里面的很多类,懒得去分析各个类的依赖关系,直接用cocos新建一个工程来做这个转换工程。我机器上的cocos2d-x现在是3.6版本,已经填过坑,可以完美读取cocosStudio1.6的csb文件,我们就用它来建新工程吧。
cocos new csb2json -l cpp
然后新建一个Csb2Json类,包含两个文件:Csb2Json.cpp和Csb2Json.h。vs中直接用类向导建立即可。大致内容如下:
1.Csb2Json.h:
#pragma once
#include "cocos2d.h"
#include "cocostudio/CocoStudio.h"
#include <fstream>
USING_NS_CC;
using namespace std;
using namespace cocostudio;
class Csb2Json
{
public:
Csb2Json();
~Csb2Json();
static Csb2Json * getInstance() {
static Csb2Json m_instance;
return &m_instance;
};
int run(const string csbName);
private:
void getTree(CocoLoader *pLoader, stExpCocoNode *pNode, int nTabNum);
ofstream * m_pOutStream;
};
2.Csb2Json.cpp
#include "Csb2Json.h"
Csb2Json::Csb2Json()
{
}
Csb2Json::~Csb2Json()
{
}
int Csb2Json::run(const string csbName)
{
std::string fullPath = FileUtils::getInstance()->fullPathForFilename(csbName);
auto len = csbName.find(".");
auto pureName = csbName.substr(0, len);
auto jsonName = pureName + ".json";
auto fileData = FileUtils::getInstance()->getDataFromFile(fullPath);
auto fileDataBytes = fileData.getBytes();
ofstream oStream(jsonName, ios::out);
m_pOutStream = &oStream;
CocoLoader tCocoLoader;
if (true == tCocoLoader.ReadCocoBinBuff((char*)fileDataBytes))
{
*m_pOutStream << "{" << endl;
stExpCocoNode* tpRootCocoNode = tCocoLoader.GetRootCocoNode();
rapidjson::Type tType = tpRootCocoNode->GetType(&tCocoLoader);
if (rapidjson::kObjectType == tType || rapidjson::kArrayType == tType)
{
getTree(&tCocoLoader, tpRootCocoNode,1);
}
*m_pOutStream << "}" << endl;
m_pOutStream->close();
return 0;
}
else {
return 1;
}
}
void Csb2Json::getTree(CocoLoader *pLoader, stExpCocoNode *pNode, int nTabNum)
{
stExpCocoNode* pArray = pNode->GetChildArray(pLoader);
int nChildNum = pNode->GetChildNum();
//strTab用于控制缩排,按cocos原来的标准,一次缩排2个空格。
std::string strTab = "";
for (int i = 0; i < nTabNum; ++i) {
strTab = strTab + " ";
}
for (int i = 0; i < nChildNum; ++i) {
stExpCocoNode *pSubNode = &pArray[i];
string strKey = pSubNode->GetName(pLoader);
string strVal = pSubNode->GetValue(pLoader);
rapidjson::Type tType = pSubNode->GetType(pLoader);
if (strKey != "") {
strKey = "\"" + strKey + "\": ";
}
if (rapidjson::kObjectType == tType) {
if (strKey == "\"children\": ") {
strKey = "";
}
*m_pOutStream << strTab << strKey << "{" << endl;
getTree(pLoader, pSubNode, nTabNum + 1);
*m_pOutStream << strTab << "}";
}
else if (rapidjson::kArrayType == tType) {
*m_pOutStream << strTab << strKey << "[";
int subNum = pSubNode->GetChildNum();
if (subNum == 0) { //避免空数组换行。
*m_pOutStream << "]";
}
else {
*m_pOutStream << endl;
getTree(pLoader, pSubNode, nTabNum + 1);
*m_pOutStream << strTab << "]";
}
}
else {
if (rapidjson::kNullType == tType) {
strVal = "null";
}
else if (rapidjson::kStringType == tType || 83 == tType) {
strVal = "\"" + strVal + "\"";
}
else if ((rapidjson::kNumberType == tType || -8 == tType) && strVal == "1") {
strVal = "true";
}
else if ((rapidjson::kNumberType == tType || -8 == tType) && strVal == "0") {
strVal = "false";
}
*m_pOutStream << strTab << strKey << strVal;
}
//判断并合理使用结尾的逗号。
if (i == nChildNum - 1) {
*m_pOutStream << endl;
}
else {
*m_pOutStream << "," << endl;
}
}
}
**注意:
1.当读取到的类型为0bject或Array时,getTree函数要被递归调用。
2.tType是个1-6的枚举,但是实际取出来有8,-8 , 83等等奇怪的值,必须一一处理。
3.true和false并不在kTrueType和kFalseType里面,这两个类型设计出来就没用过。
4.类型-8以及类型6(Number)里面的1表示true, 0表示false。
5.真正包含0和1的整数,大概都在类型8里面,但是依然有很多数在类型6中。
6.类型83是要求强制加引号的,与类型string一个意思。
7.当对象名为children时,它的上层数组也是children的时候,不显示……
8.乱到这种程度我已经无力吐槽,前面为cocoStudio叫屈的文字全部作废。
9.数组或对象里面最后一个成员后面必须不能有逗号,否则工程报错!
**
选择一个现有的工程来测试,同时输出csb和json。做个接口调用这个类,将csb文件作为输入,生成的同名json对比原工程输出的json,一点一点找到匹配规律,排除各种坑以后,最终形成上面的算法。最后将原来需要逆向的csb导入,生成的json放回工程中测试,顺利启动并完全符合需求。剩下的工作,就是做个选择文件的接口,具体不细说了,有点基础的朋友都应该能自己搞定。
网友评论