欢迎转载,转载请注明出处:http://blog.csdn.net/gnorth/article/details/9327971

说白了,也就是HOOK掉Present,这种代码,其实百度上某些地方有,但是很多人估计不知道怎样得到Present的地址。

所以就有些奇葩的例子:

先到游戏的登录器内把CreateProcess之类的HOOK掉,让游戏进程暂停启动,然后注入游戏 HOOK Direct3DCreate9 得到 IDirect3D9 对象之后,又得到 IDirect3DDevice9 对象,最终得到Present,反正挺蛋疼的,而且就是在游戏创建对象前要HOOK掉,用户体验非常的差。

其实自己创建个对象,从虚函数表就拿到真正的函数地址了。

本文的一大堆废话只是为了说明实现流程,由于写得比较仓促,所以嘛,估计要有一定基础的人才看得懂,最后面有完整的代码,你不想理解流程的话直接复制去用吧。

创建对象的代码如下:

BOOL InitializeD3D9(HWND hWnd, IDirect3D9 **ppD3D9, IDirect3DDevice9 **ppDev)
{
*ppD3D9 = Direct3DCreate9(D3D_SDK_VERSION);

if(!(*ppD3D9))
return FALSE;

D3DDISPLAYMODE d3ddm;
HRESULT hr = (*ppD3D9)->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);

if(FAILED(hr))
{
(*ppD3D9)->Release();
return FALSE;
}

D3DPRESENT_PARAMETERS d3dpp = {0};
d3dpp.Windowed= TRUE;
d3dpp.SwapEffect= D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat= d3ddm.Format;

hr = (*ppD3D9)->CreateDevice(
D3DADAPTER_DEFAULT, 
D3DDEVTYPE_HAL, 
hWnd, 
D3DCREATE_SOFTWARE_VERTEXPROCESSING, 
&d3dpp, 
ppDev);

if(FAILED(hr))
{
(*ppD3D9)->Release();
return FALSE;
}

return TRUE;
}

void ReleaseD3D9(IDirect3D9 *pD3D9, IDirect3DDevice9 *pDev)
{
if(pDev)
pDev->Release();
if(pD3D9)
pD3D9->Release();
}

LPVOID GetDirectDeviceMemberProc(LPVOID pDev, DWORD dwOffset) //根据偏移从虚函数表获取函数地址
{
LPVOID pRet = NULL;
__asm{
mov eax, dword ptr [pDev] 
mov ecx, dword ptr [eax] 
mov ebx, dwOffset
mov eax, dword ptr [ecx + ebx]
mov pRet, eax
}
return pRet;
}

这个代码要有DXSDK才行,需要依赖这两个,d3d9.h d3d9.lib,由于这种比较恶心的代码,一般就是静态库里一丢就永远不管了,但是呢,丢静态库里,假如不用这种功能的代码调用静态库时,就要出警告提示你,你木有用到d3d9.dll中的代码云云,非常不爽。

所以针对这个问题,我就自己调试了下,写了一个动态调用创建D3D设备对象的代码出来:最后面有完整的代码,你不想理解流程的话直接复制去用吧。

大概流程如下:

首先 Direct3DCreate9 是个标准API导出函数,所以,直接加载d3d9.dll,获取它即可

HMODULE hD3d9 = LoadLibraryA("d3d9.dll");

LPVOID _Direct3DCreate9 = (LPVOID)GetProcAddress(hD3d9, "Direct3DCreate9");
__asm{
push 32 //D3D_SDK_VERSION 32
call _Direct3DCreate9
mov pD3D9, eax
}

if(!pD3D9)

return false;

这样就得到了接口对象,接下来是Release 以及 GetAdapterDisplayMode

77:           (*ppD3D9)->Release();
00401243 8B 4D 0C             mov         ecx,dword ptr [ebp+0Ch]
00401246 8B 11                mov         edx,dword ptr [ecx]
00401248 8B 45 0C             mov         eax,dword ptr [ebp+0Ch]
0040124B 8B 08                mov         ecx,dword ptr [eax]
0040124D 8B 01                mov         eax,dword ptr [ecx]
0040124F 8B F4                mov         esi,esp
00401251 52                   push        edx
00401252 FF 50 08             call        dword ptr [eax+8]

它的偏移是8

GetAdapterDisplayMode

