creator 2.x 逆向还原

892 阅读3分钟

prefab

  • 开发模式下
[
  {
    "__type__": "cc.Prefab",
    "_name": "",
    "_objFlags": 0,
    "_native": "",
    "data": {
      "__id__": 1
    },
    "optimizationPolicy": 0,
    "asyncLoadAssets": false,
    "readonly": false
  },
  {
    "__type__": "cc.Node",
    "_name": "New Node",
    "_objFlags": 0,
    "_parent": null,
    "_children": [],
    "_active": true,
    "_components": [],
    "_prefab": {
      "__id__": 2
    },
    "_opacity": 255,
    "_color": {
      "__type__": "cc.Color",
      "r": 255,
      "g": 255,
      "b": 255,
      "a": 255
    },
    "_contentSize": {
      "__type__": "cc.Size",
      "width": 0,
      "height": 0
    },
    "_anchorPoint": {
      "__type__": "cc.Vec2",
      "x": 0.5,
      "y": 0.5
    },
    "_trs": {
      "__type__": "TypedArray",
      "ctor": "Float64Array",
      "array": [
        0,
        0,
        0,
        0,
        0,
        0,
        1,
        1,
        1,
        1
      ]
    },
    "_eulerAngles": {
      "__type__": "cc.Vec3",
      "x": 0,
      "y": 0,
      "z": 0
    },
    "_skewX": 0,
    "_skewY": 0,
    "_is3DNode": false,
    "_groupIndex": 0,
    "groupIndex": 0,
    "_id": ""
  },
  {
    "__type__": "cc.PrefabInfo",
    "root": {
      "__id__": 1
    },
    "asset": {
      "__id__": 0
    },
    "fileId": "",
    "sync": false
  }
]

最终序列化的json就是上边的数据

function deserialize(json, options) {
      var classFinder, missingClass;
      false;
      classFinder = cc._MissingScript.safeFindClass;
      var pool = null;
      false;
      var _require = require("../platform/deserialize-compiled"), deserializeForCompiled = _require["default"];
      var deserializeForEditor = require("../platform/deserialize-editor");
      pool = deserializeForCompiled.isCompiledJson(json) ? deserializeForCompiled.Details.pool : deserializeForEditor.Details.pool;
      var tdInfo = pool.get();
      var asset;
      try {
        asset = cc.deserialize(json, tdInfo, {
          classFinder: classFinder,
          customEnv: options
        });
      } catch (e) {
        pool.put(tdInfo);
        throw e;
      }
      false;
      var uuidList = tdInfo.uuidList;
      var objList = tdInfo.uuidObjList;
      var propList = tdInfo.uuidPropList;
      var depends = [];
      for (var i = 0; i < uuidList.length; i++) {
        var dependUuid = uuidList[i];
        depends[i] = {
          uuid: helper.decodeUuid(dependUuid),
          owner: objList[i],
          prop: propList[i]
        };
      }
      asset.__depends__ = depends;
      asset._native && (asset.__nativeDepend__ = true);
      pool.put(tdInfo);
      return asset;
    }
  • 构建后
[
    1,
    ["b6C9ZaXKNCT6erXsO4zEBN"],
    ["asset","root","data"],
    [
        ["cc.Prefab",["_name"],2],
        ["cc.Node",["_name","_prefab"],2,4],
        ["cc.PrefabInfo",["root","asset"],3,1,6]
    ],
    [
        [0,0,2],
        [1,0,1,2],
        [2,0,1,1]
    ],
    [
        [0,"New Node"],
        [1,"New Node",[2,-1,0]]
    ],
    0,
    [0,1,1,0,2,1,1],
    [0],
    [0],
    [0]
]

判断图片的扩展名

会根据id置换出来对应的纹理格式

image.png

预览模式下纹理是单文件传递过来的

image.png

所以无论你怎么排查上层的链路数据,你发现他都是上边的

但是在构建后,数据变成了

image.png

被包裹到了array[5]里面,追踪源码发现,最终也是对array[5]进行了解析

image.png

所以获取纹理的格式,直接取array[5][0]作为key就行, 然后直接取第一个元素

image.png

主要到array[5]也是一个array,详细的逻辑参考parseInstances的实现,里面有详细的解释,暂时没精力分析

image.png

至于array的其他数组数据的含义,有时间再研究

image.png

json类型

