使用Native API查询Windows硬盘分区系统设备名称

查询硬盘分区名
本文介绍了一个C++程序,用于查询Windows系统中指定物理硬盘分区的设备名称。通过加载ntdll.dll库并调用相关API,如ZwOpenSymbolicLinkObject和ZwQuerySymbolicLinkObject,可以获取符号链接对应的设备路径。
                         
标签: windowsapiattributeswinapistringobject
1932人阅读 评论(0) 收藏 举报
分类:


  1. #include <windows.h>  
  2. #include <tchar.h>  
  3. #include <stdlib.h>  
  4. #include <stdio.h>  
  5. // 定义函数返回值  
  6. typedef ULONG NTSTATUS;  
  7. // 宽字节字符串结构定义  
  8.   
  9. typedef struct _UNICODE_STRING {  
  10.     USHORT  Length;  
  11.     USHORT  MaximumLength;  
  12.     PWSTR  Buffer;  
  13. } UNICODE_STRING, *PUNICODE_STRING;  
  14.   
  15. // 对象属性定义  
  16.   
  17. typedef struct _OBJECT_ATTRIBUTES {  
  18.     ULONG Length;  
  19.     HANDLE RootDirectory;  
  20.     UNICODE_STRING *ObjectName;  
  21.     ULONG Attributes;  
  22.     PSECURITY_DESCRIPTOR SecurityDescriptor;  
  23.     PSECURITY_QUALITY_OF_SERVICE SecurityQualityOfService;  
  24. } OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;  
  25.   
  26. // 基本信息定义  
  27.   
  28. typedef struct _DIRECTORY_BASIC_INFORMATION {  
  29.     UNICODE_STRING ObjectName;  
  30.     UNICODE_STRING ObjectTypeName;  
  31. } DIRECTORY_BASIC_INFORMATION, *PDIRECTORY_BASIC_INFORMATION;  
  32.   
  33. // 返回值或状态类型定义  
  34. #define OBJ_CASE_INSENSITIVE      0x00000040L  
  35. #define DIRECTORY_QUERY           (0x0001)  
  36. #define STATUS_SUCCESS            ((NTSTATUS)0x00000000L) // ntsubauth  
  37. #define STATUS_MORE_ENTRIES       ((NTSTATUS)0x00000105L)  
  38. #define STATUS_NO_MORE_ENTRIES    ((NTSTATUS)0x8000001AL)  
  39. #define STATUS_BUFFER_TOO_SMALL   ((NTSTATUS)0xC0000023L)  
  40. #define SYMBOLIC_LINK_QUERY       (0x0001)  
  41. #define SYMBOLIC_LINK_ALL_ACCESS  (STANDARD_RIGHTS_REQUIRED | 0x1)  
  42. // 初始化对象属性宏定义  
  43. #define InitializeObjectAttributes( p, n, a, r, s ) { \  
  44.     (p)->Length = sizeof( OBJECT_ATTRIBUTES );\  
  45.     (p)->RootDirectory = r;\  
  46.     (p)->Attributes = a;\  
  47.     (p)->ObjectName = n;\  
  48.     (p)->SecurityDescriptor = s;\  
  49.     (p)->SecurityQualityOfService = NULL;\  
  50. }  
  51.   
  52. // 字符串初始化  
  53. typedef VOID (CALLBACK* RTLINITUNICODESTRING)(PUNICODE_STRING, PCWSTR);  
  54. RTLINITUNICODESTRING RtlInitUnicodeString;  
  55. // 字符串比较  
  56. typedef  
  57.     BOOLEAN  
  58.     (WINAPI *RTLEQUALUNICODESTRING)(  
  59.     const UNICODE_STRING *String1,  
  60.     const UNICODE_STRING *String2,  
  61.     BOOLEAN CaseInSensitive  
  62.     );  
  63. RTLEQUALUNICODESTRING RtlEqualUnicodeString;  
  64. // 打开对象  
  65. typedef NTSTATUS (WINAPI *ZWOPENDIRECTORYOBJECT)(  
  66.     OUT PHANDLE DirectoryHandle,  
  67.     IN ACCESS_MASK DesiredAccess,  
  68.     IN POBJECT_ATTRIBUTES ObjectAttributes  
  69.     );  
  70. ZWOPENDIRECTORYOBJECT ZwOpenDirectoryObject;  
  71. // 查询对象  
  72. typedef  
  73.     NTSTATUS  
  74.     (WINAPI *ZWQUERYDIRECTORYOBJECT)(  
  75.     IN HANDLE DirectoryHandle,  
  76.     OUT PVOID Buffer,  
  77.     IN ULONG BufferLength,  
  78.     IN BOOLEAN ReturnSingleEntry,  
  79.     IN BOOLEAN RestartScan,  
  80.     IN OUT PULONG Context,  
  81.     OUT PULONG ReturnLength OPTIONAL  
  82.     );  
  83. ZWQUERYDIRECTORYOBJECT ZwQueryDirectoryObject;  
  84. // 打开符号链接对象  
  85. typedef  
  86.     NTSTATUS  
  87.     (WINAPI *ZWOPENSYMBOLICKLINKOBJECT)(  
  88.     OUT PHANDLE SymbolicLinkHandle,  
  89.     IN ACCESS_MASK DesiredAccess,  
  90.     IN POBJECT_ATTRIBUTES ObjectAttributes  
  91.     );  
  92. ZWOPENSYMBOLICKLINKOBJECT ZwOpenSymbolicLinkObject;  
  93. // 查询符号链接对象  
  94. typedef  
  95.     NTSTATUS  
  96.     (WINAPI *ZWQUERYSYMBOLICKLINKOBJECT)(  
  97.     IN HANDLE SymbolicLinkHandle,  
  98.     IN OUT PUNICODE_STRING TargetName,  
  99.     OUT PULONG ReturnLength OPTIONAL  
  100.     );  
  101. ZWQUERYSYMBOLICKLINKOBJECT ZwQuerySymbolicLinkObject;  
  102. // 关闭已经打开的对象  
  103. typedef  
  104.     NTSTATUS  
  105.     (WINAPI *ZWCLOSE)(  
  106.     IN HANDLE Handle  
  107.     );  
  108. ZWCLOSE ZwClose;  
  109.   
  110. #define InitObjectAttributes( p, n, a, r, s ) { \  
  111.     (p)->Length = sizeof( OBJECT_ATTRIBUTES );          \  
  112.     (p)->RootDirectory = r;                             \  
  113.     (p)->Attributes = a;                                \  
  114.     (p)->ObjectName = n;                                \  
  115.     (p)->SecurityDescriptor = s;                        \  
  116.     (p)->SecurityQualityOfService = NULL;               \  
  117. }  
  118.   
  119. #define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)  
  120. #define STATUS_INSUFFICIENT_RESOURCES    ((NTSTATUS)0xC000009AL)     // ntsubauth  
  121.   
  122.   
  123. HMODULE hNtdll = NULL;  
  124.   
  125. BOOL LoadNtdllModule(void)  
  126. {  
  127.     hNtdll = LoadLibrary(_T("ntdll.dll" ));  
  128.     if ( NULL == hNtdll ) {  
  129.         _tprintf(_T("[%s]--Load ntdll.dll failed(%ld).\r\n"), __FUNCTION__, GetLastError());  
  130.         return FALSE;  
  131.     }  
  132.     return TRUE;  
  133. }  
  134.   
  135. void FreeNtdllModule(void)  
  136. {  
  137.     if (hNtdll) {  
  138.         FreeLibrary(hNtdll);  
  139.     }  
  140. }  
  141.   
  142. BOOL InitNtdllAPI(void)  
  143. {  
  144.     RtlInitUnicodeString      = (RTLINITUNICODESTRING)GetProcAddress( hNtdll, "RtlInitUnicodeString");  
  145.     RtlEqualUnicodeString     = (RTLEQUALUNICODESTRING)GetProcAddress( hNtdll, "RtlEqualUnicodeString");  
  146.     ZwOpenDirectoryObject     = (ZWOPENDIRECTORYOBJECT)GetProcAddress( hNtdll, "ZwOpenDirectoryObject");  
  147.     ZwQueryDirectoryObject    = (ZWQUERYDIRECTORYOBJECT)GetProcAddress( hNtdll, "ZwQueryDirectoryObject");  
  148.     ZwOpenSymbolicLinkObject  = (ZWOPENSYMBOLICKLINKOBJECT)GetProcAddress( hNtdll, "ZwOpenSymbolicLinkObject");  
  149.     ZwQuerySymbolicLinkObject = (ZWQUERYSYMBOLICKLINKOBJECT)GetProcAddress( hNtdll, "ZwQuerySymbolicLinkObject");  
  150.     ZwClose                   = (ZWCLOSE)GetProcAddress( hNtdll, "ZwClose");  
  151.   
  152.     if (!RtlInitUnicodeString  
  153.         || !RtlEqualUnicodeString  
  154.         || !ZwOpenDirectoryObject  
  155.         || !ZwQueryDirectoryObject  
  156.         || !ZwOpenSymbolicLinkObject  
  157.         || !ZwQuerySymbolicLinkObject  
  158.         || !ZwClose){  
  159.         return FALSE;  
  160.     }  
  161.     return TRUE;  
  162. }  
  163.   
  164. NTSTATUS QuerySymbolicLink(  
  165.     IN PUNICODE_STRING SymbolicLinkName,  
  166.     OUT PUNICODE_STRING LinkTarget  
  167.     )  
  168. {  
  169.     OBJECT_ATTRIBUTES oa;  
  170.     NTSTATUS status;  
  171.     HANDLE handle;  
  172.   
  173.     InitObjectAttributes(&oa, SymbolicLinkName,  
  174.         OBJ_CASE_INSENSITIVE,  
  175.         0, 0);  
  176.   
  177.     status = ZwOpenSymbolicLinkObject(&handle, GENERIC_READ, &oa);  
  178.     if (!NT_SUCCESS(status)) {  
  179.         return status;  
  180.     }  
  181.   
  182.     LinkTarget->MaximumLength = MAX_PATH * sizeof(WCHAR);  
  183.     LinkTarget->Length = 0;  
  184.     LinkTarget->Buffer = (PWSTR)GlobalAlloc(GPTR, LinkTarget->MaximumLength);  
  185.     if (!LinkTarget->Buffer) {  
  186.         ZwClose(handle);  
  187.         return STATUS_INSUFFICIENT_RESOURCES;  
  188.     }  
  189.   
  190.     RtlZeroMemory(LinkTarget->Buffer, LinkTarget->MaximumLength);  
  191.   
  192.     status = ZwQuerySymbolicLinkObject(handle, LinkTarget, NULL);  
  193.     ZwClose(handle);  
  194.   
  195.     if (!NT_SUCCESS(status)) {  
  196.         GlobalFree(LinkTarget->Buffer);  
  197.     }  
  198.   
  199.     return status;  
  200. }  
  201.   
  202. BOOL QueryHardiskVolume(UINT nDiskNo, UINT nPartNo, LPTSTR lpszTargetPath, DWORD dwLength)  
  203. {  
  204.     NTSTATUS status;  
  205.     UNICODE_STRING szSymbolicLink;  
  206.     UNICODE_STRING szDeviceName;  
  207.     WCHAR *lpszSymbolicLink;  
  208.     CHAR *_lpszTargetPath;  
  209.     BOOL bRet = FALSE;  
  210.     lpszSymbolicLink = (PWSTR)GlobalAlloc(GPTR, MAX_PATH * sizeof(WCHAR));  
  211.     if (!lpszSymbolicLink){  
  212.         return FALSE;  
  213.     }  
  214.     wsprintfW(lpszSymbolicLink, L"\\Device\\Harddisk%u\\Partition%u", nDiskNo, nPartNo);  
  215.   
  216.     //RtlInitUnicodeString(&szSymbolicLink, L"\\??\\C:");  
  217.     //RtlInitUnicodeString(&szSymbolicLink, L"\\??\\Harddisk0Partition1");  
  218.     //RtlInitUnicodeString(&szSymbolicLink, L"\\Device\\Harddisk0\\Partition1");  
  219.     RtlInitUnicodeString(&szSymbolicLink, lpszSymbolicLink);  
  220.   
  221.     status = QuerySymbolicLink(&szSymbolicLink, &szDeviceName);  
  222.     if (STATUS_SUCCESS == status){  
  223.         _tprintf(_T("[%S] => [%S]\n"), szSymbolicLink.Buffer, szDeviceName.Buffer);  
  224.         if (szDeviceName.Length <= dwLength) {  
  225. #ifndef _UNICODE  
  226.             _lpszTargetPath = WideCharToAnsi(szDeviceName.Buffer);            
  227.             if (_lpszTargetPath){  
  228.                 //CopyMemory(lpszTargetPath, _lpszTargetPath, lstrlenA(_lpszTargetPath));  
  229.                 lstrcpyA(lpszTargetPath, _lpszTargetPath);  
  230.                 bRet = TRUE;  
  231.                 delete [] _lpszTargetPath;  
  232.             }  
  233. #else  
  234.             CopyMemory(lpszTargetPath, szDeviceName.Buffer, szDeviceName.Length)  
  235. #endif  
  236.         }  
  237.         GlobalFree(szDeviceName.Buffer);  
  238.     }  
  239.     GlobalFree(lpszSymbolicLink);  
  240.     return bRet;  
  241. }  
