转载https://bbs.pediy.com/thread-195759.htm
本文要点:
介绍一种从显卡驱动入手的构造D3DHook的方法,DX9,DX9EX,DX10,DX11均试用,无需重启进程,注入即可使用。

以DX9为例。

前置知识:
1. API Hook的基础知识
2. DX绘图知识
3. 关于DX Hook 有很多种方法,传统的方法请自行查找资料。
4. Windows Display Driver 的基础知识

首先Vender驱动导出了一个叫做OpenAdapter的函数。
原型

1
typedef HRESULT APIENTRY _OpenAdapter(D3DDDIARG_OPENADAPTER

*pAdapterData);
通过观察这个函数的参数可以发现这两个结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct _D3DDDIARG_OPENADAPTER
{
     HANDLE                         hAdapter;           // in /out :  Runtime handle /out : Driver handle
     UINT                           Interface;          // in :  Interface version
     UINT                           Version;            // in :  Runtime version
     CONST D3DDDI_ADAPTERCALLBACKS* pAdapterCallbacks;  // in :  Pointer to runtime callbacks
     D3DDDI_ADAPTERFUNCS*           pAdapterFuncs;      // out: Driver function table
     UINT                           DriverVersion;      // out: D3D UMD interface version the
                                                        //      driver was compiled with. Use
                                                        //      D3D_UMD_INTERFACE_VERSION.
} D3DDDIARG_OPENADAPTER;
typedef struct _D3DDDI_ADAPTERFUNCS
{
     PFND3DDDI_GETCAPS                       pfnGetCaps;
     PFND3DDDI_CREATEDEVICE                  pfnCreateDevice;
     PFND3DDDI_CLOSEADAPTER                  pfnCloseAdapter;
} D3DDDI_ADAPTERFUNCS;

HookOpenAdapter代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
BOOL DetourOpenAdapter()
{
OpenAdapter = (_OpenAdapter *)GetProcAddress(GetModuleHandleA( "nvd3dum.dll" ), "OpenAdapter" );
if (OpenAdapter == NULL)
{
OpenAdapter = (_OpenAdapter *)GetProcAddress(GetModuleHandleA( "aticfx32.dll" ), "OpenAdapter" );
}
if (OpenAdapter)
{
DetourRestoreAfterWith();
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach((void **)&OpenAdapter, WarpOpenAdapter);
DetourTransactionCommit();
}
return OpenAdapter != NULL;
}

通过Hook OpenAdapter 可以得到pfnCreateDevice;
代码如下

1
2
3
4
5
6
7
8
9
10
HRESULT APIENTRY WarpOpenAdapter(D3DDDIARG_OPENADAPTER *pAdapterData)
{
     HRESULT ret = OpenAdapter(pAdapterData);
     if (ret == S_OK && pAdapterData->pAdapterFuncs->pfnCreateDevice)
     {
         DdiCreateDevice = pAdapterData->pAdapterFuncs->pfnCreateDevice;
         pAdapterData->pAdapterFuncs->pfnCreateDevice = WarpDdiCreateDevice;
     }
     return ret;
}

