内容提要:Unity引擎下的代码保护,由于Unity引擎的一些特殊性,实行起来较为复杂,在国内外业界并没有现成的方案。笔者通过在《QQ乐团》项目上的实际尝试,得出了一种具体可行,能够有效保护代码逻辑的方案。特此分享给关注Unity引擎的项目,希望能提供一些的参考。

背景

Unity引擎上的程序执行在Mono运行时上,使用Mono编译出的程序集格式与.NET标准一致。C#是Unity引擎下主要的开发语言,它具备不少高级语言特性,如反射、元数据、内置序列化等。但C#同时也是很容易被反编译的语言,如果不采用任何保护措施,使用常用的工具(.NET Reflector)便能很容易得到可二次编译的代码。对项目运营带来了比较大的风险。

.NET平台下通常的保护手段是混淆编译出的程序集。VisualStudio自带了一个混淆工具Dotfuscator可以对程序集进行混淆。功能包括名称修改,流程混淆,字符串加密等。经过Dotfuscator混淆后的程序集,能够避免被常用反编译工具破解。变量的表意性被破坏,同时函数的内部流程也被混淆(如下[B1] )。能有效起到保护源代码的效果。

publicclass181: 218

{

// Fields

publicuint0;

publicushort1;

publicstaticreadonlyuint2;

publicstaticreadonlyuint3;

// Methods

static181();

public181();

public95.02();

public95.02(ref515A_0, uintA_1);

public95.02(79A_0, refuintA_1);

public95.02(ref79A_0, uintA_1);

public95.02(byte[] A_0, intA_1, refuintA_2);

public95.02(ref481A_0, intA_1, charA_2);

public95.02(refstringA_0, intA_1, charA_2);

public95.02(refbyte[] A_0, intA_1, refintA_2, uintA_3);

public95.03(ref79A_0, uintA_1);

public95.03(refbyte[] A_0, intA_1, refintA_2, uintA_3);

public95.04(refbyte[] A_0, intA_1, refintA_2, uintA_3);

}

public95.00(refsbyteA_0, intA_1)