[
    1, // 0 version,会被替换为options的数据
    0, // 1
    0, // 2
    [
        [
            "cc.JsonAsset", // 会被替换成对应的构造函数
            [
                "_name",
                "json"
            ], // keys
            1 // classTypeOffset
        ]
    ], // 3 classes
    [
        [
            0,
            0,
            1,
            3 // maskTypeOffset
        ]
    ], // 4 masks
    [
        [
            0,
            "json",
            {}
        ]
    ],// instances的数据
    0,// instanceTypes
    0,
    [],
    [],
    []
]

json走的是通用的序列化

image.png

arr[3] classes 的数据解析

image.png

走的都是正常的序列化逻辑,不想再将这个逻辑搬运过来了,本来不同的版本还可能存在差异,所以,直接将cocos的代码改下,导出deserialize函数重新编译,然后需要一个运行环境,发现jsb有对window做适配,而单纯的nodejs环境是没有window的,

redirect

image.png

最终redirect的数据结构如下

const asset = {
    uuid:"", // 这个资源的uuid
    redirect:"internal",// 这个资源所在的bundle
}

引擎中关于redirect的解析逻辑

if (bundles.has(item.bundle)) {
  var config = bundles.get(item.bundle)._config;
  var info = config.getAssetInfo(uuid);
  if (info && info.redirect) {// 是否为重定向的资源
    if (!bundles.has(info.redirect)) { // 检查是否加载了重定向的bundle
      throw new Error("Please load bundle " + info.redirect + " first");
    }
    config = bundles.get(info.redirect)._config;// 获取重定向bundle的配置
    info = config.getAssetInfo(uuid);// 从重定向的bundle中获取资源信息
  }
  out.config = config;
  out.info = info;
}

这也解释了,engine要求使用bundle的资源之前,必须加载依赖的bundle

pack

image.png 加载pack的逻辑

load: function load(item, options, onComplete) {
  // 如果没被打包进去,就直接下载
  if (item.isNative || !item.info || !item.info.packs) return downloader.download(item.id, item.url, item.ext, item.options, onComplete);
  if (files.has(item.id)) return onComplete(null, files.get(item.id));
  var packs = item.info.packs;// 被pack进去的信息
  var pack = packs.find(isLoading);
  if (pack) return _loading.get(pack.uuid).push({
    onComplete: onComplete,
    id: item.id
  });
  pack = packs[0];
  _loading.add(pack.uuid, [ {
    onComplete: onComplete,
    id: item.id
  } ]);
  var url = cc.assetManager._transform(pack.uuid, {
    ext: pack.ext,
    bundle: item.config.name
  });
  // 下载pack的数据,也就是上图中的023c6ef0e
  downloader.download(pack.uuid, url, pack.ext, item.options, (function(err, data) {
    files.remove(pack.uuid);
    err && cc.error(err.message, err.stack);
    // 解开pack的数据
    packManager.unpack(pack.packs, data, pack.ext, item.options, (function(err, result) {
      if (!err) for (var id in result) files.add(id, result[id]);
      var callbacks = _loading.remove(pack.uuid);
      for (var i = 0, l = callbacks.length; i < l; i++) {
        var cb = callbacks[i];
        if (err) {
          cb.onComplete(err);
          continue;
        }
        var data = result[cb.id];
        data ? cb.onComplete(null, data) : cb.onComplete(new Error("can not retrieve data from package"));
      }
    }));
  }));
}

解析 pack data 的逻辑

unpackJson: function unpackJson(pack, json, options, onComplete) {
  var out = js.createMap(true), err = null;
  if (Array.isArray(json)) {
    json = (0, _deserializeCompiled.unpackJSONs)(json, cc._MissingScript.safeFindClass);
    json.length !== pack.length && cc.errorID(4915);
    for (var i = 0; i < pack.length; i++) {
      var key = pack[i] + "@import";
      out[key] = json[i];
    }
  } else {
    var textureType = js._getClassId(cc.Texture2D);
    if (json.type === textureType) {
      if (json.data) {
        var datas = json.data.split("|");
        datas.length !== pack.length && cc.errorID(4915);
        for (var _i = 0; _i < pack.length; _i++) out[pack[_i] + "@import"] = (0, _deserializeCompiled.packCustomObjData)(textureType, datas[_i]);
      }
    } else {
      err = new Error("unmatched type pack!");
      out = null;
    }
  }
  onComplete && onComplete(err, out);
}