#include <windows.h>
#include <tchar.h>
#include <stdlib.h>
#include <stdio.h>
// 定义函数返回值
typedef ULONG NTSTATUS;
// 宽字节字符串结构定义

typedef struct _UNICODE_STRING {
	USHORT  Length;
	USHORT  MaximumLength;
	PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

// 对象属性定义

typedef struct _OBJECT_ATTRIBUTES {
	ULONG Length;
	HANDLE RootDirectory;
	UNICODE_STRING *ObjectName;
	ULONG Attributes;
	PSECURITY_DESCRIPTOR SecurityDescriptor;
	PSECURITY_QUALITY_OF_SERVICE SecurityQualityOfService;
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;

// 基本信息定义

typedef struct _DIRECTORY_BASIC_INFORMATION {
	UNICODE_STRING ObjectName;
	UNICODE_STRING ObjectTypeName;
} DIRECTORY_BASIC_INFORMATION, *PDIRECTORY_BASIC_INFORMATION;

// 返回值或状态类型定义
#define OBJ_CASE_INSENSITIVE      0x00000040L
#define DIRECTORY_QUERY           (0x0001)
#define STATUS_SUCCESS            ((NTSTATUS)0x00000000L) // ntsubauth
#define STATUS_MORE_ENTRIES       ((NTSTATUS)0x00000105L)
#define STATUS_NO_MORE_ENTRIES    ((NTSTATUS)0x8000001AL)
#define STATUS_BUFFER_TOO_SMALL   ((NTSTATUS)0xC0000023L)
#define SYMBOLIC_LINK_QUERY       (0x0001)
#define SYMBOLIC_LINK_ALL_ACCESS  (STANDARD_RIGHTS_REQUIRED | 0x1)
// 初始化对象属性宏定义
#define InitializeObjectAttributes( p, n, a, r, s ) { \
	(p)->Length = sizeof( OBJECT_ATTRIBUTES );\
	(p)->RootDirectory = r;\
	(p)->Attributes = a;\
	(p)->ObjectName = n;\
	(p)->SecurityDescriptor = s;\
	(p)->SecurityQualityOfService = NULL;\
}

// 字符串初始化
typedef VOID (CALLBACK* RTLINITUNICODESTRING)(PUNICODE_STRING, PCWSTR);
RTLINITUNICODESTRING RtlInitUnicodeString;
// 字符串比较
typedef
	BOOLEAN
	(WINAPI *RTLEQUALUNICODESTRING)(
	const UNICODE_STRING *String1,
	const UNICODE_STRING *String2,
	BOOLEAN CaseInSensitive
	);
RTLEQUALUNICODESTRING RtlEqualUnicodeString;
// 打开对象
typedef NTSTATUS (WINAPI *ZWOPENDIRECTORYOBJECT)(
	OUT PHANDLE DirectoryHandle,
	IN ACCESS_MASK DesiredAccess,
	IN POBJECT_ATTRIBUTES ObjectAttributes
	);
ZWOPENDIRECTORYOBJECT ZwOpenDirectoryObject;
// 查询对象
typedef
	NTSTATUS
	(WINAPI *ZWQUERYDIRECTORYOBJECT)(
	IN HANDLE DirectoryHandle,
	OUT PVOID Buffer,
	IN ULONG BufferLength,
	IN BOOLEAN ReturnSingleEntry,
	IN BOOLEAN RestartScan,
	IN OUT PULONG Context,
	OUT PULONG ReturnLength OPTIONAL
	);
ZWQUERYDIRECTORYOBJECT ZwQueryDirectoryObject;
// 打开符号链接对象
typedef
	NTSTATUS
	(WINAPI *ZWOPENSYMBOLICKLINKOBJECT)(
	OUT PHANDLE SymbolicLinkHandle,
	IN ACCESS_MASK DesiredAccess,
	IN POBJECT_ATTRIBUTES ObjectAttributes
	);
ZWOPENSYMBOLICKLINKOBJECT ZwOpenSymbolicLinkObject;
// 查询符号链接对象
typedef
	NTSTATUS
	(WINAPI *ZWQUERYSYMBOLICKLINKOBJECT)(
	IN HANDLE SymbolicLinkHandle,
	IN OUT PUNICODE_STRING TargetName,
	OUT PULONG ReturnLength OPTIONAL
	);
ZWQUERYSYMBOLICKLINKOBJECT ZwQuerySymbolicLinkObject;
// 关闭已经打开的对象
typedef
	NTSTATUS
	(WINAPI *ZWCLOSE)(
	IN HANDLE Handle
	);
ZWCLOSE ZwClose;

#define InitObjectAttributes( p, n, a, r, s ) { \
	(p)->Length = sizeof( OBJECT_ATTRIBUTES );          \
	(p)->RootDirectory = r;                             \
	(p)->Attributes = a;                                \
	(p)->ObjectName = n;                                \
	(p)->SecurityDescriptor = s;                        \
	(p)->SecurityQualityOfService = NULL;               \
}