pfnCreateDevice是Vender驱动上报给runtime的createDevice API.当APP调用CreateDevice最后会到厂商驱动的pfnCreateDevice; 替换厂商驱动上报给runtime的pfnCreateDevice函数指针,就可以实现Hook pfnCreateDevice。这样下次APP调用CreateDevice就会先进入我们的WarpDdiCreateDevice;
代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
HRESULT APIENTRY WarpDdiCreateDevice(
     HANDLE hAdapter,
     D3DDDIARG_CREATEDEVICE *pDeviceData)
{
     // DdiCreateDevice must not be NULL if this path hit
     HRESULT ret = DdiCreateDevice(hAdapter, pDeviceData);
     if (pDeviceData->pDeviceFuncs->pfnPresent && gDetourFuncTable.DdiPresent.isAttatched == FALSE)
     {
         DdiPresent = pDeviceData->pDeviceFuncs->pfnPresent;
         ULONG_PTR realTramponLine = DetourDDiPresent();
         gDetourFuncTable.DdiPresent.isAttatched = TRUE;
         gDetourFuncTable.DdiPresent.preFuncPtr = DdiPresent;
         gDetourFuncTable.DdiPresent.TrampoLinePtr = (PFND3DDDI_PRESENT)realTramponLine;
         gDetourFuncTable.DdiPresent.warpFuncPtr = WarpDdiPresent;
     }
     if (pDeviceData->pDeviceFuncs->pfnPresent1 && gDetourFuncTable.DdiPresent1.isAttatched == FALSE)
     {
         DdiPresent1 = pDeviceData->pDeviceFuncs->pfnPresent1;
         ULONG_PTR realTramponLine = DetourDDiPresent1();
         gDetourFuncTable.DdiPresent1.isAttatched = TRUE;
         gDetourFuncTable.DdiPresent1.preFuncPtr = DdiPresent1;
         gDetourFuncTable.DdiPresent1.TrampoLinePtr = (PFND3DDDI_PRESENT1)realTramponLine;
         gDetourFuncTable.DdiPresent1.warpFuncPtr = WarpDdiPresent1;
     }
     DdiLock = pDeviceData->pDeviceFuncs->pfnLock;
     DdiCreateResource = pDeviceData->pDeviceFuncs->pfnCreateResource;
     DdiCreateResource2 = pDeviceData->pDeviceFuncs->pfnCreateResource2;
     DdiUnlock = pDeviceData->pDeviceFuncs->pfnUnlock;
     DdiBlt = pDeviceData->pDeviceFuncs->pfnBlt;
     return ret;
}
typedef struct _D3DDDIARG_CREATEDEVICE
{
     HANDLE                          hDevice;                // in :  Runtime handle /out : Driver handle
     UINT                            Interface;              // in :  Interface version
     UINT                            Version;                // in :  Runtime Version
     CONST D3DDDI_DEVICECALLBACKS*   pCallbacks;             // in :  Pointer to runtime callbacks
     VOID*                           pCommandBuffer;         // in :  Pointer to the first command buffer to use.
     UINT                            CommandBufferSize;      // in :  Size of the first command buffer to use.
     D3DDDI_ALLOCATIONLIST*          pAllocationList;        // out: Pointer to the first allocation list to use.
     UINT                            AllocationListSize;     // in :  Size of the allocation list that will be available
                                                             //      when the first command buffer is submitted.
     D3DDDI_PATCHLOCATIONLIST*       pPatchLocationList;     // out: Pointer to the first patch location list to use.
     UINT                            PatchLocationListSize;  // in :  Size of the patch location list that will be available
                                                             //      when the first command buffer is submitted.
     D3DDDI_DEVICEFUNCS*             pDeviceFuncs;           // out: Driver function table
     D3DDDI_CREATEDEVICEFLAGS        Flags;                  // in :  Flags
#if (D3D_UMD_INTERFACE_VERSION >= D3D_UMD_INTERFACE_VERSION_WIN7)
     D3DGPU_VIRTUAL_ADDRESS          CommandBuffer;          // out: GPU Virtual address to the command buffer to use. _ADVSCH_
#endif // D3D_UMD_INTERFACE_VERSION
} D3DDDIARG_CREATEDEVICE;

观察参数D3DDDIARG_CREATEDEVICE *pDeviceData不难发现 pDeviceFuncs中有我们需要的一切绘图API,这里用到的函数如下:

1
2
3
4
5
6
7
8
PFND3DDDI_CREATEDEVICE DdiCreateDevice = NULL;
PFND3DDDI_PRESENT DdiPresent = NULL;
PFND3DDDI_PRESENT1 DdiPresent1= NULL;
PFND3DDDI_LOCK DdiLock = NULL;
PFND3DDDI_CREATERESOURCE DdiCreateResource = NULL;
PFND3DDDI_CREATERESOURCE2 DdiCreateResource2 = NULL;
PFND3DDDI_UNLOCK DdiUnlock = NULL;
PFND3DDDI_BLT DdiBlt = NULL;

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
HRESULT APIENTRY WarpDdiCreateDevice(
     HANDLE hAdapter,
     D3DDDIARG_CREATEDEVICE *pDeviceData)
{
     // DdiCreateDevice must not be NULL if this path hit
     HRESULT ret = DdiCreateDevice(hAdapter, pDeviceData);
     if (pDeviceData->pDeviceFuncs->pfnPresent && gDetourFuncTable.DdiPresent.isAttatched == FALSE)
     {
         DdiPresent = pDeviceData->pDeviceFuncs->pfnPresent;
         ULONG_PTR realTramponLine = DetourDDiPresent();
         gDetourFuncTable.DdiPresent.isAttatched = TRUE;
         gDetourFuncTable.DdiPresent.preFuncPtr = DdiPresent;
         gDetourFuncTable.DdiPresent.TrampoLinePtr = (PFND3DDDI_PRESENT)realTramponLine;
         gDetourFuncTable.DdiPresent.warpFuncPtr = WarpDdiPresent;
     }
     if (pDeviceData->pDeviceFuncs->pfnPresent1 && gDetourFuncTable.DdiPresent1.isAttatched == FALSE)
     {
         DdiPresent1 = pDeviceData->pDeviceFuncs->pfnPresent1;
         ULONG_PTR realTramponLine = DetourDDiPresent1();
         gDetourFuncTable.DdiPresent1.isAttatched = TRUE;
         gDetourFuncTable.DdiPresent1.preFuncPtr = DdiPresent1;
         gDetourFuncTable.DdiPresent1.TrampoLinePtr = (PFND3DDDI_PRESENT1)realTramponLine;
         gDetourFuncTable.DdiPresent1.warpFuncPtr = WarpDdiPresent1;
     }
     DdiLock = pDeviceData->pDeviceFuncs->pfnLock;
     DdiCreateResource = pDeviceData->pDeviceFuncs->pfnCreateResource;
     DdiCreateResource2 = pDeviceData->pDeviceFuncs->pfnCreateResource2;
     DdiUnlock = pDeviceData->pDeviceFuncs->pfnUnlock;
     DdiBlt = pDeviceData->pDeviceFuncs->pfnBlt;
     return ret;
}

到这里我们已经顺利拿到厂商的pfnPresent函数了,是不是有点兴奋呢?因为我们要做的任何绘图工作需要在pfnPresent之前完成。这样present才会把我们画的东西拷贝到屏幕上。
或许你会有一个疑问,APP已经在运行了,不会再调用CreateDevice了,这样我们的Hook不就无效了吗?
答案是这样的,既然APP调用CreateDevice,那我们就自己调用。我们来构造这样一个CreateDevice的条件,创建出来的d3dDevice还能在后面用来作为我们绘图的设备(OnRenderFrame).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
BOOL TryInitD3D(HWND hWnd)
{
     D3DPRESENT_PARAMETERS d3dpp;
     HRESULT hResult = D3DERR_INVALIDCALL;
     ZeroMemory(&d3dpp, sizeof(d3dpp));
     d3dpp.Windowed = TRUE;
     d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
     d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
     pd3dInternal = Direct3DCreate9(D3D_SDK_VERSION);
     if (pd3dInternal != NULL)
     {
         hResult = pd3dInternal->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
             D3DCREATE_SOFTWARE_VERTEXPROCESSING,
             &d3dpp, &pd3dDevInternal);
     }
     return hResult == S_OK && pd3dDevInternal != NULL && InitDx();
}

拿到pfnPresent之后做inlineHook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ULONG_PTR DetourDDiPresent1()
{
     ULONG_PTR realTrampoLine = NULL;
     DetourRestoreAfterWith();
     DetourTransactionBegin();
     DetourUpdateThread(GetCurrentThread());
     if (DdiPresent1)
     {
         DetourAttachEx((void **)&DdiPresent1, WarpDdiPresent1, (PDETOUR_TRAMPOLINE *)&realTrampoLine, NULL, NULL);
     }
     DetourTransactionCommit();
     return realTrampoLine;
}

