2017-02-26 21:05:25 justFWD 阅读数 12749

   网上已经有很多文章讲解过Unity3d脚本DLL 解密,基本方法原理都差不多,就是通过HOOK或者调试下断mono_image_open_from_data_with_name这个函数拿到解密后的DLL。这些方法都需要比较专业的知识(Hook或者过反调试),另外对于使用了强度比较高的第三方保护,也无能为力。下面我们来探讨一下更为通用简便的U3D脚本DLL解密方法。

一、脚本解密原理

      先来理一下通过mono_image_open_from_data_with_name解密脚本DLL的原理。

      Unity3d游戏的脚本默认情况下是非加密形态,保存于如下图的压缩包目录下,一般情况名字为Assembly-CSharp.dllAssembly-CSharp-firstpass.dll

  

 这样的脚本DLL是由C#语言编写的,可以被类似Reflector之类的工具反编译成源码。破解者得到源码就可以对其进行修改,实现内购破解、修改游戏人物属性(修改金币、HP)、修改战斗技能(无敌、强制胜利等)        

   我们来看下mono_image_open_from_data_with_name这个函数的源码


几个主要函数的参数意义:

data: 脚本内容

data_len:脚本长度

name:脚本名称

这个函数执行了如下几步操作:

1.data指向的脚本拷贝到新申请的内存

2.填充一个MonoImage结构体

3.使用do_mono_image_load初步加载该脚本

4.注册并返回MonoImage结构体指针

    这个原始mono_image_open_from_data_with_name函数,如果输入的data指向的是个加密过的脚本DLL,在这个函数执行之前,先要对该data指向的内存进行解密。

调试解密可在return处下断,此时data指向的内存已经解密,把该处内存拷贝出来即是解密后的DLL

     HOOK解密原理是待mono_image_open_from_data_with_name原始函数返回后拷贝data指向的内存

    不过往往第三方保护都是带反调试的,新手要过掉反调试还是比较费周折的,有些厉害的反调试,老手都不一定过得了。对SOHOOK也是要有一定的技术基础才能做到。

    对于强度稍微高点的保护,这个函数处是拿不到解密后的DLL的,等这个函数返回的时候,内存可能是空的。  

    绝大部分加密,都紧盯着data指向的内存来做文章,认为只要把mono_image_open_from_data_with_name参数里的data加密搞定就万事大吉了,而忽略掉了另一块永久存在的处于解密状态的DLL内存。

    mono_image_open_from_data_with_name第一步就是把data内存使用memcpy拷贝到一块新申请的内存里,这块内存将永久存在,因为C#语言是需要动态解析类、函数等信息的,时不时要用到这块内存。而data待这个函数调用完后,将会被释放掉。

    这也是某些强度比较高的第三方保护,通过那两种方法都得不到解密DLL的关键所在,它在mono_image_open_from_data_with_name函数返回前就把data指向的内存给清空了。

 

二、脚本解密方法

    下面就来讲一下我们的U3D脚本DLL解密方法,原理即是利用了内存中存在的那个memcpy拷贝的DLL

    由于内存中永久存在这个DLL,我们只要全内存搜索这个DLL就可以了。如何全内存搜索呢,自己写个工具还是挺麻烦的一件事。

    其实用手游分析者比较熟悉的一个现成工具就可以做到:烧饼修改器(八门神器等其它修改器也可以),烧饼修改器可以对游戏进行数值搜索。那我们要搜索什么数值呢?

   先用二进制工具hiew来看下Assembly-CSharp.dll


 这是个PE结构的文件,以4d 5a 90 00这四个字节为magic head,这可以做为C# DLL脚本的特征,我们只要搜索0x905a4d这个数值就可以了,由于烧饼修改器是使用10进制数值,将其转换成10进制值:9460301

    下图是使用烧饼修改器搜索一个3D卡车小游戏的示例:


  显示出来的是搜索结果,一共搜索到了12个结果

     第一个搜索结果,即是以52c4c008起始地址的脚本DLL,我们可以使用memdump工具把这块内存DUMP下来,由于不知道大小,dump 0x3000大小来看一下。


保存为abc.dll,再用hiew看下abc.dll


  可以看到DUMP下来的确实是个CSharp脚本DLL,DLL实际大小可通过下图这个FFI工具,解析出来,将第3行的0x10A00加上0x200即是实际大小。可通过这个方法来确定这12DLL,哪个是你需要解密的DLL

  

   这个方法的优点是可以不用去管反调试,也不用去学习怎么HOOK,用现成的工具组合起来就可以做到解密脚本。最重要一点是对保护强度比较高的保护也可以无视。

       (有兴趣的同学可以关注我的微信公众号:sybaohu,以及QQ群:606228104)