#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)
#define STATUS_INSUFFICIENT_RESOURCES    ((NTSTATUS)0xC000009AL)     // ntsubauth


HMODULE hNtdll = NULL;

BOOL LoadNtdllModule(void)
{
	hNtdll = LoadLibrary(_T("ntdll.dll" ));
	if ( NULL == hNtdll ) {
		_tprintf(_T("[%s]--Load ntdll.dll failed(%ld).\r\n"), __FUNCTION__, GetLastError());
		return FALSE;
	}
	return TRUE;
}

void FreeNtdllModule(void)
{
	if (hNtdll) {
		FreeLibrary(hNtdll);
	}
}

BOOL InitNtdllAPI(void)
{
	RtlInitUnicodeString      = (RTLINITUNICODESTRING)GetProcAddress( hNtdll, "RtlInitUnicodeString");
	RtlEqualUnicodeString     = (RTLEQUALUNICODESTRING)GetProcAddress( hNtdll, "RtlEqualUnicodeString");
	ZwOpenDirectoryObject     = (ZWOPENDIRECTORYOBJECT)GetProcAddress( hNtdll, "ZwOpenDirectoryObject");
	ZwQueryDirectoryObject    = (ZWQUERYDIRECTORYOBJECT)GetProcAddress( hNtdll, "ZwQueryDirectoryObject");
	ZwOpenSymbolicLinkObject  = (ZWOPENSYMBOLICKLINKOBJECT)GetProcAddress( hNtdll, "ZwOpenSymbolicLinkObject");
	ZwQuerySymbolicLinkObject = (ZWQUERYSYMBOLICKLINKOBJECT)GetProcAddress( hNtdll, "ZwQuerySymbolicLinkObject");
	ZwClose                   = (ZWCLOSE)GetProcAddress( hNtdll, "ZwClose");

	if (!RtlInitUnicodeString
		|| !RtlEqualUnicodeString
		|| !ZwOpenDirectoryObject
		|| !ZwQueryDirectoryObject
		|| !ZwOpenSymbolicLinkObject
		|| !ZwQuerySymbolicLinkObject
		|| !ZwClose){
		return FALSE;
	}
	return TRUE;
}