然后在pfnPrsent的代理函数中做我们自己的绘图操作。里面涉及的函数下面会有解释
我实现了一个简单的pfnPresent1代理函数,在里面调用真正的pfnPresent1之前做我们的绘图(我是的WIN 8.1,WDDM1.3 所以就对pfnPresent1动手了)
代理函数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
HRESULT APIENTRY WarpDdiPresent1(
     HANDLE hDevice,
     CONST D3DDDIARG_PRESENT1 *pPresent1)
{
     static HANDLE wrapDevice = NULL;
     static HANDLE hDestSurf = NULL;
     static UINT cachedWidth = 0;
     static UINT cachedHeight = 0;
     RECT rc = { 0 };
     RECT destRC = { 0 };
     RECT srcRC = { 0 };
     IDirect3DSurface9 * pSrcSurf = NULL;
     HRESULT hRet = S_OK;
         // 如果是运行中的app画图,就进入下面的流程进行我们的画图
         // 反之是我们的画图操作,直接放过,避免重入
     if (hDevice == wrapDevice || wrapDevice == NULL)
     {
         wrapDevice = hDevice;
         if (S_OK == onRenderFrame(&pSrcSurf, &rc))
         {
             if (rc.right - rc.left != cachedWidth ||
                 rc.bottom - rc. top != cachedHeight)
             {
                 cachedWidth = rc.right - rc.left;
                 cachedHeight = rc.bottom - rc. top ;
                 //TODO : destroy hDestSurf
                 hDestSurf = CreateSurfaceforWDDM1_3(hDevice, cachedWidth, cachedHeight);
             }
             if (hDestSurf != NULL)
             {
                 destRC = srcRC = rc;
                 hRet = SurfaceBltInternal(hDevice, hDestSurf, destRC, pSrcSurf, srcRC);
             }
             if (hRet == S_OK)
             {
                 BltToRTWDDM1_3(hDevice, hDestSurf, pPresent1,rc);
             }
         }
     }
     hRet = gDetourFuncTable.DdiPresent1.TrampoLinePtr(hDevice, pPresent1);
     return hRet;
}

HRESULT APIENTRY onRenderFrame(__out IDirect3DSurface9 ** pOutSurf, RECT * pOutRC)是我们的绘图函数,在这里实现我们要的东西,画到RenderTarget上,然后输出RenderTarget和它的大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
HRESULT APIENTRY onRenderFrame(__out IDirect3DSurface9 ** pOutSurf, RECT * pOutRC)
{
     HRESULT hr;
     RECT rc;
     pd3dDev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_RGBA(0, 0, 0, 0), 1.0f, 0);
     if (S_OK == pd3dDev->BeginScene())
     {
         hr = pd3dDev->SetRenderTarget(NULL, pRTSurf);
         // 在这里画你需要的任何东西,没错 就是这么任性。
         pd3dDev->EndScene();
     }
     if (hr == S_OK)
     {
         *pOutSurf = pRTSurf;
         *pOutRC = rc;
     }
     else
     {
         *pOutSurf = NULL;
         pOutRC-> top = 0;
         pOutRC->left = 0;
         pOutRC->bottom = 0;
         pOutRC->right = 0;
     }
     return hr;
}

当在WarpDdiPresent1得到我们的绘图结果(RenderTarget和大小)之后,我们就能把画好的结果拿出来,然后通过调用厂商提供的pfnBlt函数 贴到present的SrcSurf上。

//用上面拿到的pfnCreateResource2 创建一张surface 用来保存我们的绘图结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
HANDLE CreateSurfaceforWDDM1_3(HANDLE hDevice, UINT width, UINT height)
{
     D3DDDIARG_CREATERESOURCE2 createResourceData2;
     D3DDDI_SURFACEINFO surfList[1];
     memset(surfList, 0, sizeof(surfList));
     memset(&createResourceData2, 0, sizeof(createResourceData2));
     surfList[0].Height = width;
     surfList[0].Width = height;
     surfList[0].Depth = 1;
     surfList[0].pSysMem = NULL;
     surfList[0].SysMemPitch = 0;
     surfList[0].SysMemSlicePitch = 0;
     createResourceData2.Format = D3DDDIFMT_A8R8G8B8;
     createResourceData2.Pool = D3DDDIPOOL_NONLOCALVIDMEM;
     createResourceData2.MultisampleType = D3DDDIMULTISAMPLE_NONE;
     createResourceData2.MultisampleQuality = 0;
     createResourceData2.pSurfList = surfList;
     createResourceData2.SurfCount = 1;
     createResourceData2.MipLevels = 0;
     createResourceData2.Fvf = 0;
     createResourceData2.VidPnSourceId = 0;
     createResourceData2.RefreshRate.Denominator = 0;
     createResourceData2.RefreshRate.Numerator = 0;
     // use a particular handle that runtime do not use
     createResourceData2.hResource = (HANDLE)0xfff00000;
     createResourceData2.Flags.Value = 0;
     createResourceData2.Rotation = D3DDDI_ROTATION_IDENTITY;
     DdiCreateResource2(hDevice, &createResourceData2);
     return createResourceData2.hResource;
}

