https://bbs.pediy.com/thread-212532.htm

【文章标题】: 基于unity3d游戏的android版本逆向初探

【文章作者】: dreaman

【作者邮箱】: [email]dreaman_163@163.com[/email]

【作者主页】: https://github.com/dreamanlan

【软件名称】: 匿了

【软件大小】: 好几百MB

【下载地址】: 自己搜索下载

【加壳方式】: 梆梆加密

【保护方式】: 梆梆

【编写语言】: unity3d

【使用工具】: 见后面总结

【操作平台】: android

【软件介绍】: 一款MMO游戏

【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!

--------------------------------------------------------------------------------

【详细过程】

  0、背景

  最近某游戏老厂出了一款MMO手游,听说画面很好,是基于unity3d的,很想看一下都是怎样的效果,但可惜

  测试时间太短而且还要激活码,等我装好apk,进去时发现我既没有激活码,而且测试也结束了。。不过登录

  的界面看起来确实很好,所以,只好逆向一下看看。

  10年前我们在微软的dotnet平台下研究过一些安全相关的东东,10年后由于mono项目与unity3d的流行,dotnet

  技术竟然在移动平台流行起来,凭着当年断断续续的记忆与一些文档,我就这么搞了一次游戏逆向,呵呵。

  

  在移动平台,dotnet dll是没有签名的,这在一定程度上让逆向与修改更容易了。

  

  本文简要介绍一下本次逆向的过程,与游戏相关的内容就略去了。

  

  1、apk解包、重打包与签名

  使用“Android逆向助手”或ApkStudio都可以,我们使用ApkStudio解包,Android逆向助手重新打包并签名。

  

  2、梆梆加密解密

  解包后的一级目录如下:

    AndroidManifest.xml

    apktool.yml

    assets/

    build/

    lib/

    original/

    res/

    smali/

  首先用Reflector或IlSpy打开assets/bin/Data/Managed目录下的Assembly-CSharp.dll这个标准的unity3d游戏模块,

  打开失败,文件加密了。。

  打开lib/armeabi-v7a目录,可以看到依赖的so文件如下:

    libAkSoundEngine.so

    libBlueDoveMediaRender.so

    libCrasheyeNDK.so

    libDexHelper.so

    libKGAudio.so

    libmain.so

    libmono.so

    libmsc.so

    libslua.so

    libunity.so

    libuwa.so

    libweibosdkcore.so

  

  从文件名看了看,多数是功能性模块,只有libDexHelper.so比较可疑,上网搜了一下知道是梆梆的东东。网上没找到

  梆梆用于unity3d游戏的加密原理与方案等信息,只能自己看看了。

  

  这时候得用上ida pro了,我用的是6.6版本,首先看一下libmono.so,这个是mono的运行时库,对dotnet的加载及运行

  支持都在这里,直接看一下mono_image_open_from_data_with_name函数,看了一下,没有明显被修改的痕迹,又找了

  几个相关的加载相关函数,都没发现修改痕迹。。

  

  怀疑是不是根本没有修改libmono.so,unity3d的版本信息在其资源文件里比较容易查到,随便打开assets/bin/Data下

  的某个assets后缀的文件(记得用十六进制编辑器),可以在文件开头看到5.3.3p2,居然用的是一个补丁版本而不是f1

  这种正式版本,去unity3d网站下个对应版本的编辑器+android发布包,安装后找到

  Unity5.3.3p2\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\mono\Release\Libs\armeabi-v7a\libmono.so

  用Beyond Compare等支持二进制比较的工具与游戏里的libmono.so对比一下,完全相同。看来梆梆是不会静态修改

  libmono.so的了,想想也是,像unity3d这种版本频繁更新的,梆梆要是每个版本都静态改一下,还是挺被动的。不过

  话说某厂的加密就是静态修改的libmono.so(其实是自己修改源码了重新编译的)。

  

  搞不清libDexHelper.so是怎么工作的,我不是专业搞逆向的,用ida pro打开看了看,没发现线索就放弃了

  (在ida pro里没找到Assembly-CSharp.dll这样的字符串,不过用十六进制编辑器在libDexHelper.so文件尾是看到有

  这个字符串的,有兴趣的同学可以研究下它的加密原理)。

  

  我换了个思路来得到解密后的dll,就是从libmono.so动手,前面已经发现梆梆没有对这个文件进行静态处理,所以我们

  想怎么处理都比较容易,看mono的源码知道在mono_image_open_from_data_with_name函数里dll文件的内容会以完整的内

  存映像出现。

  

  所以至少有几种办法得到解密后的dll:

  1)、断点后dump内存,我用的是电脑上的虚拟机,断不了。。

  2)、修改mono源码,加入dump代码再替换游戏的libmono.so,这个理论上是可行的,但为了这么点事有点费劲了

  3)、直接修改libmono.so,手动打补丁,看了下mono源码后,发现mono_image_open_from_data_with_name函数的开头有

  一段判空检查,正常情况没什么用,看了下ida pro里这段代码占用的字节数,足够打补丁的了:)

  

  我们还需要找一个能放我们补丁代码的地方,这需要一个不怎么被使用的函数,连蒙代猜的,我选择了

  mono_load_remote_field,这个函数的空间足够写很多代码了。

  

  我们要在函数开头加的代码如下:

    

    if(data_len>6000000){

      FILE* fp = fopen("/data/local/tmp/test.dll","wb");

      if(fp){

        fwrite(data,1,data_len,fp);

        fclose(fp);

      }

    }

  

  开始的字节数判断是为了只dump想解密的dll,因为这个dll个头很大,用大小就可以判断了,这样还比较省字节数:)

  我们要手动打补丁,流程大概如下:

  1)、先翻译成字节码,这里我使用ADS 1.2来编译代码片断,字节码与反汇编如下:

      $a

      .text

          0x00000000:    e92d4070    p@-.    STMFD    r13!,{r4-r6,r14}

          0x00000004:    e1a06000    .`..    MOV      r6,r0

          0x00000008:    e59f003c    <...    LDR      r0,0x4c

          0x0000000c:    e1a05001    .P..    MOV      r5,r1

          0x00000010:    e1510000    ..Q.    CMP      r1,r0

          0x00000014:    98bd8070    p...    LDMLSFD  r13!,{r4-r6,pc}

          0x00000018:    e28f0034    4...    ADD      r0,pc,#0x34
; #0x54

          0x0000001c:    e28f102c    ,...    ADD      r1,pc,#0x2c
; #0x50

          0x00000020:    ebfffffe    ....    BL       fopen

          0x00000024:    e1b04000    .@..    MOVS     r4,r0

          0x00000028:    08bd8070    p...    LDMEQFD  r13!,{r4-r6,pc}

          0x0000002c:    e1a03004    .0..    MOV      r3,r4

          0x00000030:    e1a02005    . ..    MOV      r2,r5

          0x00000034:    e3a01001    ....    MOV      r1,#1

          0x00000038:    e1a00006    ....    MOV      r0,r6

          0x0000003c:    ebfffffe    ....    BL       fwrite

          0x00000040:    e1a00004    ....    MOV      r0,r4

          0x00000044:    e8bd4070    p@..    LDMFD    r13!,{r4-r6,r14}

          0x00000048:    eafffffe    ....    B        fclose

      $d

          0x0000004c:    005b8d80    ..[.    DCD    6000000

          0x00000050:    00006277    wb..    DCD    25207

          0x00000054:    7461642f    /dat    DCD    1952539695

          0x00000058:    6f6c2f61    a/lo    DCD    1869360993

          0x0000005c:    2f6c6163    cal/    DCD    795631971

          0x00000060:    2f706d74    tmp/    DCD    795897204

          0x00000064:    74736574    test    DCD    1953719668

          0x00000068:    6c6c642e    .dll    DCD    1819042862

          0x0000006c:    00000000    ....    DCD    0

  

  还挺好的,恰好把字符串放在代码后面了,特别适合在代码里打补丁。

  

  2)、然后再拷到目标函数里

  这里我用的Hex workshop,先在ida pro里找到函数对应的起始位置,然后把之前说的那段没什么用的代码nop掉,加一个

到mono_load_remote_field的调用,再把前面的字节码粘贴到函数mono_load_remote_field开头就可以了。

  

  3)、再对系统函数手动重定位一下。。

  因为我们用到了3个c语言库函数fopen/fwrite/fclose,为啥用这3个函数呢,因为一般的程序都应该引入了这个库,这样

  就只需要修改一下偏移就好了(实际上ADS编译出来的指令里这几个调用也是预留给后面重定位的),在ida pro里找一下

  这三个函数的导入代码(就是三个过程)的地址,然后计算一下三个调用处到目标的偏移(这里有一点没搞明白,必须用

  “目标地址 - 调用指令地址 - 8”才对,查ARM手册也没见有这需求,谁要是清楚麻烦告诉我),修改指令的后3个字节

  为偏移即可。

  

  修改后的mono_image_open_from_data_with_name

  .text:00190A94

  .text:00190A94 ; =============== S U B R O U T I N E =======================================

  .text:00190A94

  .text:00190A94 ; Attributes: bp-based frame

  .text:00190A94

  .text:00190A94                 EXPORT mono_image_open_from_data_with_name

  .text:00190A94 mono_image_open_from_data_with_name     ; CODE XREF:
sub_133E34+170p

  .text:00190A94                                         ; mono_image_open_from_data_full+3Cp

  .text:00190A94

  .text:00190A94 var_24          = -0x24

  .text:00190A94 var_20          = -0x20

  .text:00190A94 n               = -0x1C

  .text:00190A94 src             = -0x18

  .text:00190A94 var_10          = -0x10

  .text:00190A94 var_C           = -0xC

  .text:00190A94 dest            = -8

  .text:00190A94 arg_0           =  4

  .text:00190A94 arg_4           =  8

  .text:00190A94

  .text:00190A94                 STMFD           SP!, {R11,LR}

  .text:00190A98                 ADD             R11, SP, #4

  .text:00190A9C                 SUB             SP, SP, #0x20

  .text:00190AA0                 STR             R0, [R11,#src]

  .text:00190AA4                 STR             R1, [R11,#n]

  .text:00190AA8                 STR             R2, [R11,#var_20]

  .text:00190AAC                 STR             R3, [R11,#var_24]

  .text:00190AB0                 LDR             R2, [SP,#0x24+src]

  .text:00190AB4                 BL              mono_load_remote_field

  .text:00190AB8                 CMP             R3, #0

  .text:00190ABC                 CMP             R3, #0

  .text:00190AC0                 CMP             R3, #0

  .text:00190AC4                 CMP             R3, #0

  .text:00190AC8                 CMP             R3, #0

  .text:00190ACC                 CMP             R3, #0

  .text:00190AD0                 CMP             R3, #0

  .text:00190AD4                 CMP             R3, #0

  .text:00190AD8                 CMP             R3, #0

  .text:00190ADC                 CMP             R3, #0

  .text:00190AE0                 CMP             R3, #0

  .text:00190AE4                 CMP             R3, #0

  ;后面是原来的代码了

  .text:00190AE8                 LDR             R3, [R11,#src]

  ...

  

  修改后的mono_load_remote_field

  .text:001F9A4C

  .text:001F9A4C ; =============== S U B R O U T I N E =======================================

  .text:001F9A4C

  .text:001F9A4C

  .text:001F9A4C                 EXPORT mono_load_remote_field

  .text:001F9A4C mono_load_remote_field                  ; CODE XREF:
mono_image_open_from_data_with_name+20p

  .text:001F9A4C                 STMFD           SP!, {R4-R6,LR}

  .text:001F9A50                 MOV             R6, R0

  .text:001F9A54                 LDR             R0, =0x5B8D80

  .text:001F9A58                 MOV             R5, R1

  .text:001F9A5C                 CMP             R1, R0

  .text:001F9A60                 LDMLSFD         SP!, {R4-R6,PC}

  .text:001F9A64                 ADR             R0, aDataLocalTmpTe
; "/data/local/tmp/test.dll"

  .text:001F9A68                 ADR             R1, dword_1F9A9C
; modes

  .text:001F9A6C                 BL              fopen

  .text:001F9A70                 MOVS            R4, R0

  .text:001F9A74                 LDMEQFD         SP!, {R4-R6,PC}

  .text:001F9A78                 MOV             R3, R4  ; s

  .text:001F9A7C                 MOV             R2, R5  ; n

  .text:001F9A80                 MOV             R1, #1  ; size

  .text:001F9A84                 MOV             R0, R6  ; ptr

  .text:001F9A88                 BL              fwrite

  .text:001F9A8C                 MOV             R0, R4  ; stream

  .text:001F9A90                 LDMFD           SP!, {R4-R6,LR}

  .text:001F9A94                 B               fclose

  .text:001F9A94 ; End of function mono_load_remote_field

  .text:001F9A94 ; ---------------------------------------------------------------------------

  .text:001F9A98 dword_1F9A98    DCD 0x5B8D80            ; DATA XREF:
mono_load_remote_field+8r

  .text:001F9A9C dword_1F9A9C    DCD 0x6277              ; DATA XREF:
mono_load_remote_field+1Co

  .text:001F9AA0 aDataLocalTmpTe DCB "/data/local/tmp/test.dll",0

  .text:001F9AA0                                         ; DATA XREF:
mono_load_remote_field+18o

  .text:001F9AB9                 DCB 0, 0, 0

  .text:001F9ABC ; ---------------------------------------------------------------------------

  ;后面是原来的代码了

  .text:001F9ABC                 MOV             R2, R3

  .text:001F9AC0                 LDR             R3, =(aObject_c
- 0x1F9ACC)

  .text:001F9AC4                 ADD             R3, PC, R3 ; "object.c"

  .text:001F9AC8                 BL              sub_29D7F8

  ...

  

  现在重新打包、签名后应该已经可以得到解密的dll了。只是还不能进入游戏。。

  

  3、去除对梆梆so的依赖

  这个就是按网上说的做就可以了(对manifest的处理有点忘了具体的修改点了。。)

  1)、修改解出的包里AndroidManifest.xml,把里面对梆梆的Activity去掉。

  2)、修改解压出的smali文件,主要在smali\com\secneo\apkwrapper目录下,把加载DexHelper的代码注掉。

  

  现在再重新打包、签名后在虚拟机里安装apk后运行,可以进游戏了(因为之前已经把dll解密了,这次要把libmono.so换

  成原版unity3d的)。

  

  4、编写自己的调试模块

  呵呵,终于可以从ARM的汇编回到人类世界了。

  现在游戏的dll已经解密,并且重新打包后也可以运行了。所以我们可以试着在逻辑上打补丁了,好吧,其实我是为了研究

  一下它有没有什么新技巧。

  编程序的事情就不说了,大概就是基于DebugConsole.cs (http://wiki.unity3d.com/index.php?title=DebugConsole),

  然后添加一些我们需要的命令,比如动态加载一个Assembly,再比如利用reflection API调用函数,嗯,这在android上确

  实是可行的,对dotnet来说,这基本不是事(所以我就不写细节,要注意的就是读dll一定要先用文件API读出byte[],然

  后再Assembly.Load。另外一个是dll所在的目录必须是有权限读的,比如sdcard上的目录就比较好)

  

  5、合并调试模块到目标游戏

  我们基于DebugConsole.cs修改编译了一个自己的dll,下一步就是把这个dll合并到目标Assembly-CSharp.dll里并且修改

  Assembly-CSharp.dll里的运行时会走到的代码来调用我们的代码了。

  合并dll有微软开发好的非常牛的工具(微软研究院经常会做一些很奇怪的事情,比如Detours项目,再比如这个IlMerge工具)

  这个工具除了会合并dotnet dll文件外,其实它还是一个dotnet PE文件处理的源码库(其实找不到源码,不过用IlSpy或

  Reflector看基本上也没障碍),另外,它还可以用来重新整理我们修改过的dotnet可执行文件。

  

  6、修改目标游戏代码调用调试模块

  这一步我采取了自制工具的方式,要手动处理的话,用CFF Explore也是可以的,这里就不详说了。

  简单说一下我们实际做的事:

  1)、游戏的启动类Game,在Update里调用了TestInput

  private void Update()

  {

      try

      {

          ...

          this.TestInput();

      }

      catch (Exception exception)

      {

          Log.Exception(exception);

      }

  }

  

  private void TestInput()

  {

  }

  

  TestInput是一个空函数,这个函数不会访问任何Game实例的变量,和静态函数效果一样,我们将它的方法体替换成我们提

  供的一个函数(就是前面用IlMerge合并到Assembly-CSharp.dll里的代码),这样我们静态注入的代码就有了执行的机会:

  private void TestInput()

  {

      DebugConsoleHelper.Init(base.gameObject);

      DebugConsoleHelper.Tick();

  }

  

  修改的原理是将Game类的TestInput方法元数据里的RVA改为GamePatch即我们合并进的类的TestInput方法的RVA。

  修改后,Game.TestInput与GamePatch.TestInput方法其实是共享了同一块指令,这在dotnet PE文件里是没问题的。

  这一步用CFF Explore手动修改也比较容易,因为只是替换一下元数据。

  我因为学习的需要要多次修改dll,所以用了自己的工具执行一段脚本来自动处理(见后)。

  

  2)、游戏的PlayerController类的Update函数里,我们需要修改一下来实现移动(主要用于没有连接服务器的情况下浏览场景)

  private void Update()

  {

      if (activeController == this)

      {

          this.ProcessJoystick();

          this.m_moveElapseTime += Time.deltaTime;

          if (this.m_moveElapseTime > m_moveInterval)

          {

              DebugConsoleHelper.Move(this.m_player, this.m_moveElapseTime);

              this.m_moveElapseTime = 0f;

          }

      }

      this.ProcessSkillCD();

  }

  

  这里需要修改字节码来实现我们的代码,本质上与传统可执行文件的修改是一样,先NOP掉一段代码,然后写入我们的代码

  .method private hidebysig instance void Update() cil managed

  {

      .maxstack 8

      L_0000: call class PlayerController PlayerController::get_activeController()

      L_0005: ldarg.0 

      L_0006: call bool [UnityEngine]UnityEngine.Object::op_Equality(class
[UnityEngine]UnityEngine.Object, class [UnityEngine]UnityEngine.Object)

      L_000b: brfalse L_0076

      L_0010: ldarg.0 

      L_0011: call instance void PlayerController::ProcessJoystick()

      L_0016: ldarg.0 

      L_0017: dup 

      L_0018: ldfld float32 PlayerController::m_moveElapseTime

      L_001d: call float32 [UnityEngine]UnityEngine.Time::get_deltaTime()

      L_0022: add 

      L_0023: stfld float32 PlayerController::m_moveElapseTime

      L_0028: ldarg.0 

      L_0029: ldfld float32 PlayerController::m_moveElapseTime

      L_002e: ldsfld float32 PlayerController::m_moveInterval

      L_0033: ble.un L_0076

  ;下面是我们修改的代码

      L_0038: ldarg.0 

      L_0039: ldfld class Player PlayerController::m_player

      L_003e: ldarg.0 

      L_003f: ldfld float32 PlayerController::m_moveElapseTime

      L_0044: call void DebugConsoleHelper::Move(class Player, float32)

      L_0049: nop 

      L_004a: nop 

      L_004b: nop 

      L_004c: nop 

      L_004d: nop 

      L_004e: nop 

      L_004f: nop 

      L_0050: nop 

      L_0051: nop 

      L_0052: nop 

      L_0053: nop 

      L_0054: nop 

      L_0055: nop 

      L_0056: nop 

      L_0057: nop 

      L_0058: nop 

      L_0059: nop 

      L_005a: nop 

      L_005b: nop 

      L_005c: nop 

      L_005d: nop 

      L_005e: nop 

      L_005f: nop 

      L_0060: nop 

      L_0061: nop 

      L_0062: nop 

      L_0063: nop 

      L_0064: nop 

      L_0065: nop 

      L_0066: nop 

      L_0067: nop 

      L_0068: nop 

      L_0069: nop 

      L_006a: nop 

  ;修改结束

      L_006b: ldarg.0 

      L_006c: ldc.r4 0

      L_0071: stfld float32 PlayerController::m_moveElapseTime

      L_0076: ldarg.0 

      L_0077: call instance void PlayerController::ProcessSkillCD()

      L_007c: ret 

  }

  

  这一步我也是用我的自制工具自动处理的,用CFF Explore与Hex workshop手动修改也可以,但如果需要多次修改的话就

  有点烦了。

  

  7、自制工具

  前面说到的方法体替换与方法代码修改我自己做了一个小工具

  (是在10年前的DeObfuscator上修改而成:http://bbs.pediy.com/showthread.php?threadid=34127)

  

  对于方法体替换,由于多个类会共享方法体,所以这样的方法不能访问类的实例变量,也就是一般要用在静态方法上。

  方法体替换在工具里是直接支持的,添加好目标文件后,输入被替换类与替换类,点“方法实现替换”即可。

  

  这个小工具主要是通过脚本来自动处理上面的修改,脚本是基于我的另一个开源项目DSL实现的。

  本文涉及的修改使用的脚本如下:

  proc(main)

  {

          $files = getfilelist();

          begin("开始脚本处理");

          looplist($files){

                  $file=$$;

                  beginfile($file,"开始对"+$file+"进行修改。。。");

                  beginreplace($file);

                  replace($file,"Game","GamePatch");

                  endreplace($file);

                  beginmodify($file);

                  writeloadarg($file,"PlayerController","Update",0x38,0);

                  writeloadfield($file,"PlayerController","Update",0x39,"PlayerController","m_player");

                  writeloadarg($file,"PlayerController","Update",0x3e,0);

                  writeloadfield($file,"PlayerController","Update",0x3f,"PlayerController","m_moveElapseTime");

                  writecall($file,"PlayerController","Update",0x44,"DebugConsoleHelper","Move");

                  writenops($file,"PlayerController","Update",0x49,0x22);

                  endmodify($file);

                  endfile($file);

          };

          end("结束脚本处理");

  };

  

  这个小工具我已经开源了,欢迎使用:https://github.com/dreamanlan/DotnetPatch

  考虑到伟大的墙,我在看雪也放一个目前的版本。  

  

--------------------------------------------------------------------------------

【经验总结】

  所用工具列表:

  1、ApkStudio

  2、Android逆向助手

  3、Reflector 8

  4、ida pro 6.6

  5、hex workshop

  6、cff explore

  7、IlMerge

  8、ADS 2.1

  9、自制工具DotnetPatch

  

--------------------------------------------------------------------------------

【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!



                  2016年09月01日 20:23:13

上传的附件:

基于unity3d游戏的android版本逆向初探的更多相关文章

  1. [Unity3D]Unity3D游戏开发Android内嵌视图Unity查看

    ---------------------------------------------------------------------------------------------------- ...

  2. 【转】 各种 基于Unity3d 引擎的Android游戏优化 (drawcall)

     合并纹理,减少贴图数量,合并网格,ui上减少不必要的层级叠加关系等   1. 更新不透明贴图的压缩格式为ETC 4bit,因为android市场的手机中的GPU有多种,每家的GPU支持不同的压缩格式 ...

  3. 基于Unity3d 引擎的Android游戏优化

    原文地址:http://blog.csdn.net/jixuguo/article/details/9018669 近期项目进入收尾阶段,之前对项目做了非常多优化,mesh合并 .降低DrawCall ...

  4. [Unity3D]Unity3D游戏开发之跑酷游戏项目解说

    大家好,我是秦元培.我參加了CSDN2014博客之星的评选,欢迎大家为我投票,同一时候希望在新的一年里大家能继续支持我的博客. 大家晚上好.我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.c ...

  5. Unity3D游戏开发初探—2.初步了解3D模型基础

    一.什么是3D模型? 1.1 3D模型概述 简而言之,3D模型就是三维的.立体的模型,D是英文Dimensions的缩写. 3D模型也可以说是用3Ds MAX建造的立体模型,包括各种建筑.人物.植被. ...

  6. [Unity3D]Unity3D游戏开发之Unity与Android交互调用研究

    各位朋友,大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei.在前一篇文章中,我们研究了Android平台上Unity3D的手势操作并在之前的基础 ...

  7. Unity3D游戏开发之Unity与Android交互调用研究

    各位朋友,大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei.在前一篇文章中,我们研究了Android平台上Unity3D的手势操作并在之前的基础 ...

  8. 将Unity3D游戏移植到Android平台上

    将Unity3D游戏移植到Android平台是一件很容易的事情,只需要在File->Build Settings中选择Android平台,然后点击Switch Platform并Build出ap ...

  9. Unity3D游戏GC优化总结---protobuf-net无GC版本优化实践

    protobuf-net优化效果图 protobuf-net是Unity3D游戏开发中被广泛使用的Google Protocol Buffer库的c#版本,之所以c#版本被广泛使用,是因为c++版本的 ...

随机推荐

  1. HDU 5247 找连续数 (set妙用)

    找连续数 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submi ...

  2. GStreamer 从摄像头获取图像 转264

    1.这里有个简单的例子,可以看看GStreamer如何编程的. 2.GStreamer  GstAppSink的官方Document,翻译了一下它的描述部分,点击这里. 3.GStreamer  Gs ...

  3. 【shell】awk引用外部变量

    在使用awk的过程中,经常会需要引用外部变量,但是awk需要使用单引号将print包起来,导致print后的$引用无效,可以采用下面的方式 例如: #!/bin/bash a="line1 ...

  4. 小记tensorflow-1:tf.nn.conv2d 函数介绍

    tf.nn.conv2d函数介绍 Input: 输入的input必须为一个4d tensor,而且每个input的格式必须为float32 或者float64. Input=[batchsize,im ...

  5. Java for LeetCode 102 Binary Tree Level Order Traversal

    Given a binary tree, return the level order traversal of its nodes' values. (ie, from left to right, ...

  6. 微信公众号验证TOKEN

    服务端验证微信token header('Content-type:text'); define("TOKEN", "weixin"); $signature ...

  7. 纯CSS3实现淡入淡出下拉菜单

    纯CSS3实现淡入淡出下拉菜单是一款比较简单清新的CSS3教程下拉菜单,这款下拉菜单是垂直方向的,点击主菜单项可以展开和折叠子菜单,在展开折叠的过程中伴随着淡入淡出的动画效果 源代码:http://w ...

  8. Servlet传递数据方式

    Servlet传递数据方式 基本概述 Servlet传递数据的方式有很多,这里提供五种方式: 1.静态变量 2.HttpServletResponse的sendRedirect()方法 3.HttpS ...

  9. Java_正则_00_资源贴

    二.参考资料 1.揭开正则表达式的神秘面纱

  10. Linux_服务器_03_xxx is not in the sudoers file.This incident will be reported.的解决方法

    1.切换到root用户下,怎么切换就不用说了吧,不会的自己百度去. 2.添加sudo文件的写权限,命令是:chmod u+w /etc/sudoers 3.编辑sudoers文件vi /etc/sud ...