NTSTATUS QuerySymbolicLink(
	IN PUNICODE_STRING SymbolicLinkName,
	OUT PUNICODE_STRING LinkTarget
	)
{
	OBJECT_ATTRIBUTES oa;
	NTSTATUS status;
	HANDLE handle;

	InitObjectAttributes(&oa, SymbolicLinkName,
		OBJ_CASE_INSENSITIVE,
		0, 0);

	status = ZwOpenSymbolicLinkObject(&handle, GENERIC_READ, &oa);
	if (!NT_SUCCESS(status)) {
		return status;
	}

	LinkTarget->MaximumLength = MAX_PATH * sizeof(WCHAR);
	LinkTarget->Length = 0;
	LinkTarget->Buffer = (PWSTR)GlobalAlloc(GPTR, LinkTarget->MaximumLength);
	if (!LinkTarget->Buffer) {
		ZwClose(handle);
		return STATUS_INSUFFICIENT_RESOURCES;
	}

	RtlZeroMemory(LinkTarget->Buffer, LinkTarget->MaximumLength);

	status = ZwQuerySymbolicLinkObject(handle, LinkTarget, NULL);
	ZwClose(handle);

	if (!NT_SUCCESS(status)) {
		GlobalFree(LinkTarget->Buffer);
	}

	return status;
}