72:       HRESULT hr = (*ppD3D9)->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);
00401209 8B F4                mov         esi,esp
0040120B 8D 45 EC             lea         eax,[ebp-14h]
0040120E 50                   push        eax
0040120F 6A 00                push        0
00401211 8B 4D 0C             mov         ecx,dword ptr [ebp+0Ch]
00401214 8B 11                mov         edx,dword ptr [ecx]
00401216 8B 45 0C             mov         eax,dword ptr [ebp+0Ch]
00401219 8B 08                mov         ecx,dword ptr [eax]
0040121B 8B 01                mov         eax,dword ptr [ecx]
0040121D 52                   push        edx
0040121E FF 50 20             call        dword ptr [eax+20h]

偏移为0x20

LPVOID d3d9_Release = GetDirectDeviceMemberProc(pD3D9, 0x08);

LPVOID _GetAdapterDisplayMode = GetDirectDeviceMemberProc(pD3D9, 0x20);

//先调用_GetAdapterDisplayMode

//这里要传入一个 D3DDISPLAYMODE结构指针,但是外面并不需要对这个结构进行操作,所以这里只需要知道结构大小,然后分配一个一样大的内存给它就好。

//sizeof(D3DDISPLAYMODE)   大小为 0x10

BYTE d3ddm[0x10]

HRESULT hr = NULL;
__asm{
lea eax, d3ddm
push eax
push 0
push pD3D9
call _GetAdapterDisplayMode
mov hr, eax
}

if(FAILED(hr))  //  hr != S_OK;就释放对象,说明到这里就创建失败了,具体为啥失败我不懂
{
__asm{
push pD3D9
call d3d9_Release
}
return false;
}

接下来要用到 D3DPRESENT_PARAMETERS 结构,这个结构要在外面填参数了,所以,就要去看他的原型,看哪些是我们需要填的偏移。

它的大小是0x38, 注意我后面注释的值是偏移。

typedef struct _D3DPRESENT_PARAMETERS_
{
    UINT                BackBufferWidth;       //0
    UINT                BackBufferHeight;      //4
    D3DFORMAT           BackBufferFormat;      //8
    UINT                BackBufferCount;       //c

D3DMULTISAMPLE_TYPE MultiSampleType;       //10  enum
    DWORD               MultiSampleQuality;    //14

D3DSWAPEFFECT       SwapEffect;            //18  enum
    HWND                hDeviceWindow;         //1C
    BOOL                Windowed;              //20
    BOOL                EnableAutoDepthStencil;//24
    D3DFORMAT           AutoDepthStencilFormat;//28  enum
    DWORD               Flags;                 //2c

/* FullScreen_RefreshRateInHz must be zero for Windowed mode */
    UINT                FullScreen_RefreshRateInHz;//30
    UINT                PresentationInterval;  //34
} D3DPRESENT_PARAMETERS;

我们可以从结构上看到,需要操作的偏移如下
Windowed;              //20
SwapEffect;            //18
BackBufferFormat;      //8

然后代码中可以看到,BackBufferFormat是从d3ddm里过来的
结构是这样, Format的偏移为0x0c
typedef struct _D3DDISPLAYMODE
{
    UINT            Width;
    UINT            Height;
    UINT            RefreshRate;
    D3DFORMAT       Format;       //0x0c
} D3DDISPLAYMODE;

所以代码如下:

BYTE d3dpp[0x38];

memset(d3dpp, 0, 0x38);//如果不把结构填充为0,创建设备就会失败。

__asm{
lea eax, d3dpp
mov dword ptr [eax + 0x20], 1
mov dword ptr [eax + 0x18], 1
lea ebx, d3ddm
mov ecx, dword ptr [ebx + 0x0C]
mov dword ptr [eax + 8], ecx
}

最后就是创建设备的代码

86:       hr = (*ppD3D9)->CreateDevice(
87:           D3DADAPTER_DEFAULT,
88:           D3DDEVTYPE_HAL,
89:           hWnd,
90:           D3DCREATE_SOFTWARE_VERTEXPROCESSING,
91:           &d3dpp,
92:           ppDev);
00401287 8B F4                mov         esi,esp
00401289 8B 55 10             mov         edx,dword ptr [ebp+10h]
0040128C 52                   push        edx
0040128D 8D 45 B4             lea         eax,[ebp-4Ch]
00401290 50                   push        eax
00401291 6A 20                push        20h
00401293 8B 4D 08             mov         ecx,dword ptr [ebp+8]
00401296 51                   push        ecx
00401297 6A 01                push        1
00401299 6A 00                push        0
0040129B 8B 55 0C             mov         edx,dword ptr [ebp+0Ch]
0040129E 8B 02                mov         eax,dword ptr [edx]
004012A0 8B 4D 0C             mov         ecx,dword ptr [ebp+0Ch]
004012A3 8B 11                mov         edx,dword ptr [ecx]
004012A5 8B 0A                mov         ecx,dword ptr [edx]
004012A7 50                   push        eax
004012A8 FF 51 40             call        dword ptr [ecx+40h]                   <-----------偏移0x40