{

// This item is obfuscated and can not be translated.

goto Label_0006;

if(1!= 0)

{

}

95.0local= 95.0.0;

bytenum= 0;

local = this.0(refnum,A_1);

A_0 = (sbyte) num;

returnlocal;

Unity引擎下,Mono编译出的程序集,由于采用与.NET相同的格式标准。能够直接被Dotfuscator混淆。但Unity引擎有一些特殊的地方,使混淆工作与一般的.NET程序存在差异。第三节将主要讨论这些特殊点。

Unity引擎下代码混淆的特殊性

代码被资源引用[B2] 。Unity的可视化编辑特性在设计上的关键之处在于使代码能够以组件的形式依附到资源实例上。相比传统游戏,Unity的两类资源(scene和prefab)不仅包括数据,还包括附加在资源上的类对象。也就是说,这两类资源的存储格式中存在唯一标识某代码类型的数据。混淆流程必须不破环这种对应关系才能使资源上的代码逻辑正确被执行。(Unity这样设计的意义并不是本文讨论的重点,而另一篇分享个人对Unity可视化编辑的理解的文章中将会详细说明。)

发布到Web的Unity项目,在生成播放器可执行包(*.unity)的接口中,将编译程序集和打包这两个步骤捆绑在的一起。我们没办法像普通.NET程序那样,对编译出的程序集进行混淆后再打到播放器可执行包中。

UnityEngine按函数名进行调用。MonoBehaviour是Unity引擎的一个重要的组件基类。其上的很多方法,Unity是通过方法名称进行访问的,如Awake、Start、Update等等。这些方法如果在混淆中被改名,将使方法调用失败。这个问题相对比较好处理,Dotfuscator的重命名功能提供了排除配置。我们只要得到继承于MonoBehaviour的所有类型,就能生成相应的排除配置,告知Dotfuscator不要对这些方法进行重命名。生成的配置节选如下[B3] :

<option>xmlserialization</option>

<excludelist>

<type name="CEventMgr|CGameRoot|…|…" regex="true" excludetype="false">

<method name="Update"regex="true" />

<method name="LateUpdate"regex="true" />

<method name="FixedUpdate"regex="true" />

<methodname="Awake" regex="true" />

<customattributename="System.Runtime.CompilerServices.CompilerGeneratedAttribute"regex="true" />

<method name=".*"regex="true" />

<field name=".*"regex="true" />

</type>

<type name=".*"regex="true">

<customattributename="ANoRenameInObfuscate" regex="true" />

</type>

<type name=".*"excludetype="false" regex="true">

<method name=".*"regex="true">

<customattributename="ANoRenameInObfuscate" regex="true" />

</method>

</type>

思路

何时混淆?由于Web项目编译和打包的过程是捆绑在一起的,官方没有提供独立的接口。(之前有跟官方反馈,但目前官方并没有提供具体计划。)想自己来分析官方的打包格式是行不通并且不太科学的。仅剩的办法就是自己将代码编译成DLL,混淆之后再添加到Unity项目中。

顺着这条思路,笔者在《QQ乐团》项目上作了尝试。将项目中所有执行相关的代码(不包括编辑器扩展的代码)移出,指定相关的Unity依赖库,编译成DLL。再将此DLL复制到原项目中。这时意料之中的事情发生了——项目中所有资源上的代码引用全部丢失。为了找到资源对代码的映射形式,笔者调整Unity编辑器的设定,将资源的序列化格式改为文本格式,并进行对比分析。发现资源中是通过一个GUID来对应具体代码的[B4] 。(如下)

m_ObjectHideFlags: 1

m_PrefabParentObject: {fileID: 0}

m_PrefabInternal: {fileID: 100100000}

m_GameObject: {fileID: 100000}

m_Enabled: 1

m_EditorHideFlags: 0

m_Script: {fileID:11500000, guid: 8ae38faa3fc9f91418a5a9872bcc4b0f, type: 1}

m_Name:

mInt: 1

mFloat: .5

中的类型虽然还没有进行过混淆,但GUID已经发生了变化。将新的GUID替换到资源文件中,引用关系果然恢复了。

Unity引擎下的特殊问题都是可以解决的。于是顺着这思路,开发了若干工具,得到了前后GUID的对应关系,并扫描所有资源以进行GUID的替换。另一方面,在混淆之后,类型的变量名发生了改变,资源中变量名赋有具体的值,也需要替换资源中的变量名对应到混淆后的变量名。这一切花费了不少的精力,终于是把工具都做成了。

然而人算不如天算,最终导致此方案走进死角的是一个之前很难意料到的问题:Unity引擎在处理DLL中的模版类型时存在缺陷——DLL中的模版类型没有GUID,不能被资源所引用。这个问题在Unity官方网站上有少量反馈,而官方承认了这个bug,且没有给出解决方案。而《QQ乐团》的项目在UI操作上比较广泛地使用了模版类型,去除模版的使用谈何容易。就这样,这么一个不经意的问题为这个尝试的方向画上了句号。

“系着枷锁跳舞”,这句话是形容的是在各种条件约束下尽可能的追求解决方案的一种状态。总结之前的失败,最终还是找到了实际可行的改进方案,并成功应用到《QQ乐团》的Web版本和微客户端版本上。

最终的思路是将项目进行分层。独立出一个不被资源引用的,包含最敏感的协议解析和各个系统模块的“逻辑层”,将逻辑层的代码独立编译成一个DLL,进行混淆再包含到项目中。逻辑层之外的代码主要包括被资源引用到的,或是系统模块部分接口定义这样的不太敏感的内容,姑且称为“行为层”。为了让逻辑层可以独立编译,我们要求逻辑层可对行为层进行引用,而行为层则只能通过留在行为层的逻辑层接口访问逻辑层。这样我们就保护了我们最重要的代码,同时绕过了资源引用代码的问题。

这个方案对项目架构提出了一定的要求。一是要求敏感代码和资源保持独立,需要一个框架来加载各个模块,而不是直接将模块代码直接附在场景物体的资源中。二是要求层次清晰,不允许反向依赖。有利于《QQ乐团》项目的消息是,《QQ乐团》从最早期就实现了一个较清晰的架构管理方法。因此花费了一定的时间进行分层,和实现接口访问机制后,就成功执行了这个方案。

实际混淆步骤。《QQ乐团》是使用VisualBuild来执行版本构建和发布流程的。以下介绍版本构建中混淆相关的流程:

从Unity项目的Assets目录中拷贝出逻辑层的代码目录(CodeGameLogic)。和编辑器扩展代码(避免混淆后编辑器扩展代码对逻辑层的依赖丢失导致编译出错)。

调用Unity.exe命令行编译剩余的行为层部分:

这个函数实际执行了:

BuildPipeline.BuildPlayer(new string[] {"Assets/obfuscated.unity" }, "WebPlayerObfuscated",

BuildTarget.WebPlayer, BuildOptions.None);

Editor程序集(也就是编辑器扩展程序集)时编译失败,中断编译过程,避免在BuildPlayer过程结束时构建生成的DLL被清理掉。BuildPlayer之前故意在Editor目录下弄一个错误的代码文件即可。

将生成的行为层DLL拷贝到逻辑层构建目录。行为层DLL的路径是在项目的Library/ScriptAssemblies下,有Assembly-CSharp.dll和Assembly-CSharp-firstpass.dll两个文件。另外也拷贝逻辑层依赖的其它DLL到构建目录,包括UnityEngine.dll,以及项目Plugins目录下的依赖库。

调用Mono的编译器mcs编译逻辑层DLL——CodeGameLogic.dll。编译命令如下:

生成DotObfuscator的配置文件”WebCfg.xml”。这里是用自己编写的工具,扫描CodeGameLogic.dll中的类型,得到不能被混淆的类型名和方法名,加入到配置文件的排出列表中。如“三。3”小节所示。

调用DotObfuscator对CodeGameLogic.dll执行混淆,得到混淆后的CodeGameLogic.dll:

将混淆后的CodeGameLogic.dll拷贝到项目中,然后构建项目。这里要注意的是,如果是构建Web项目,需要将dll拷贝到Plugins目录。如果是Standalone(即客户端)项目,直接拷贝到Assets目录下即可。另外,这次构建是不可以有编译错误的,所以第1部需要移除Editor目录下的编辑器扩展的代码。

接下来将构建好的项目与资源合并,就可以得到完整的混淆版本。

总结:

Unity项目的代码反编译较为容易。需要在重视代码混淆工作。

Unity项目的代码混淆方案实施起来限制较多。本文介绍的方案是笔者知晓的目前唯一可用的混淆方案。对项目的架构分层有强制性的要求。最好是在项目初期就考虑如何对项目进行分层,将需要保护的内容放置在被混淆的层中。

内容提要:Unity引擎下的代码保护,由于Unity引擎的一些特殊性,实行起来较为复杂,在国内外业界并没有现成的方案。笔者通过在《QQ乐团》项目上的实际尝试,得出了一种具体可行,能够有效保护代码逻辑的方案。特此分享给关注Unity引擎的项目,希望能提供一些的参考。

背景

Unity引擎上的程序执行在Mono运行时上,使用Mono编译出的程序集格式与.NET标准一致。C#是Unity引擎下主要的开发语言,它具备不少高级语言特性,如反射、元数据、内置序列化等。但C#同时也是很容易被反编译的语言,如果不采用任何保护措施,使用常用的工具(.NET Reflector)便能很容易得到可二次编译的代码。对项目运营带来了比较大的风险。

.NET平台下通常的保护手段是混淆编译出的程序集。VisualStudio自带了一个混淆工具Dotfuscator可以对程序集进行混淆。功能包括名称修改,流程混淆,字符串加密等。经过Dotfuscator混淆后的程序集,能够避免被常用反编译工具破解。变量的表意性被破坏,同时函数的内部流程也被混淆(如下[B1] )。能有效起到保护源代码的效果。

publicclass181: 218

{

// Fields

publicuint0;

publicushort1;

publicstaticreadonlyuint2;

publicstaticreadonlyuint3;

// Methods

static181();

public181();

public95.02();

public95.02(ref515A_0, uintA_1);

public95.02(79A_0, refuintA_1);

public95.02(ref79A_0, uintA_1);

public95.02(byte[] A_0, intA_1, refuintA_2);

public95.02(ref481A_0, intA_1, charA_2);

public95.02(refstringA_0, intA_1, charA_2);

public95.02(refbyte[] A_0, intA_1, refintA_2, uintA_3);

public95.03(ref79A_0, uintA_1);

public95.03(refbyte[] A_0, intA_1, refintA_2, uintA_3);

public95.04(refbyte[] A_0, intA_1, refintA_2, uintA_3);

}

public95.00(refsbyteA_0, intA_1)

{

// This item is obfuscated and can not be translated.

goto Label_0006;

if(1!= 0)

{

}

95.0local= 95.0.0;

bytenum= 0;

local = this.0(refnum,A_1);

A_0 = (sbyte) num;

returnlocal;

Unity引擎下,Mono编译出的程序集,由于采用与.NET相同的格式标准。能够直接被Dotfuscator混淆。但Unity引擎有一些特殊的地方,使混淆工作与一般的.NET程序存在差异。第三节将主要讨论这些特殊点。

Unity引擎下代码混淆的特殊性

代码被资源引用[B2] 。Unity的可视化编辑特性在设计上的关键之处在于使代码能够以组件的形式依附到资源实例上。相比传统游戏,Unity的两类资源(scene和prefab)不仅包括数据,还包括附加在资源上的类对象。也就是说,这两类资源的存储格式中存在唯一标识某代码类型的数据。混淆流程必须不破环这种对应关系才能使资源上的代码逻辑正确被执行。(Unity这样设计的意义并不是本文讨论的重点,而另一篇分享个人对Unity可视化编辑的理解的文章中将会详细说明。)

发布到Web的Unity项目,在生成播放可执行包(*.unity)的接口中,将编译程序集和打包这两个步骤捆绑在的一起。我们没办法像普通.NET程序那样,对编译出的程序集进行混淆后再打到播放可执行包中。

UnityEngine按函数名进行调用。MonoBehaviour是Unity引擎的一个重要的组件基类。其上的很多方法,Unity是通过方法名称进行访问的,如Awake、Start、Update等等。这些方法如果在混淆中被改名,将使方法调用失败。这个问题相对比较好处理,Dotfuscator的重命名功能提供了排除配置。我们只要得到继承于MonoBehaviour的所有类型,就能生成相应的排除配置,告知Dotfuscator不要对这些方法进行重命名。生成的配置节选如下[B3] :

<option>xmlserialization</option>

<excludelist>

<type name="CEventMgr|CGameRoot|…|…" regex="true" excludetype="false">

<method name="Update"regex="true" />

<method name="LateUpdate"regex="true" />

<method name="FixedUpdate"regex="true" />

<methodname="Awake" regex="true" />

<customattributename="System.Runtime.CompilerServices.CompilerGeneratedAttribute"regex="true" />

<method name=".*"regex="true" />

<field name=".*"regex="true" />

</type>

<type name=".*"regex="true">

<customattributename="ANoRenameInObfuscate" regex="true" />

</type>

<type name=".*"excludetype="false" regex="true">

<method name=".*"regex="true">

<customattributename="ANoRenameInObfuscate" regex="true" />

</method>

</type>

思路

何时混淆?由于Web项目编译和打包的过程是捆绑在一起的,官方没有提供独立的接口。(之前有跟官方反馈,但目前官方并没有提供具体计划。)想自己来分析官方的打包格式是行不通并且不太科学的。仅剩的办法就是自己将代码编译成DLL,混淆之后再添加到Unity项目中。

顺着这条思路,笔者在《QQ乐团》项目上作了尝试。将项目中所有执行相关的代码(不包括编辑扩展的代码)移出,指定相关的Unity依赖库,编译成DLL。再将此DLL复制到原项目中。这时意料之中的事情发生了——项目中所有资源上的代码引用全部丢失。为了找到资源对代码的映射形式,笔者调整Unity编辑的设定,将资源的序列化格式改为文本格式,并进行对比分析。发现资源中是通过一个GUID来对应具体代码的[B4] 。(如下)

m_ObjectHideFlags: 1

m_PrefabParentObject: {fileID: 0}

m_PrefabInternal: {fileID: 100100000}

m_GameObject: {fileID: 100000}

m_Enabled: 1

m_EditorHideFlags: 0

m_Script: {fileID:11500000, guid: 8ae38faa3fc9f91418a5a9872bcc4b0f, type: 1}

m_Name:

mInt: 1

mFloat: .5

中的类型虽然还没有进行过混淆,但GUID已经发生了变化。将新的GUID替换到资源文件中,引用关系果然恢复了。

Unity引擎下的特殊问题都是可以解决的。于是顺着这思路,开发了若干工具,得到了前后GUID的对应关系,并扫描所有资源以进行GUID的替换。另一方面,在混淆之后,类型的变量名发生了改变,资源中变量名赋有具体的值,也需要替换资源中的变量名对应到混淆后的变量名。这一切花费了不少的精力,终于是把工具都做成了。

然而人算不如天算,最终导致此方案走进死角的是一个之前很难意料到的问题:Unity引擎在处理DLL中的模版类型时存在缺陷——DLL中的模版类型没有GUID,不能被资源所引用。这个问题在Unity官方网站上有少量反馈,而官方承认了这个bug,且没有给出解决方案。而《QQ乐团》的项目在UI操作上比较广泛地使用了模版类型,去除模版的使用谈何容易。就这样,这么一个不经意的问题为这个尝试的方向画上了句号。

“系着枷锁跳舞”,这句话是形容的是在各种条件约束下尽可能的追求解决方案的一种状态。总结之前的失败,最终还是找到了实际可行的改进方案,并成功应用到《QQ乐团》的Web版本和微客户端版本上。

最终的思路是将项目进行分层。独立出一个不被资源引用的,包含最敏感的协议解析和各个系统模块的“逻辑层”,将逻辑层的代码独立编译成一个DLL,进行混淆再包含到项目中。逻辑层之外的代码主要包括被资源引用到的,或是系统模块部分接口定义这样的不太敏感的内容,姑且称为“行为层”。为了让逻辑层可以独立编译,我们要求逻辑层可对行为层进行引用,而行为层则只能通过留在行为层的逻辑层接口访问逻辑层。这样我们就保护了我们最重要的代码,同时绕过了资源引用代码的问题。

这个方案对项目架构提出了一定的要求。一是要求敏感代码和资源保持独立,需要一个框架来加载各个模块,而不是直接将模块代码直接附在场景物体的资源中。二是要求层次清晰,不允许反向依赖。有利于《QQ乐团》项目的消息是,《QQ乐团》从最早期就实现了一个较清晰的架构管理方法。因此花费了一定的时间进行分层,和实现接口访问机制后,就成功执行了这个方案。

实际混淆步骤。《QQ乐团》是使用VisualBuild来执行版本构建和发布流程的。以下介绍版本构建中混淆相关的流程:

Unity项目的Assets目录中拷贝出逻辑层的代码目录(CodeGameLogic)。和编辑扩展代码(避免混淆后编辑扩展代码对逻辑层的依赖丢失导致编译出错)。

调用Unity.exe命令行编译剩余的行为层部分:

这个函数实际执行了:

BuildPipeline.BuildPlayer(new string[] {"Assets/obfuscated.unity" }, "WebPlayerObfuscated",

BuildTarget.WebPlayer, BuildOptions.None);

Editor程序集(也就是编辑扩展程序集)时编译失败,中断编译过程,避免在BuildPlayer过程结束时构建生成的DLL被清理掉。BuildPlayer之前故意在Editor目录下弄一个错误的代码文件即可。

将生成的行为层DLL拷贝到逻辑层构建目录。行为层DLL的路径是在项目的Library/ScriptAssemblies下,有Assembly-CSharp.dll和Assembly-CSharp-firstpass.dll两个文件。另外也拷贝逻辑层依赖的其它DLL到构建目录,包括UnityEngine.dll,以及项目Plugins目录下的依赖库。

调用Mono的编译mcs编译逻辑层DLL——CodeGameLogic.dll。编译命令如下:

生成DotObfuscator的配置文件”WebCfg.xml”。这里是用自己编写的工具,扫描CodeGameLogic.dll中的类型,得到不能被混淆的类型名和方法名,加入到配置文件的排出列表中。如“三。3”小节所示。

调用DotObfuscator对CodeGameLogic.dll执行混淆,得到混淆后的CodeGameLogic.dll:

混淆后的CodeGameLogic.dll拷贝到项目中,然后构建项目。这里要注意的是,如果是构建Web项目,需要将dll拷贝到Plugins目录。如果是Standalone(即客户端)项目,直接拷贝到Assets目录下即可。另外,这次构建是不可以有编译错误的,所以第1部需要移除Editor目录下的编辑扩展的代码。

接下来将构建好的项目与资源合并,就可以得到完整的混淆版本。

总结:

Unity项目的代码反编译较为容易。需要在重视代码混淆工作。

Unity项目的代码混淆方案实施起来限制较多。本文介绍的方案是笔者知晓的目前唯一可用的混淆方案。对项目的架构分层有强制性的要求。最好是在项目初期就考虑如何对项目进行分层,将需要保护的内容放置在被混淆的层中。

unity3d 混淆相关推荐

  1. Unity3D代码混淆

    Unity代码混淆方案 内容提要:Unity引擎下的代码保护,由于Unity引擎的一些特殊性,实行起来较为复杂,在国内外业界并没有现成的方案.笔者通过在<QQ乐团>项目上的实际尝试,得出了 ...

  2. Android项目:proguard混淆之常见开源项目混淆配置

    1.Gson混淆 ## ---------------------------------- ## ########## Gson混淆 ########## ## ------------------ ...

  3. unity3d 数学基础与数学辅助类

    原文 http://my.oschina.net/u/243648/blog/67193 1.  数学(点乘/叉乘)/unity3d的数学辅助类 2.  坐标系统(本地/世界/屏幕) 3.  Unit ...

  4. 干货整理 Unity3D资源汇总

    Unity3D是目前最流行的游戏开发工具,是一个可以让玩家轻松创建诸如三维视频游戏.建筑可视化.实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎. 为了让开发者能够 ...

  5. android unity hook,[原创]Unity3d安卓游戏DLL动态调式与HOOK基础

    [原创]Unity3d安卓游戏DLL动态调式与HOOK基础 2016-4-4 02:40 8207 [原创]Unity3d安卓游戏DLL动态调式与HOOK基础 2016-4-4 02:40 8207 ...

  6. (转)Unity3d UnityEditor编辑器定制和开发插件

    在阅读本教程之前,你需要对Unity的操作流程有一些基础的认识,并且最好了解内置的GUI系统如何使用. 如何让编辑器运行你的代码 Unity3D可以通过事件触发来执行你的编辑器代码,但是我们需要一些编 ...

  7. unity3d 数学的数学基础和辅助类

    转载注明smartdot:http://my.oschina.net/u/243648/blog/67193 1.  数学(点乘/叉乘)/unity3d的数学辅助类 2.  坐标系统(本地/世界/屏幕 ...

  8. Unity3D开发技巧:如何避开unity编辑器的那些坑

    2015-12-04 09:00:33 来源: CocoaChina 跟贴 0 条 手机看新闻 文/瀚阳 以下总结一部分来自经验之谈,一部分来自其他人的分享.总的来讲,Unity开发原型和效果.验证想 ...

  9. Unity3D 的物理渲染和光照模型

    阅读目录 漫反射面:郎伯模型 朗伯着色器(Lambertian shader) Toon shading 镜面: Blinn-Phong模型 在Unity5中物理渲染 Unity 中使用的着色技术 结 ...

最新文章

  1. mysql isreg_`Innodb` MySQL中如何优雅的删除大表跑路
  2. 数据库事务系列-事务模型基础
  3. Ecos笔记--2014/5/3
  4. 语音识别学习日志 2019-7-15 语音识别基础知识准备4 {Baun-Welch算法}
  5. IOS – OpenGL ES 调节图像对比度 GPUImageContrastFilter
  6. 行为设计模式 - 解释器设计模式
  7. BUPT 2012复试机考 2T
  8. 图像变换——对数变换
  9. Bailian2972 确定进制(POJ NOI0113-34,POJ NOI0201-1973)【暴力+进制】
  10. 吴裕雄 python 机器学习——人工神经网络与原始感知机模型
  11. 【基于机器学习/深度学习的睡眠信号分类】主题必读论文推荐
  12. Java学习笔记Day2:流程控制
  13. [week13] 2 - T1
  14. netlink实现驱动和应用层通信
  15. 可以通过培训“速成速转”
  16. 雷神笔记本做java,详细爆料雷神911Air星战三代 15.6寸笔记本好不好用?怎么样呢?大神吐槽真心话...
  17. 微信小程序中实现——【音乐播放器】
  18. 怎么调计算机运行内存大小,运行内存太小怎么办?如何扩大电脑的运行内存?...
  19. c语言 txt文件数据,怎样用C语言从txt文件中读入数据?
  20. 单片机这个知识点一定要会!两大延时方法总结

热门文章

  1. mysql存储过程重命名_MySQL数据库重命名存储过程
  2. COS操作 java实现
  3. sourcetree出现提交成功但推送失败的问题
  4. Windows操作系统+朝鲜红星+国产麒麟+红旗+渗透专用系统+Oracle专用+技术专栏【资源大合集】 | 寻找C站宝藏
  5. Dubbo高频面试题
  6. 理论篇-地图学与GIS制图的基础理论(二)
  7. 如何以HTML显示Base64图像?
  8. 彭博社:Web3 巨大潜力之下的未解之谜
  9. 【设计】宽范围输入线性稳压器
  10. Linux 网络 I/O 模型简介(图文)