BOOL QueryHardiskVolume(UINT nDiskNo, UINT nPartNo, LPTSTR lpszTargetPath, DWORD dwLength)
{
	NTSTATUS status;
	UNICODE_STRING szSymbolicLink;
	UNICODE_STRING szDeviceName;
	WCHAR *lpszSymbolicLink;
	CHAR *_lpszTargetPath;
	BOOL bRet = FALSE;
	lpszSymbolicLink = (PWSTR)GlobalAlloc(GPTR, MAX_PATH * sizeof(WCHAR));
	if (!lpszSymbolicLink){
		return FALSE;
	}
	wsprintfW(lpszSymbolicLink, L"\\Device\\Harddisk%u\\Partition%u", nDiskNo, nPartNo);

	//RtlInitUnicodeString(&szSymbolicLink, L"\\??\\C:");
	//RtlInitUnicodeString(&szSymbolicLink, L"\\??\\Harddisk0Partition1");
	//RtlInitUnicodeString(&szSymbolicLink, L"\\Device\\Harddisk0\\Partition1");
	RtlInitUnicodeString(&szSymbolicLink, lpszSymbolicLink);

	status = QuerySymbolicLink(&szSymbolicLink, &szDeviceName);
	if (STATUS_SUCCESS == status){
		_tprintf(_T("[%S] => [%S]\n"), szSymbolicLink.Buffer, szDeviceName.Buffer);
		if (szDeviceName.Length <= dwLength) {
#ifndef _UNICODE
			_lpszTargetPath = WideCharToAnsi(szDeviceName.Buffer);			
			if (_lpszTargetPath){
				//CopyMemory(lpszTargetPath, _lpszTargetPath, lstrlenA(_lpszTargetPath));
				lstrcpyA(lpszTargetPath, _lpszTargetPath);
				bRet = TRUE;
				delete [] _lpszTargetPath;
			}
#else
			CopyMemory(lpszTargetPath, szDeviceName.Buffer, szDeviceName.Length)
#endif
		}
		GlobalFree(szDeviceName.Buffer);
	}
	GlobalFree(lpszSymbolicLink);
	return bRet;
}


测试: 获取系统第一块物理硬盘第一分区设备名称

  1. TCHAR szDeviceName[MAX_PATH];  
  2. if (LoadNtdllModule()) {  
  3.     if(InitNtdllAPI()){  
  4.         QueryHardiskVolume(0, 1, szDeviceName, MAX_PATH);               
  5.     }  
  6.     FreeNtdllModule();  
  7. }  
    TCHAR szDeviceName[MAX_PATH];
    if (LoadNtdllModule()) {
        if(InitNtdllAPI()){
            QueryHardiskVolume(0, 1, szDeviceName, MAX_PATH);             
        }
        FreeNtdllModule();
    }



0
0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值