//把OnRenderFrame输出的RenderTarget的内容拷贝到创建的Surface

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
HRESULT SurfaceBltInternal(HANDLE hDevice, HANDLE hDestSurf, RECT destSurfRC, IDirect3DSurface9 * pSrcSurf, RECT srcSurfRC)
{
     D3DDDIARG_LOCK lockdata;
     D3DDDIARG_UNLOCK unlockData;
     D3DLOCKED_RECT lockedRC;
     HRESULT hr;
     if (destSurfRC.right - destSurfRC.left < srcSurfRC.right - srcSurfRC.left ||
         destSurfRC.bottom - destSurfRC. top < srcSurfRC.bottom - srcSurfRC. top )
     {
         hr = S_FALSE;
     }
     else
     {
         memset(&lockdata, 0, sizeof(lockdata));
         memset(&unlockData, 0, sizeof(unlockData));
         lockdata.hResource = hDestSurf;
         lockdata.SubResourceIndex = 0;
         lockdata.Area.left = 0;
         lockdata.Area.right = destSurfRC.right - destSurfRC.left;
         lockdata.Area. top = 0;
         lockdata.Area.bottom = destSurfRC.bottom - destSurfRC. top ;
         hr = DdiLock(hDevice, &lockdata);
         if (hr == S_OK)
         {
             hr = pSrcSurf->LockRect(&lockedRC, &srcSurfRC, NULL);
             if (S_OK == hr)
             {
                 CHAR *pDest = (CHAR*)lockdata.pSurfData;
                 CHAR *pSrc = (CHAR*)lockedRC.pBits;
                 for (UINT i = 0; i < destSurfRC.bottom - destSurfRC. top ; i++)
                 {
                     CopyMemory(pDest, pSrc, lockedRC.Pitch < lockdata.Pitch ? lockedRC.Pitch : lockdata.Pitch);
                     pDest += lockdata.Pitch;
                     pSrc += lockedRC.Pitch;
                 }
             }
             pSrcSurf->UnlockRect();
             memset(&unlockData, 0, sizeof(unlockData));
             unlockData.hResource = hDestSurf;
             unlockData.SubResourceIndex = 0;
             DdiUnlock(hDevice, &unlockData);
         }
     }
     return hr;
}

//调用厂商提供的pfnBLT把surafce上保存的图画 搬到 present的SrcSurf中,这样 present的时候 我们画的东西就展现出来了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
HRESULT BltToRTWDDM1_1(HANDLE hDevice, HANDLE hRes, CONST D3DDDIARG_PRESENT *pPresent)
{
     D3DDDIARG_BLT bltData;
     RECT srcRC;
     RECT destRC;
     memset(&bltData, 0, sizeof(bltData));
     memset(&srcRC, 0, sizeof(srcRC));
     memset(&destRC, 0, sizeof(destRC));
     
     srcRC.left = 0;
     srcRC. top = 0;
     srcRC.right = 200;
     srcRC.bottom = 200;
     destRC.left = 0;
     destRC. top = 0;
     destRC.right = 200;
     destRC.bottom = 200;
     bltData.hDstResource = pPresent->hSrcResource;
     bltData.DstSubResourceIndex = pPresent->SrcSubResourceIndex;
     bltData.hSrcResource = hRes;
     bltData.SrcSubResourceIndex = 0;
     bltData.SrcRect = srcRC;
     bltData.DstRect = destRC;
     return DdiBlt(hDevice, &bltData);
}

如有错误之处,还请各位指正。欢迎各位大牛小牛牛牛拍砖了。