LPVOID _CreateDevice = GetDirectDeviceMemberProc(pD3D9, 0x40);
//0x40

__asm{
lea eax, pD3D9Dev
push eax
lea eax, d3dpp
push eax
push 0x20
push hWnd
push 1
push 0
push pD3D9
call _CreateDevice
mov hr, eax
}

if(FAILED(hr))
{
__asm{
push pD3D9
call d3d9_Release
}

return false;

}

最终得到设备对象后,就用同样的方法,从虚函数表获取真正的函数地址, Present的偏移为0x44

inline钩子函数,大概如下:

typedef HRESULT (WINAPI *DEFPRESENT)(LPDIRECT3DDEVICE9,CONST RECT *,CONST RECT *,HWND,CONST RGNDATA *);
DEFPRESENT PresentData = NULL;
HRESULT WINAPI _Present(LPDIRECT3DDEVICE9 p1, CONST RECT *p2, CONST RECT *p3, HWND p4, CONST RGNDATA *p5)
{
Sleep(64);//这里也就是降帧了
return PresentData(p1, p2, p3, p4, p5);
}

但是,这种代码,一般得写在DLL里才行,要是DLL卸载出来了,代码自然就没了,你不卸载HOOK就是一个字,“崩”!

而有些非常时期,却又不得不卸载DLL。所以嘛,就自然有了ShellCode的代码。

ShellCode不必完全模仿完善的inline hook 钩子函数,因为要模仿起来太累了, 因为存在PresentData中间代码实际上也是ShellCode。

所以大概原型如下:

__asm{

pushad

pushfd

//这里call Sleep

以及执行它的前5个字节

mov edi, edi

push ebp

mov ebp, esp     好像是这个样子

popfd

popad

jmp 原始Present + 5

}

这个代码实现起来不难,要注意的是,Sleep是不能直接调用的,因为Sleep实际上会被编译器进行包装,分配一个指针来包装它,然后call dword ptr [包装的指针]

而这个指针,在你的dll卸载时,也会无效掉。所以我们应该自己包装Sleep

PBYTE psc = (PBYTE)VirtualAllocEx(GetCurrentProcess(), NULL, 4, MEM_COMMIT | MEM_RESERVE, 0x40);

*(PDWORD)psc = (DWORD)Sleep;

//使用VirtualAllocEx进行分配内存,使用MEM_COMMIT | MEM_RESERVE模式,这样分配出来的内存不会跟随你的DLL一起被释放掉。

//其他的几种方式  new  会跟随dll释放

//C函数库的 malloc也会释放

//GlobalAlloc  不能改变为可执行可读写保护模式

//还好最后剩VirtualAllocEx能用

//其实我早就感觉,dll卸载时会把dll new  出来的各种内存都释放掉了,也就是通过这个例子,我终于证实了这个结论。

ShellCode 如下

BYTE pCode[22] = {
0x60,     //pushad
0x9C,    //pushfd
0x6A, bTime,     //Sleep参数
0xFF, 0x15, 0x00, 0x00, 0x00, 0x00,    //call dword ptr [psc]   暂时留空,在下面会对这里写入
0x9D, //popfd
0x61,  //popad            我断了下Sleep看过,其实被改变的通用寄存器好像只有eax ecx edx这几个,但为了代码更简单点,所以直接pushad pushfd popad popfd
0x8B, 0xFF,   //mov edi, edi   实际上这个代码是可以不写的
0x55,         // push ebp
0x8B, 0xEC,   //mov ebp, esp
0xE9, 0x00, 0x00, 0x00, 0x00,    //jmp 回 Present + 5   留到下面去写入跳转相对地址
};

*((PDWORD)(pCode + 6)) = (DWORD)psc; //把Sleep的包装地址写入进去,由于call dword ptr 不是用的相对地址,所以直接写入地址进去即可

