欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

《DNFMobile》图片资源提取笔记

程序员文章站 2024-01-27 15:48:22
...

  欢迎参与讨论,转载请注明出处。
  本文转载自https://musoucrow.github.io/2018/01/20/dnf_mobile_ex/

前言

  《DNFMobile》的新一波内测到来了,恰好得到了安装包。便欲对比其资源较之端游有何不同,遂试图提取之。在尝试的途中遇到了不少问题,特此记录之。源码地址

读取资源包

  与众多手游的习惯一样,初始的安装包所携带的资源甚少,皆需经过更新方才完整。更新后经过观察得知游戏采用Unity制作,那么事情便简单了,直接上UnityStudio读取。虽然资源文件的后缀名为.npk,但实际上则是Unity的AssetBundle,且并未作加密。然而诸多现成的Unity提取工具皆有多少缺陷(无法识别pvr格式、无法批量化操作、导出资源过于原始等),且图片资源是大图形式存在的,需要进行切图,而使用切图工具一则怕不够精确,二则怕无法批量化。于是我选择直接使用Unity制作工具以面对此需求。
  由于资源文件本身即是Unity的格式,那么直接调用API加载即可,类似如此:

var assetBundle = AssetBundle.LoadFromFile(path);
var assetBundle.LoadAllAssets<Texture2D>();

foreach (var tex in texs) {
    //...
}

切图

  这样可谓相当方便,接下来的问题便是切图了。我本以为大图是由Unity自动生成,所以理应资源内会有对应的Sprite资源,这样通过Sprite资源的信息即可进行切割。但实际上并非如此:大图是事先生成好,然后使用脚本填写每帧配置在运行时自动生成Sprite。这种做法也是理所当然的,毕竟Unity的Sprite的pivot与DNF的IMG包提供的偏移点可谓天南地北。(一者为当前图片下的浮点百分比,另一者为实际坐标)通过直接在配置直接对接IMG包的数据然后进行转化这是很正常的做法。可这下子就麻烦了,我们并无法直接知道这脚本的具体信息。幸好UnityStudio的解析中包括了关于MonoBehavior资源配置的信息。
《DNFMobile》图片资源提取笔记
  可即使知道也无法直接Unity进行获取,毕竟我们本身是没有该脚本的。鉴于UnityStudio开源的特性,我起初打算阅读源码掌握其解析之法。最后也成功了,可我突然脑内灵光一闪,想到了直接建立一个同名脚本,并根据配置的信息进行模拟。代码如下:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DNFAtlasAsset : MonoBehaviour {
    [Serializable]
    public struct Rectf {
        public float x;
        public float y;
        public float width;
        public float height;
    }

    [Serializable]
    public struct DNFAtlasElement {
        public string name;
        public int originIndex;
        public int referenceIndex;
        public int originWidth;
        public int originHeight;
        public int offsetX;
        public int offsetY;
        public Rectf rect;
    }

    [Serializable]
    public struct DNFAtlasSlot {
        public int matType;
        public DNFAtlasElement[] elementList;
    }

    public string atalsName;
    public DNFAtlasSlot[] atlasSlotList;
}

  天可怜见,居然成功了!那么接下来采用类似如下方法即可:

int width = (int)element.rect.width;
int height = (int)element.rect.height;

if (width == 0 || height == 0) {
    continue;
}

var colors = tex.GetPixels((int)element.rect.x, (int)element.rect.y, width, height);
var newTex = new Texture2D(width, height, tex.format, tex.mipmapCount > 1);
newTex.SetPixels(colors);
newTex.Apply();

var path = this.GetPath() + tex.name + "/";
var bytes = newTex.EncodeToPNG();
var json = JsonUtility.ToJson(element);
var name = this.ToNumber(element.name);

this.CreateDirectory(path);
File.WriteAllText(path + name + ".json", json);
File.WriteAllBytes(path + name + ".png", bytes);
Texture2D.DestroyImmediate(newTex, true);

  主要思路便是通过GetPixels方法读取区域像素并覆盖至新图。最后将图片转换为PNG、配置转换为JSON并输出即可。
  当然这里还有关于Texture2D的readable问题,隶属于资源包的Texture2D并无法直接使用GetPixels方法,需要对其进行复制,然后利用新图施为,为此我写了个函数:

private Texture2D GetTexture(Texture2D tex){
    if (!this.texureMap.ContainsKey(tex)) {
        if (tex.width == 0 || tex.height == 0) {
            return null;
        }

        var copyTex = new Texture2D(tex.width, tex.height, tex.format, tex.mipmapCount > 1);
        copyTex.LoadRawTextureData(tex.GetRawTextureData());
        copyTex.Apply();
        var writeTex = new Texture2D(copyTex.width, copyTex.height);
        writeTex.SetPixels32(copyTex.GetPixels32());
        writeTex.Apply();
        writeTex.name = this.ToName(tex.name);

        this.texureMap[tex] = writeTex;
        Texture2D.DestroyImmediate(copyTex, true);
    }

    return this.texureMap[tex];
}

  以上便是关于切图方面的问题,具体可参阅源码。

内存问题

  游戏目前的资源包数量高达2900以上,在尝试一口气全部提取时内存竟然高达5G!最终电脑不堪重负倒下收场。这很显然是资源并未回收所致,是以作此函数:

public void Destroy() {
    foreach (var texture in this.texureMap) {
        Texture2D.DestroyImmediate(texture.Key, true);
        Texture2D.DestroyImmediate(texture.Value, true);
    }

    this.texureMap.Clear();
    this.assetBundle.Unload(true);
    AssetBundle.DestroyImmediate(this.assetBundle, true);
}

  这里采用的是DestroyImmediate方法,好处是立即进行回收,但却会因此阻塞,影响提取效率。若使用Destroy方法则不会如此,不过峰值内存会上升。但大规模读取时还是以稳定为主,而小份读取则两者并无所谓。是以选择DestroyImmediate方法。
  另外在其他地方涉及到资源生成且是继承自UnityEngine.Object的,皆需注意此问题。在经过优化后,占用由5G跌倒了500M-1.5G之间,成功提取了全部图片资源。大功告成!

后记

  这次《DNFMobile》的声音资源经过了高压,原本几M的音乐变成了上百K,可谓惨不忍听,遂无提取的价值。而纸娃娃方面则是采用了类似NPK_Ver4的色板做法,也并无法直接提取到成品。由此可见制作组为了节省空间可是下了不少功夫呀。