2016-01-05 18:11:50 angelsmiles 阅读数 13790

unity3d将一张图片切成多张图片

声明:本文内容与代码来源百度贴吧怎么将图集那样N张图片切割出来呢,作者遇到同样问题,在此负责整理。

问题

在独立开发游戏过程中,由于缺乏美术,大部分图都download于网络,遇到需要把一张大图切成多张小图。
如图:
卡通数字

需要切成单个图片再组成Atlas。

解决方法

利用NGUI的Sprite编辑,把图片Texture Type改为Sprite,Sprite Mode改为Multiple,如图所示:

图1

打开Sprite Editor,左上角选择Slice,可以自动切图,或者手动调整,然后应用,自动会生成一张张图片,但此时图片的信息只是存在meta中,重新制作Atlas需要导出图片。
图2

可以写脚本完成。

代码

选中切分后的图片,然后在菜单栏依次选择Assets->Sprite Sheet Packer->Process to Sprites

using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;

public static class SpriteSheetPackerImport
{
    [MenuItem("Assets/Sprite Sheet Packer/Process to Sprites")]
    static void ProcessToSprite()
    {
        Texture2D image = Selection.activeObject as Texture2D;//获取旋转的对象
        string rootPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(image));//获取路径名称
        string path = rootPath + "/" + image.name + ".PNG";//图片路径名称

        TextureImporter texImp = AssetImporter.GetAtPath(path) as TextureImporter;

        AssetDatabase.CreateFolder(rootPath, image.name);//创建文件夹

        foreach (SpriteMetaData metaData in texImp.spritesheet)//遍历小图集
        {
            Texture2D myimage = new Texture2D((int)metaData.rect.width, (int)metaData.rect.height);

            for (int y = (int)metaData.rect.y; y < metaData.rect.y + metaData.rect.height; y++)//Y轴像素
            {
                for (int x = (int)metaData.rect.x; x < metaData.rect.x + metaData.rect.width; x++)
                    myimage.SetPixel(x - (int)metaData.rect.x, y - (int)metaData.rect.y, image.GetPixel(x, y));
            }

            //转换纹理到EncodeToPNG兼容格式
            if (myimage.format != TextureFormat.ARGB32 && myimage.format != TextureFormat.RGB24)
            {
                Texture2D newTexture = new Texture2D(myimage.width, myimage.height);
                newTexture.SetPixels(myimage.GetPixels(0), 0);
                myimage = newTexture;
            }
            var pngData = myimage.EncodeToPNG();

            File.WriteAllBytes(rootPath + "/" + image.name + "/" + metaData.name + ".PNG", pngData);
        }
    }
}

此外

脚本主要用到SpriteMetaDataTextureImporter.spritesheetTexture2D.SetPixel,具体参考官方API文档。

脚本由13471713164原创,本人负责整理。

2013-01-21 14:53:57 jeksonal 阅读数 16704

unity3D 用3DMax 做的模型时需要注意几点

1:方向问题
2:模型尺寸问题

3:模型所占资源大小问题

 

3DMax 做的东西导入到 Unity3D 中,在 Unity3D 引擎中 X 会被反向旋转 90 度();个人处理方式是建模时先将模型 X 轴旋转 90;再调节模型;

3DMax 做的东西导入到 Unity3D 中,由于 3DMax 默认的单位处理机制与 Unity3D 不一样,而导致显示过小的问题;所以,在制作模型前先调节好 3DMax 中的系统单位设置

具体操作:

 菜单栏: 选择 “自定义”  ,

再选择 “单位设置”-- >

 接着点选 “系统单位设置 ”  --->   将系统单位比例设置成:  ;再点击 “ 确定” 保存!

在模型的制作过程当中,为了使物体的坐标在物体的中心点,要记得冻结物体的坐标属性

当模型制作完成时,导出为 FBX 格式时还需注意:

在导出的 设置面板中 :

点击高级选项,然后出现:

确保 “ 场景单位转化为:”的设置为 "Meters"


模型导出后,模型资源可能很大,原因有以下几个:

1: 检测一下材质贴图的格式是否为:.png 的,不是请改过来 ;

2:看模型当中是否有过多的独立存在的物体,将不需要单独 存在给它合并在一块!