pCode是在局部分配来写ShellCode的,

所以需要分配一个能够保留的地址,拷贝一份代码过去。

PBYTE pShellCode = (PBYTE)VirtualAllocEx(GetCurrentProcess(), NULL, 22, MEM_COMMIT | MEM_RESERVE, 0x40);
memcpy(pShellCode, pCode, 22);

PDWORD pAddr = (PDWORD)(pShellCode + 18);//得到最后4字节的指针

*pAddr = (DWORD)pPresent - ((DWORD)pAddr - 1);//计算相对地址并写入

//这里普及下常识,    0xE8     call             0xE9   jmp       这种远跳  调用的代码

//它的值为相对地址,这个地址的计算公式为:      目标地址 - (指令地址 + 指令长度)

// (DWORD)pPresent - ((DWORD)pAddr - 1);   根据公式的话,这里看起来很诡异吧

//首先,我们要在这里跳回Present + 5     那么目标就是 (Present + 5 )    然后,我们跳转的指令地址是在这里   0xE9, 0x00, 0x00, 0x00, 0x00,    从0xE9开始

//但是(PDWORD)(pShellCode + 18);//这里取得最后的4字节的地址时,已经到0xE9后面的一个字节去了, 所以:跳转地址实际上为(pShellCode - 1 + 5)

根据公式:    就应该是这样  (Present + 5 )  - (pShellCode - 1 + 5)      即: (DWORD)pPresent - ((DWORD)pAddr - 1);

一切准备完毕之后,用同样的方法,让原始的Present头部跳到pShellCode头部,这里需要VirtualProtect  改变一下要操作的那几个字节为可读写可执行,操作完后改回去

DWORD dwProtect = 0;
VirtualProtect(pPresent, 5, 0x40, &dwProtect);
*(PBYTE)pPresent = 0xE9;
*((PDWORD)((PBYTE)pPresent + 1)) = (DWORD)pShellCode - (DWORD)pPresent - 5;
VirtualProtect(pPresent, 5, dwProtect, NULL);

完整的代码如下:

[cpp]  view plain copy
  1. LPVOID GetDirectDeviceMemberProc(LPVOID pDev, DWORD dwOffset)
  2. {
  3. LPVOID pRet = NULL;
  4. __asm{
  5. mov eax, dword ptr [pDev]
  6. mov ecx, dword ptr [eax]
  7. mov ebx, dwOffset
  8. mov eax, dword ptr [ecx + ebx]
  9. mov pRet, eax
  10. }
  11. return pRet;
  12. }
  13. struct _d3dhookdev_type{
  14. LPVOID pD3D9;
  15. LPVOID pD3DDev;
  16. };
  17. bool InitD3D9(HWND hWnd, _d3dhookdev_type &dev)
  18. {
  19. HMODULE hD3d9 = LoadLibraryA("d3d9.dll");
  20. LPVOID pD3D9 = NULL, pD3D9Dev = NULL;
  21. BYTE d3ddm[0x10];
  22. if(hD3d9)
  23. {
  24. LPVOID _Direct3DCreate9 = (LPVOID)GetProcAddress(hD3d9, "Direct3DCreate9");
  25. __asm{
  26. push 32
  27. call _Direct3DCreate9
  28. mov pD3D9, eax
  29. }
  30. if(!pD3D9)
  31. return false;
  32. LPVOID d3d9_Release = GetDirectDeviceMemberProc(pD3D9, 0x08);
  33. LPVOID _GetAdapterDisplayMode = GetDirectDeviceMemberProc(pD3D9, 0x20);
  34. HRESULT hr = NULL;
  35. __asm{
  36. lea eax, d3ddm
  37. push eax
  38. push 0
  39. push pD3D9
  40. call _GetAdapterDisplayMode
  41. mov hr, eax
  42. }
  43. if(FAILED(hr))
  44. {
  45. __asm{
  46. push pD3D9
  47. call d3d9_Release
  48. }
  49. return false;
  50. }
  51. BYTE d3dpp[0x38];
  52. memset(d3dpp, 0, 0x38);
  53. __asm{
  54. lea eax, d3dpp
  55. mov dword ptr [eax + 0x20], 1
  56. mov dword ptr [eax + 0x18], 1
  57. lea ebx, d3ddm
  58. mov ecx, dword ptr [ebx + 0x0C]
  59. mov dword ptr [eax + 8], ecx
  60. }
  61. LPVOID _CreateDevice = GetDirectDeviceMemberProc(pD3D9, 0x40);
  62. //0x40
  63. __asm{
  64. lea eax, pD3D9Dev
  65. push eax
  66. lea eax, d3dpp
  67. push eax
  68. push 0x20
  69. push hWnd
  70. push 1
  71. push 0
  72. push pD3D9
  73. call _CreateDevice
  74. mov hr, eax
  75. }
  76. printf("%X\n", hr);
  77. if(FAILED(hr))
  78. {
  79. __asm{
  80. push pD3D9
  81. call d3d9_Release
  82. }
  83. return false;
  84. }
  85. dev.pD3D9 = pD3D9;
  86. dev.pD3DDev = pD3D9Dev;
  87. return true;
  88. }
  89. return false;
  90. }
  91. void ReleaseD3D9(_d3dhookdev_type &dev)
  92. {
  93. if(dev.pD3D9)
  94. {
  95. LPVOID d3d_Release = GetDirectDeviceMemberProc(dev.pD3D9, 0x08);
  96. __asm{
  97. push dev.pD3D9
  98. call d3d_Release
  99. }
  100. }
  101. if(dev.pD3DDev)
  102. {
  103. LPVOID d3ddev_Release = GetDirectDeviceMemberProc(dev.pD3DDev, 0x08);
  104. __asm{
  105. push dev.pD3DDev
  106. call d3ddev_Release
  107. }
  108. }
  109. }
  110. void D3DPresentShellCodeHook(HWND hWnd, BYTE bTime)
  111. {
  112. _d3dhookdev_type dev = {NULL, NULL};
  113. LPVOID pPresent = NULL;
  114. if(InitD3D9(hWnd, dev))
  115. {
  116. pPresent = GetDirectDeviceMemberProc(dev.pD3DDev, 0x44);
  117. ReleaseD3D9(dev);
  118. if(!pPresent)
  119. return;
  120. if(0xE9 == *(PBYTE)pPresent)
  121. return;
  122. }
  123. PBYTE psc = (PBYTE)VirtualAllocEx(GetCurrentProcess(), NULL, 4, MEM_COMMIT | MEM_RESERVE, 0x40);
  124. *(PDWORD)psc = (DWORD)Sleep;
  125. BYTE pCode[22] = {
  126. 0x60,
  127. 0x9C,
  128. 0x6A, bTime,
  129. 0xFF, 0x15, 0x00, 0x00, 0x00, 0x00,
  130. 0x9D,
  131. 0x61,
  132. 0x8B, 0xFF,
  133. 0x55,
  134. 0x8B, 0xEC,
  135. 0xE9, 0x00, 0x00, 0x00, 0x00,
  136. };
  137. *((PDWORD)(pCode + 6)) = (DWORD)psc;
  138. PBYTE pShellCode = (PBYTE)VirtualAllocEx(GetCurrentProcess(), NULL, 22, MEM_COMMIT | MEM_RESERVE, 0x40);
  139. memcpy(pShellCode, pCode, 22);
  140. PDWORD pAddr = (PDWORD)(pShellCode + 18);
  141. *pAddr = (DWORD)pPresent - ((DWORD)pAddr - 1);
  142. DWORD dwProtect = 0;
  143. VirtualProtect(pPresent, 5, 0x40, &dwProtect);
  144. *(PBYTE)pPresent = 0xE9;
  145. *((PDWORD)((PBYTE)pPresent + 1)) = (DWORD)pShellCode - (DWORD)pPresent - 5;
  146. VirtualProtect(pPresent, 5, dwProtect, NULL);
  147. }