显卡驱动入手的构造D3DHook的方法相关推荐

  1. 安装驱动显卡重启计算机,Win7电脑安装显卡驱动后一直重启的解决方法

    显卡驱动就是用来驱动显卡的程序,它是硬件所对应的软件,而有不少用户发现在win7系统中安装了显卡驱动之后,电脑就一直不停的重启,而进入到安装模式将显卡驱动卸载后就正常,这是怎么回事呢,接下来小编就跟大 ...

  2. tesla p4 linux驱动,Ubuntu 16.04. 装tesla p4 显卡驱动+cuda9.0+docker+nvidia-docker 详细方法,这里是服务器为主...

    Ubuntu 16.04. 装tesla p4 显卡驱动+cuda9.0+docker+nvidia-docker 详细方法,这里是服务器为主 这里 说明一下,我也是在网上看的教程,小白一个,通过好几 ...

  3. amd显卡驱动linux 卸载,AMD显卡驱动安装和卸载的正确方法

    不正确的卸载和安装或升级会导致各种问题 比如蓝屏/驱动安装不了/新特性的功能没有用/CCC打不开/游戏出问题.下面是学习啦小编跟大家分享的是AMD显卡驱动安装和卸载的正确方法,欢迎大家来阅读学习. A ...

  4. Ubuntu 安装ATI显卡驱动12.4失败的解决方法

    A卡驱动很不靠谱,前几个版本包括最新的12.4在正常情况下是无法安装成功的. 难道ATI觉得Linux用户都是大神...可以自己给他们的驱动打补丁,所以故意放出会出现错误的驱动. ========== ...

  5. linux 显卡亮度,在Deepin 15.10系统中安装NVIDIA显卡驱动后设置屏幕亮度的方法

    首先需要在Deepin 15.10操作系统中安装NVIDIA显卡驱动,参考在Deepin系统中安装英伟达NVIDIA显卡驱动的方法一文,然后按照下面的两种方法设置屏幕亮度,实测在Deepin 15.1 ...

  6. 华硕K42JC安装显卡驱动后进不了系统解决方法

    K42JC是双显卡,Inter集显和NIVIDA独显,一开始用驱动精灵自动更新后,进入win7,在输入用户名密码后,用停留在登陆界面,那个圆圈一直在转啊转.只能强制关机.再次开机,按F8,进入安全模式 ...

  7. ubuntu16.04安装NVIDIA显卡驱动和C++开发环境配置方法

    sudo apt-get purge nvidia* sudo add-apt-repository ppa:graphics-drivers/ppa sudo apt-get update sudo ...

  8. Ubuntu Linux 3D桌面完全教程 显卡驱动安装方法 compiz特效介绍

    [2011年5月7日更新] Ubuntu Linux 3D桌面完全教程,显卡驱动安装方法,compiz特效介绍,常见问题解答. 本教程的从2008年至今,经历了三个重大版本的修改: 最早是一善鱼编写并 ...

  9. Ubuntu 11.10 Linux 3D桌面完全教程,显卡驱动安装方法,compiz特效介绍,常见问题解答

    学技术怎么能不会用服务器,阿里云服务器现8折优惠,还有更多优惠券限量发放 https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.ht ...

最新文章

  1. Python 抖音机器人,论如何在抖音上找到漂亮小姐姐?
  2. 跨平台使用exp/imp进行数据库转移时出现丢失对象的原因
  3. Mathematica常用命令
  4. Python实现Adaboost
  5. 评测任务征集 | 全国知识图谱与语义计算大会(CCKS 2022)
  6. angular下拉框点击无反应_angular 实现 下拉菜单 的 点击其他区域关闭下拉菜单功能?...
  7. 【Kafka】kafka报错 UnknownHostException: %HOSTGROUP::host_group_zookeeper%: Temporary failure in name re
  8. 罗技无线网卡linux,linux(debian)安装USB无线网卡(tp-link TL-WN725N rtl8188eu )
  9. 事务(进程 ID )与另一个进程已被死锁在 lock 资源上,且该事务已被选作死锁牺牲品。请重新运行该事务...
  10. 怎么获取求生之路服务器信息失败,新人服务器出现问题 求助求助!!!!!...
  11. 7 ida pro 网盘_7月上市新车汇总 日产轩逸领衔 自主高端红旗HS7最具潜力
  12. Linux的WIFI架构,Linux Wireless架构总结
  13. 代码实现利用inf文件安装硬件驱动
  14. 马氏距离(Mahalanobis Distance)与欧式距离
  15. Hopping Rabbit---牛客
  16. Morsel-Driven Parallelism: 一种NUMA感知的并行Query Execution框架
  17. Unity shader护盾特效
  18. easyui treegrid
  19. 如何锻炼现货白银的心态?
  20. 阿里云ECS主机部署LAMP环境

热门文章

  1. 山寨手机流行海外贡献排行
  2. 这个策略曾年赚00美
  3. Java面向对象之泛型基本使用
  4. Discuz!插件模板实现DIY的方法
  5. 动态 SQL 你还敢用?
  6. 【汇正财经】蓝皮书强推新能源发展,储能并网加速景气提升
  7. Oracle P6软件编制WBS结构的原则
  8. 谷歌显示不安全连接到服务器,Chrome 浏览器显示“网站连接不安全”,是什么原因?...
  9. 软件行业和饭店惊人的相似
  10. 重装系统软件哪个好用?懒人重装系统方法推荐