要是模型导入到Unity3D 中后,引擎运行变卡,就得看看模型的面是不是太多了!


2012-12-25 15:01:43 asd237241291 阅读数 4778

原创文章如需转载请注明:转载自 脱莫柔Unity3D学习之旅  QQ群:【119706192】 本文链接地址: Unity3D 批量图片资源导入设置

复制代码
using UnityEngine;
using System.Collections;
using UnityEditor;
/// <summary>
/// 批量图片资源导入设置
/// 使用说明: 选择需要批量设置的贴图,
/// 单击DuanMenu/Texture Import Settings,
/// 打开窗口后选择对应参数,
/// 点击Set Texture ImportSettings,
/// 稍等片刻,--批量设置成功。
/// </summary>


public class TextureImportSetting : EditorWindow {
    
    /// <summary>
    /// 临时存储int[]
    /// </summary>
    private int[] IntArray = new int[] { 0, 1, 2, 3, 4, 5, 6, 7 };
    //AnisoLevel
    private int AnisoLevel = 1;
    //Filter Mode
    private int FilterModeInt = 0;
    private string[] FilterModeString = new string[] { "Point", "Bilinear", "Trilinear" };
    //Wrap Mode
    private int WrapModeInt = 0;
    private string[] WrapModeString = new string[] { "Repeat", "Clamp" };
    //Texture Type
    private int TextureTypeInt = 0;
    private string[] TextureTypeString = new string[] { "Texture", "Normal Map", "GUI", "Refelection", "Cookie", "Lightmap", "Advanced" };
    //Max Size
    private int MaxSizeInt = 5;
    private string[] MaxSizeString = new string[] { "32", "64", "128", "256", "512", "1024", "2048", "4096" };
    //Format
    private int FormatInt = 0;
    private string[] FormatString = new string[] { "Compressed", "16 bits", "true color" };
    
    /// <summary>
    /// 创建、显示窗体
    /// </summary>
    [@MenuItem("DuanMenu/Texture Import Settings")]
    private static void Init()
    {    
        TextureImportSetting window = (TextureImportSetting)EditorWindow.GetWindow(typeof(TextureImportSetting), true, "TextureImportSetting");
        window.Show();
    }
    
    /// <summary>
    /// 显示窗体里面的内容
    /// </summary>
    private void OnGUI()
    {
        //AnisoLevel
        GUILayout.BeginHorizontal();
        GUILayout.Label("Aniso Level  ");
        AnisoLevel = EditorGUILayout.IntSlider(AnisoLevel, 0, 9);
        GUILayout.EndHorizontal();
        //Filter Mode
        FilterModeInt = EditorGUILayout.IntPopup("Filter Mode", FilterModeInt, FilterModeString, IntArray);
        //Wrap Mode
        WrapModeInt = EditorGUILayout.IntPopup("Wrap Mode", WrapModeInt, WrapModeString, IntArray);
        //Texture Type
        TextureTypeInt = EditorGUILayout.IntPopup("Texture Type", TextureTypeInt, TextureTypeString, IntArray);
        //Max Size
        MaxSizeInt = EditorGUILayout.IntPopup("Max Size", MaxSizeInt, MaxSizeString, IntArray);
        //Format
        FormatInt = EditorGUILayout.IntPopup("Format", FormatInt, FormatString, IntArray);
        if (GUILayout.Button("Set Texture ImportSettings"))
            LoopSetTexture();
    }
    