D3D游戏降帧的动态创建D3D设备以及ShellCode HOOK玩法相关推荐

  1. 诸仙D3D游戏环境下如何实现真正D3D的窗口

    诸仙D3D游戏环境下如何实现真正D3D的窗口,给出关键代码及其方法(希望大家在D3 D游戏中做出D3D窗口)! 前些日子一直忙,也没来看看,说要给出代码和方法的一直没有给出来请大家见谅,今天给出来!  ...

  2. 游戏提高性能 游戏降帧处理

    什么是降帧&为什么降帧 一般情况下我们为了提高整个游戏的体验,所以我们一般会将游戏的帧数(每秒钟刷新多少次)设置的比较高.一般情况下,我们的游戏所有的代码都是一帧执行一次.为了让每一帧都变成真 ...

  3. 决斗小游戏代码html,《游戏王:决斗链接》的基础玩法介绍

    <游戏王:决斗链接>是konami于2016年发行的<YU-GI-OH Duel Links>的国服,同时也是在手机应用市场下载量位于卡牌游戏榜首,是全世界最火的游戏王网络游戏 ...

  4. 扑克牌猜数字游戏规则_【趣味游戏】扑克牌的10种益智玩法!

    原标题:[趣味游戏]扑克牌的10种益智玩法! "扑克牌"算是十分常见的材料了,你是否有想过用扑克牌也能和孩子玩出花样.玩出乐趣? 您可别小瞧这扑克牌,也能一物多玩. 今天,小编就为 ...

  5. 又一杀手级VR游戏?《星球大战:前线2》或支持VR玩法

    游戏开发团队透露了游戏可能会有VR部分的消息,让玩家进一步沉浸在故事体验中. 最近,EA公布了2017年当家FPS力作<星球大战:前线2>,随后将陆续登陆PC.PS4以及Xbox One. ...

  6. 战地风暴游戏需要的计算机配置,战地风暴最合适你的玩法让我们一起入门

    玩这种策略游戏不是RMB我真的建议创建1-3个号. 刚开区主要上大号小号有时间就发展.大家记得小号要进第一联盟.大号你想进那就进哪.因为第一联盟联盟活动基本都是第一,每次联盟活动结束都会送2个三小时加 ...

  7. 分享一下自己写的2048游戏(3*3,4*4,5*5,6*6多种玩法,可反悔)

    2048是一款非常常见的小游戏,所以我也想自己尝试着写一款,给自己练练手.说道练手,这里需要交代一下:我从事Android的工作刚刚一年,平时的工作主要是客制化UI和修改Bug,也就是这里改改,那里改 ...

  8. 【Unity小游戏】整整一个周末写一款《皇室战争 玩法》 的 即时战斗类 游戏Demo。两万多字游戏制作过程+解析

  9. 第六章 DirectX 2D游戏和帧动画(上)

    目前,我们已经掌握了如何使用DirectX绘制四边形,纹理映射技术,以及正交摄像机的内容.对于2D游戏的开发,这些内容基本上已经足够了.2D游戏的本质就是图像游戏,2D游戏中的动画其实就是一系列连续动 ...

最新文章

  1. HDU 4869 Turn the pokers(思维+组合公式+高速幂)
  2. Kotlinkotlin二进制与十六进制之间的转化
  3. 基础篇:7.Content provider与Content Resolver实现数据共享
  4. 35 个 Java 代码性能优化总结
  5. cmd下,如何在文本的指定行添加内容
  6. 自然语言交流系统 phxnet团队 创新实训 项目博客 (五)
  7. 小心使用tf.image.resize_images,填坑经验分享给你
  8. 20211102:数字滤波器按照实现结构的分类及其优缺点总结
  9. 周期T与频率f数量级对应关系(MHz--μs)
  10. linux 定时关机命令,linux 定时关机命令
  11. FroalaEditor使用方法汇总
  12. FFmpeg 和 MP4Box 几个命令
  13. 如何快速提高产品互动能力?
  14. css样式calc的兼容性_在前端CSS3中使用calc()以及calc()的兼容性.
  15. Android之实现遮罩动画的小技巧 类似flash遮罩动画
  16. 数字 IC 笔试面试必考点(8)时钟偏差以及时钟抖动
  17. 电脑开始菜单没有了关机选项,怎么办
  18. 浅谈域名分级及域名解析过程
  19. 未接响铃1秒是什么意思_空调能耗等级是什么意思 家用有必要买1级空调吗 看了它就明白了...
  20. 【Linux】目录权限和默认权限

热门文章

  1. Mac 常用记录备忘
  2. RAID 0 添加新磁盘
  3. Java流与文件操作
  4. vue——路由之路由跳转、路由传参、路由嵌套、路由模式
  5. 天野第五期易语言半内存辅助培训班教程
  6. mysql查找所有男生_MYSQL-SELECT查
  7. 【iOS应用安全】游戏安全之IPA破解原理及防御
  8. 算法与数据结构 - 排序详解
  9. SecureCRT 9.1.0安装教程
  10. JavaScript罗马数字转整数