分类 默认分类 下的文章

n卡截图是通过dwm实现的
2025-08-28T07:30:38.png
其中nvwgf2umx.dll为n卡用户层模块,nvspcap64.dll为n卡截图模块
2025-08-28T07:32:11.png
nvspcap64.dll模块导出了三个函数,其中QueryShadowPlayDdiShimInterface可以查询到接口
2025-08-28T07:34:30.png
可以发现导出一堆代理函数,n卡驱动会调用,其中两很重要
1.CreateSession_V2创建实例
2.DdiPrePresent_V2 present的前回调函数(截图部分)
先看CreateSession_V2

Instance = CShadowPlayProxyShim__CreateInstance();

__int64 __fastcall sub_18003B480(__int64 a1)
{
  __int64 v2; // rax
  __int64 v3; // rax
  CHAR MultiByteStr[272]; // [rsp+70h] [rbp-548h] BYREF
  WCHAR Filename[264]; // [rsp+180h] [rbp-438h] BYREF
  WCHAR WideCharStr[264]; // [rsp+390h] [rbp-228h] BYREF

  *(_QWORD *)a1 = &ShadowPlayDdiShim_vtable;

一直追可以找到 vtable instance 这个后面会经常用到
下面可以研究DdiPrePresent_V2 中是怎么截图的了

return (*(__int64 (__fastcall **)(__int64, __int64 *))(*(_QWORD *)a1 + 0x40LL))(a1, v14);

分析其中调用了一个虚函数,根据刚才的虚函数表找到函数

if ( *(_QWORD *)(v46 + 8) )
    {
      if ( (unsigned __int8)CSPCaptureScreenShot__Initialise(*(_QWORD *)(a1 + 80), (__int64)PerformanceCount) )
      {
        CurrentProcessId = GetCurrentProcessId();
        if ( *(_BYTE *)(v46 + 56) )
        {
          v45 = sub_18000BDAC(v46, CurrentProcessId, v47);
          goto LABEL_116;
        }
      }
      else
      {
        sub_180001C21(v48);
        sub_180008F6C(7LL, "CSPCaptureScreenShot::CaptureScreenShotWrapper: ScreenShot Initialize failed");
      }
    }

其中截图部分,通过文字可以知道,sub_18000BDAC是真正的截图部分。
进入,截图函数

v13 = sub_180003E22(v12, 0LL, 0LL);
LABEL_16:
  v14 = v13;
  if ( !v13 )
    goto LABEL_38;
  v15 = *(_QWORD *)(a1 + 8);
  v25[7] = v13 + 160;
  v16 = (*(__int64 (__fastcall **)(__int64, _QWORD *))(*(_QWORD *)v15 + 8LL))(v15, v25);
  if ( v16 )
  {
    dword_1802BFF70 = 7;
    if ( dword_1802C00AC <= 7 )
      sub_18000A1D7(
        &off_1802BFF60,
        "CSPCaptureScreenShot::CaptureScreenShot: Screenshot Capture Frame Failed(0x%X)",
        v16);
    if ( *(_QWORD *)a1 )
    {
      sub_180009CC3(a1);
      return 0;
    }
    return 0;
  }

其中v13是共享buffer,v25[7] = v13 + 160是buffer中的截图部分,下面需要追v16 = (*(__int64 (__fastcall **)(__int64, _QWORD *))(*(_QWORD *)v15 + 8LL))(v15, v25)这个函数是哪里来的。v15 = (_QWORD )(a1 + 8);
可知v15是a1的一个成员。
回到DdiPrePresent_V2

v45 = sub_18000BDAC(v46, CurrentProcessId, v47);
v46 = *(_QWORD *)(instance + 80);

发现截图函数的a1是instance的偏移80部分,其实a1是CSPCaptureScreenShot的对象。
往上找有这么一行

*(_QWORD *)(*(_QWORD *)(instance + 80) + 8LL) = *(_QWORD *)(*(_QWORD *)(instance + 64) + 24LL);

继续向上

v43 = CSPRendererManager__GetRenderer(*(_QWORD *)(instance + 56), *(_QWORD *)(a2 + 8), 0, 0, 0LL, 0);
    *(_QWORD *)(instance + 64) = v43;

可是偏移64是CSPRendererManager,分析CSPRendererManager__GetRenderer
sub_18002DC50

*(_BYTE *)(v15 + 16) = 0;
        *(_BYTE *)(v15 + 37) = 0;
        *(_QWORD *)(v15 + 8) = a3;
        *(_QWORD *)v15 = a2;
        *(_DWORD *)(v15 + 20) = a4;
        *(_QWORD *)(v15 + 24) = a5;
        *(_DWORD *)(v15 + 32) = a6;
        *(_BYTE *)(v15 + 46) = 0;
        *(_QWORD *)(v15 + 48) = 0LL;
        *(_BYTE *)(v15 + 36) = 0;
        *(_QWORD *)(v15 + 38) = 0LL;

可知v15 + 24也就是截图调用的对象为 a5
向上查找引用可知sub_18003E640

  v43 = CSPRendererManager__GetRenderer(
          v36,
          *(_QWORD *)(a2 + 8),
          *(_QWORD *)(a2 + 24),
          *(_DWORD *)(a2 + 40),
          *(_QWORD *)(a2 + 32),
          *(_DWORD *)(a2 + 16));

其中截图对象为a2 + 32
向上查找分析是个虚函数调用.rdata:0000000180247A88 dq offset sub_18000353F

Instance = CShadowPlayProxyShim__CreateInstance_();
    if ( Instance )
    {
      HIDWORD(CreateSession_V2_Args[0]) = 0;
      HIDWORD(CreateSession_V2_Args[2]) = 0;
      HIDWORD(CreateSession_V2_Args[5]) = 0;
      sub_180008959((__int64)v39, 0LL, 926LL);
      v25 = a2[14] == 0;
      v26 = (int *)*((_QWORD *)a2 + 6);
      CreateSession_V2_Args[1] = *((_QWORD *)a2 + 1);
      v37 = !v25;
      CreateSession_V2_Args[3] = *((_QWORD *)a2 + 3);
      LODWORD(CreateSession_V2_Args[2]) = a2[4];
      LODWORD(CreateSession_V2_Args[5]) = a2[8];
      LODWORD(CreateSession_V2_Args[0]) = *a2;
      v38 = *(_BYTE *)(v9 + 91);
      LODWORD(CreateSession_V2_Args[6]) = a2[17];
      HIDWORD(CreateSession_V2_Args[6]) = v35[0];
      CreateSession_V2_Args[4] = v5;
      if ( v26 )
        sub_180001203(v39, 100LL, v26, -1LL);
      if ( (unsigned int)*a2 >= 0x70340 )
      {
        if ( *((_WORD *)a2 + 156) )
          sub_180001203(v40, 260LL, a2 + 78, -1LL);
        v26 = a2 + 28;
        if ( *((_WORD *)a2 + 56) )
          sub_180001203(v41, 100LL, v26, -1LL);
      }
      v21 = (*(__int64 (__fastcall **)(__int64, __int64 *, int *))(*(_QWORD *)Instance + 32LL))(
              Instance,
              CreateSession_V2_Args,
              v26);
    }

然后发现这个虚函数会被调用CreateSession_V2

CreateSession_V2_Args[4] = v5;
 v5 = *((_QWORD *)a2 + 9);

可以找到截图对象是CreateSession_V2是第二个参数的偏移72。截图函数是其第2个虚函数(+8).

 sub_180004769(
        v19,
        6LL,
        "CreateSession_V2: Proxyshim instance (0x%x), pArgs->ver = 0x%x, pArgs->hDevice = 0x%x, hLogoRenderer = 0x%x, hSc"
        "reenshotCaptureDelegate = 0x%x, pArgs->d3dVersion = 0x%X, pArgs->rendererVersion = 0x%x, pArgs->shadowPlayFlags = 0x%x",
        v14,
        *(_DWORD *)a2,
        *(_QWORD *)(a2 + 8),
        *(_QWORD *)(a2 + 24),
        v5,
        *(_DWORD *)(a2 + 16),
        *(_DWORD *)(a2 + 32),
        *(_DWORD *)(a2 + 68));
    }

可知该对象名字叫hSreenshotCaptureDelegate

开启联机方:
Minecraft打开对局域网开放
2024-12-12T06:04:26.png
输入端口号
2024-12-12T06:06:05.png
超级房间,获取房间号
2024-12-12T06:06:25.png

接受联机方
输入房间号
2024-12-12T06:12:19.png
加入房间,获取地址
2024-12-12T06:07:39.png
Minecraft中输入地址即可

视频教程:https://www.bilibili.com/video/BV1WLqYYaEzh/?spm_id_from=333.999.0.0
查看所有房间:
https://ml.hhhhhi.com/
GitHub:
https://github.com/oakboat/MineLink
蓝奏云:
https://wwos.lanzoub.com/b0pm7c5kh
密码:7cqx