    /// <summary>
    /// 获取贴图设置
    /// </summary>
    public TextureImporter GetTextureSettings(string path)
    {
        TextureImporter textureImporter = AssetImporter.GetAtPath(path) as TextureImporter;
        //AnisoLevel
        textureImporter.anisoLevel = AnisoLevel;
        //Filter Mode
        switch (FilterModeInt)
        {
            case 0:
                textureImporter.filterMode = FilterMode.Point;
                break;
            case 1:
                textureImporter.filterMode = FilterMode.Bilinear;
                break;
            case 2:
                textureImporter.filterMode = FilterMode.Trilinear;
                break;
        }
        //Wrap Mode
        switch (WrapModeInt)
        {
            case 0:
                textureImporter.wrapMode = TextureWrapMode.Repeat;
                break;
            case 1:
                textureImporter.wrapMode = TextureWrapMode.Clamp;
                break;
        }
        //Texture Type
        switch (TextureTypeInt)
        {
            case 0:
                textureImporter.textureType = TextureImporterType.Image;
                break;
            case 1:
                textureImporter.textureType = TextureImporterType.Bump;
                break;
            case 2:
                textureImporter.textureType = TextureImporterType.GUI;
                break;
            case 3:
                textureImporter.textureType = TextureImporterType.Reflection;
                break;
            case 4:
                textureImporter.textureType = TextureImporterType.Cookie;
                break;
            case 5:
                textureImporter.textureType = TextureImporterType.Lightmap;
                break;
            case 6:
                textureImporter.textureType = TextureImporterType.Advanced;
                break;
        }
        //Max Size 
        switch (MaxSizeInt)
        {
            case 0:
                textureImporter.maxTextureSize = 32;
                break;
            case 1:
                textureImporter.maxTextureSize = 64;
                break;
            case 2:
                textureImporter.maxTextureSize = 128;
                break;
            case 3:
                textureImporter.maxTextureSize = 256;
                break;
            case 4:
                textureImporter.maxTextureSize = 512;
                break;
            case 5:
                textureImporter.maxTextureSize = 1024;
                break;
            case 6:
                textureImporter.maxTextureSize = 2048;
                break;
            case 7:
                textureImporter.maxTextureSize = 4096;
                break;
        }
        //Format
        switch (FormatInt)
        {
            case 0:
                textureImporter.textureFormat = TextureImporterFormat.AutomaticCompressed;
                break;
            case 1:
                textureImporter.textureFormat = TextureImporterFormat.Automatic16bit;
                break;
            case 2:
                textureImporter.textureFormat = TextureImporterFormat.AutomaticTruecolor;
                break;
        }
        return textureImporter;
    }
    
    /// <summary>
    /// 循环设置选择的贴图
    /// </summary>
    private void LoopSetTexture()
    {
        Object[] textures = GetSelectedTextures();
        Selection.objects = new Object[0];
        foreach (Texture2D texture in textures)
        {
            string path = AssetDatabase.GetAssetPath(texture);
            TextureImporter texImporter = GetTextureSettings(path);
            TextureImporterSettings tis = new TextureImporterSettings();
            texImporter.ReadTextureSettings(tis);
            texImporter.SetTextureSettings(tis);
            AssetDatabase.ImportAsset(path);
        }
    }
    
    /// <summary>
    /// 获取选择的贴图
    /// </summary>
    /// <returns></returns>
    private Object[] GetSelectedTextures()
    {
        return Selection.GetFiltered(typeof(Texture2D), SelectionMode.DeepAssets);
    }
}
复制代码

2016-01-08 11:35:12 andyhebear 阅读数 8818

最近在做项目的过程中遇到这样的一个需求:玩家可以在游戏过程中进行实时存档,在存档过程中会保存当前游戏进度,同时会截取当前游戏画面并加载到游戏存档界面中。当下一次进入游戏的时候,将读取本地存档图片并加载到游戏界面中。这在单机游戏中是特别常见的一种功能,这里主要有两个关键点。首先是截取游戏画面,这个问题大家可以在《Unity教程之-Unity3d游戏开发之截屏保存精彩瞬间》这篇文章中找到答案。其次是从本地加载图片,因为这里要保证可读可写,因此传统的Resources.Load()方式和AssetBundle方式均无法实现这样的功能。那么怎样从外部加载图片到游戏中,这就是我们今天要讨论的内容啦。好了,这里介绍两种方法来实现这一目的。

喜闻乐见的WWW方式

喜闻乐见的WWW方式之所以喜闻乐见,这是因为这是我们最为熟悉的一种,我们都知道通过WWW可以从网络上加载文本、图片、音频等形式的内容,那么通过WWW能否加载本地外部(相对于应用程序)资源呢?答案是肯定的,这是因为WWW可以支持http和file两种协议。我们通常接触到的WWW默认都是指http协议,现在我们来说说file协议,该协议可以用来访问本地资源(绝对路径)。例如我们希望加载文件D:\TestFile\pic001.png这个文件,则此时对应的C#脚本为:

//请求WWW
WWW www = new WWW("file://D:\\TestFile\\pic001.png);
yield return www;        
if(www != null && string.IsNullOrEmpty(www.error))
{
    //获取Texture
    Texture texture=www.texture;   
    //更多操作...       
}

注意到这里出现了yield return结构,这表示这里使用到了协程,因此我们需要付出的代价就是需要在项目中使用StartCoroutine等协程相关的方法来调用这些协程。虽然在Unity3D中使用协程是件简单的事情,可是如果我们随随便便地使用协程而不注意去维护这些协程,那么这些让我们引以为傲的简单代码可能就会变成我们痛苦不堪的无尽深渊。

亘古不变的传统IO方式

好了,下面我们隆重推出亘古不变的传统IO方式,这种方式相信大家都没有接触过,所以这里将这种方法和大家分享。既然是传统的IO方式,那么无非就是各种IO流的处理啦。好,我们一起来看下面这段代码:

//创建文件读取流
FileStream fileStream = new FileStream(screen, FileMode.Open, FileAccess.Read);
fileStream.Seek(0, SeekOrigin.Begin);
//创建文件长度缓冲区
byte[] bytes = new byte[fileStream.Length]; 
//读取文件
fileStream.Read(bytes, 0, (int)fileStream.Length);
//释放文件读取流
fileStream.Close();
fileStream.Dispose();
fileStream = null;

//创建Texture
int width=800;
int height=640;
Texture2D texture = new Texture2D(width, height);
texture.LoadImage(bytes);

可以看到在使用这种方式读取图片文件的时候主要是将图片文件转化为byte[]数组,再利用Texture2D的LoadImage方法转化为Unity3D中的Texture2D。这种方法需要在创建过程中传入图片的大小,在这里我们创建了一张800X640的图片。经过博主的研究发现,这种方式加载外部图片相对于使用WWW加载外部图片效率更高,所以如果大家遇到类似的需求,博主个人推荐大家使用这种方式进行加载。

到目前为止我们解决了如何从外部加载图片到Unity3D中,现在我们回到最开始的问题,我们从外部读取到这些图片以后需要将它们加载到游戏界面中。比如当我们使用UGUI的时候,UGUI中的Image控件需要一个Sprite来作为它的填充内容,那么此时我们就需要将Texture转化为Sprite.号了,下面我们给出一个简单的例子:

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using System.IO;

public class TestLoading : MonoBehaviour 
{
    /// <summary>
    /// Image控件
    /// </summary>
    private Image image;

    void Start () 
    {
        image = this.transform.Find("Image").GetComponent<Image>();

        //为不同的按钮绑定不同的事件
        this.transform.Find("LoadByWWW").GetComponent<Button>().onClick.AddListener
        (
           delegate(){LoadByWWW();}
        );

        this.transform.Find("LoadByIO").GetComponent<Button>().onClick.AddListener
        (
          delegate(){LoadByIO();}
        );
    }

    /// <summary>
    /// 以IO方式进行加载
    /// </summary>
    private void LoadByIO()
    {
        double startTime = (double)Time.time;
        //创建文件读取流
        FileStream fileStream = new FileStream("D:\\test.jpg", FileMode.Open, FileAccess.Read);
        fileStream.Seek(0, SeekOrigin.Begin);
        //创建文件长度缓冲区
        byte[] bytes = new byte[fileStream.Length];
        //读取文件
        fileStream.Read(bytes, 0, (int)fileStream.Length);
        //释放文件读取流
        fileStream.Close();
        fileStream.Dispose();
        fileStream = null;

        //创建Texture
        int width = 300;
        int height = 372;
        Texture2D texture = new Texture2D(width, height);
        texture.LoadImage(bytes);

        //创建Sprite
        Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
        image.sprite = sprite;

        startTime=(double)Time.time-startTime;
        Debug.Log("IO加载用时:" + startTime);
    }

    /// <summary>
    /// 以WWW方式进行加载
    /// </summary>
    private void LoadByWWW()
    {
        StartCoroutine(Load());
    }

    IEnumerator Load()
    {
        double startTime = (double)Time.time;
        //请求WWW
        WWW www = new WWW("file://D:\\test.jpg");
        yield return www;        
        if(www != null && string.IsNullOrEmpty(www.error))
        {
            //获取Texture
            Texture2D texture=www.texture;

            //创建Sprite
            Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
            image.sprite = sprite;

            startTime = (double)Time.time - startTime;
            Debug.Log("WWW加载用时:" + startTime);
        }
    }
}

现在我们运行程序可以发现两种方式均可以让图片加载进来,为了对比两种方式在执行效率上的高低,我们在脚本中加入了相关代码,通过对比可以发现使用IO方式加载一张227k的图片需要的时间为0s,而使用WWW方式加载需要0.0185s,因此传统的IO方式具有更高的效率,建议大家在遇到这类问题时尽可能地使用这种方式。

没有更多推荐了,返回首页