iTimothy

君看一叶舟,出没风波里


  • 首页

  • 分类

  • 归档

  • 项目

  • 关于

用.DEF文件导出DLL中的函数

发表于 2004-10-27 | 分类于 技术控 | | 阅读次数:
字数统计: 854 字 | 阅读时长 ≈ 3 分钟

通常我们在调用DLL时所需的DLL文件必须位于以下三个目录之一:   (1)Windows的系统目录:\windows\system;   (2)DOS中path所指出的任何目录;   (3)程序所在的目录。   一、动态链接库的结构

  动态链接库中定义有两种函数:导出函数(export function)和内部函数(internal function), 导出函数可以被其它模块调用,内部函数只能在库内部使用。我们在用C++定制动态库文件时, 需要编写的就是包含导出函数表的模块定义文件(.DEF)和实现导出函数功能的C++文件。下面以 Sample.dll为例介绍DEF文件和实现文件的结构。   1.模块定义文件(.DEF)是一个或多个用于描述DLL属性的模块语句组成的文本文件,每个DEF文 件至少必须包含以下模块定义语句:   ·第一个语句必须是LIBRARY语句,指出DLL的名字;   ·EXPORTS语句列出被导出函数的名字;   ·可以使用DESCRIPTION语句描述DLL的用途(此句可选);   ·”;”对一行进行注释(可选)。   2.实现文件   实现入口表函数的cpp文件中,包含DLL入口点处理的API函数和导出函数的代码。   二、创建Sample.dll    1.首先创建Sample.dll的工程,启动VC++5.0按以下步骤生成DLL工程:   ·在选单中选择File\New\Project;   ·在工程列表中选择Win32 Dynamic-Link Library;   ·在Project Name中输入工程名:Sample;   ·单击Location右边按钮,选择c:\sample目录;   ·单击OK完成,至此已创建了Sample.dll的工程文件。   2.创建Sample.def文件:   ·在选单中选择File\New\Text File;   ·输入以下代码后保存文件名”Sample.def”:    ;Sample.def    ;指出DLL的名字Sample,链接器将这个名字放到DLL导入库中    LIBRARY Sample    ;定义导出函数ShowMe()为例

   EXPORTS    ShowMe    ;def文件结束   3.创建Sample.cpp    .在选单中选择File\New\C++ Source File项    .输入以下代码后保存文件名”Sample.cpp”    //Sample.cpp    #include 〈windows.h〉    int ShowMe(void);    //DllEntryPoint为DLL入口点函数,负责初试化并终止DLL    BOOL WINAPI DllEntryPoint(HINSTANCE hDLL,DWORD dwReason,LPVOID Reserved)    { switch(dwReason)    { case DLL-PROCESS-ATTACH:    { break; }    case DLL-PROCESS-DETACH:    { break; } }    return TRUE; }    int ShowMe(void)    { //蜂鸣器响一下    MessageBeep((WORD)-1);    MessageBox(“你好!”);    return 1; }   4.编译DLL文件   从Build选单中选择Build Sample.DLL,产生Sample.DLL文件,以后就可以随时调用了。   三、在应用程序中调用DLL文件   在应用程序中要首先装入DLL后才能调用导出表中的函数,例如用MFC创建基于对话框的工 程Test,并在对话框上放置”Load”按钮,你就必须添加装载代码。   1.首先在TestDlg.cpp的首部添加变量设置代码:   //设置全局变量gLibSample用于存储DLL句柄   HINSTANCE gLibSample=NULL;   //第二个变量ShowMe是指向DLL库中ShowMe()函数的指针   typedef int( SHOWME)(void);

  SHOWME ShowMe;   2.利用ClassWizard为”Load”按钮添加装载DLL的代码:   Void CTestDlg::OnLoadButton()   { //要添加的代码如下    if(gLibMyDLL!=NULL)    { MessageBox(“The Sample.DLL has already been load.”);    return; }    //装载Sample.dll,未加路径,将在三个默认路径中寻找    gLibSample=LoadLibrary(“SAMPLE.DLL”);    //返回DLL中ShowMe()函数的地址    ShowMe=(SHOWME)GetProcAddress(gLibSample,”ShowMe”); }   3.只要DLL装载成功,在应用程序中就可以直接调用ShowMe()函数(本程序在Windows 95,VC++5.0中运行通过)。

实时时间显示

发表于 2004-10-26 | 分类于 技术控 | | 阅读次数:
字数统计: 73 字 | 阅读时长 ≈ 1 分钟

用计时器实现: SetTimer(1, 1000, NULL); 然后在响应函数: void CAlertDlg::OnTimer(UINT nIDEvent) { static CString strTemp; COleDateTime dtTime;

// 刷新显示的时间 dtTime = COleDateTime::GetCurrentTime(); strTemp.Format(“现在时间 %02i:%02i:%02i “, dtTime.GetHour(), dtTime.GetMinute(), dtTime.GetSecond()); GetDlgItem(IDC_CURTIME)->SetWindowText(strTemp); //IDC_CURTIME即为窗口显示时间的一个 //CEDIT类控件

}

VC中实现历史记录的全面清除

发表于 2004-10-26 | 分类于 技术控 | | 阅读次数:
字数统计: 9 字 | 阅读时长 ≈ 1 分钟

http://www.vckbase.com/document/viewdoc.asp?id=280

从文件装载图象

发表于 2004-10-26 | 分类于 技术控 | | 阅读次数:
字数统计: 55 字 | 阅读时长 ≈ 1 分钟

  • 这是从文件里装放位图 **/ BOOL CXxxView::OnDraw(CDC* pDC) { HBITMAP bitmap,OldBitmap; bitmap = (HBITMAP)LoadImage(NULL,”xxx.bmp”,IMAGE_BITMAP,
    0,0,LR_LOADFROMFILE|LR_CREATEDIBSECTION);
    
    CDC MemDC; MemDC.CreateCompatibleDC(pDC); OldBitmap=(HBITMAP)MemDC.SelectObject(bitmap); pDC->BitBlt(0,0,400,300,&MemDC;,0,0,SRCCOPY); MemDC.SelectObject(OldBitmap); return TRUE; }

链接错误浅谈

发表于 2004-10-26 | 分类于 技术控 | | 阅读次数:
字数统计: 1.2k 字 | 阅读时长 ≈ 4 分钟

学习VC++时经常会遇到链接错误LNK2001,该错误非常讨厌,因为对于 编程者来说,最好改的错误莫过于编译错误,而一般说来发生连接错误时, 编译都已通过。产生连接错误的原因非常多,尤其LNK2001错误,常常使人不 明其所以然。如果不深入地学习和理解VC++,要想改正连接错误LNK2001非 常困难。   初学者在学习VC++的过程中,遇到的LNK2001错误的错误消息主要为:   unresolved external symbol “symbol”(不确定的外部“符号”)。   如果连接程序不能在所有的库和目标文件内找到所引用的函数、变量或 标签,将产生此错误消息。一般来说,发生错误的原因有两个:一是所引用 的函数、变量不存在、拼写不正确或者使用错误;其次可能使用了不同版本 的连接库。   以下是可能产生LNK2001错误的原因:   一.由于编码错误导致的LNK2001。   1.不相匹配的程序代码或模块定义(.DEF)文件能导致LNK2001。例如, 如果在C++ 源文件内声明了一变量“var1”,却试图在另一文件内以变量 “VAR1”访问该变量,将发生该错误。   2.如果使用的内联函数是在.CPP文件内定义的,而不是在头文件内定 义将导致LNK2001错误。   3.调用函数时如果所用的参数类型同函数声明时的类型不符将会产生 LNK2001。   4.试图从基类的构造函数或析构函数中调用虚拟函数时将会导致LNK2001。   5.要注意函数和变量的可公用性,只有全局变量、函数是可公用的。   静态函数和静态变量具有相同的使用范围限制。当试图从文件外部访问 任何没有在该文件内声明的静态变量时将导致编译错误或LNK2001。   函数内声明的变量(局部变量) 只能在该函数的范围内使用。   C++ 的全局常量只有静态连接性能。这不同于C,如果试图在C++的 多个文件内使用全局变量也会产生LNK2001错误。一种解决的方法是需要时在 头文件中加入该常量的初始化代码,并在.CPP文件中包含该头文件;另一种 方法是使用时给该变量赋以常数。   二.由于编译和链接的设置而造成的LNK2001   1.如果编译时使用的是/NOD(/NODEFAULTLIB)选项,程序所需要的运行 库和MFC库在连接时由编译器写入目标文件模块, 但除非在文件中明确包含 这些库名,否则这些库不会被链接进工程文件。在这种情况下使用/NOD将导 致错误LNK2001。   2.如果没有为wWinMainCRTStartup设定程序入口,在使用Unicode和MFC 时将得到“unresolved external on [email protected]”的LNK2001错误信息。   3.使用/MD选项编译时,既然所有的运行库都被保留在动态链接库之内, 源文件中对“func”的引用,在目标文件里即对“impfunc” 的引用。 如果试图使用静态库LIBC.LIB或LIBCMT.LIB进行连接,将在impfunc上发 生LNK2001;如果不使用/MD选项编译,在使用MSVCxx.LIB连接时也会发生LNK2001。   4.使用/ML选项编译时,如用LIBCMT.LIB链接会在_errno上发生LNK2001。   5.当编译调试版的应用程序时,如果采用发行版模态库进行连接也会产 生LNK2001;同样,使用调试版模态库连接发行版应用程序时也会产生相同的 问题。   6.不同版本的库和编译器的混合使用也能产生问题,因为新版的库里可 能包含早先的版本没有的符号和说明。   7.在不同的模块使用内联和非内联的编译选项能够导致LNK2001。如果 创建C++库时打开了函数内联(/Ob1或/Ob2),但是在描述该函数的相应头 文件里却关闭了函数内联(没有inline关键字),这时将得到该错误信息。 为避免该问题的发生,应该在相应的头文件中用inline关键字标志内联函数。   8.不正确的/SUBSYSTEM或/ENTRY设置也能导致LNK2001。   其实,产生LNK2001的原因还有很多,以上的原因只是一部分而已,对初 学者来说这些就够理解一阵子了。但是,分析错误原因的目的是为了避免错 误的发生。LNK2001错误虽然比较困难,但是只要注意到了上述问题,还是能 够避免和予以解决的。

重载消息循环主要代码

发表于 2004-10-26 | 分类于 技术控 | | 阅读次数:
字数统计: 24 字 | 阅读时长 ≈ 1 分钟

MSG msg; if(::PeekMessage(&msg;,NULL,0,0,PM_REMOVE)){ if(msg.message==WM_QUIT) { ::PostQuitMessage(-1); }

if(!AfxGetApp()->PreTranslageMessage(&msg;)) { ::TranslateMessage(&msg;); ::DispatchMessage(&msg;); }

动态加载DLL

发表于 2004-10-26 | 分类于 技术控 | | 阅读次数:
字数统计: 44 字 | 阅读时长 ≈ 1 分钟

核心代码片断:

void CLoadLibraryTestDlg::OnLoad() { typedef int (WINAPI MyFun)(HWND,LPCTSTR,LPCTSTR,UINT); MyFun fun=NULL; HINSTANCE hHandle; hHandle=LoadLibrary(“user32.dll”); fun=(int (WINAPI )(HWND,LPCTSTR,LPCTSTR,UINT))::GetProcAddress(hHandle,”MessageBoxA”); if(fun!=NULL) { fun(NULL,”hahahahahahahaha”,”success :)”,MB_OK); } }

一份进程注入的代码

发表于 2004-10-22 | 分类于 技术控 | | 阅读次数:
字数统计: 304 字 | 阅读时长 ≈ 1 分钟

// Injection.cpp : 定义控制台应用程序的入口点。 //

#include “stdafx.h”

#include “Injection.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#endif

// 唯一的应用程序对象

CWinApp theApp;

using namespace std;

typedef struct _RemotePara{//参数结构 char pMessageBox[12]; DWORD dwMessageBox; }RemotePara; //远程线程 DWORD stdcall ThreadProc (RemotePara *lpPara){ typedef int (stdcall *MMessageBoxA)(HWND,LPCTSTR,LPCTSTR,DWORD);//定义MessageBox函数 MMessageBoxA myMessageBoxA; myMessageBoxA =(MMessageBoxA) lpPara->dwMessageBox ;//得到函数入口地址 myMessageBoxA(NULL,lpPara->pMessageBox ,lpPara->pMessageBox,0);//call return 0; } void EnableDebugPriv();//提升应用级调试权限

int _tmain(int argc, TCHAR argv[], TCHAR envp[]) { const DWORD THREADSIZE=10244; DWORD byte_write; EnableDebugPriv();//提升权限 HANDLE hWnd = ::OpenProcess (PROCESS_ALL_ACCESS,FALSE,760); if(!hWnd)return 0; void pRemoteThread =::VirtualAllocEx(hWnd,0,THREADSIZE,MEM_COMMIT| MEM_RESERVE,PAGE_EXECUTE_READWRITE); if(!pRemoteThread)return 0; if(!::WriteProcessMemory(hWnd,pRemoteThread,&ThreadProc;,THREADSIZE,0)) return 0;

//再付值 RemotePara myRemotePara; ::ZeroMemory(&myRemotePara;,sizeof(RemotePara)); HINSTANCE hUser32 = ::LoadLibrary (“user32.dll”); myRemotePara.dwMessageBox =(DWORD) ::GetProcAddress (hUser32 , “MessageBoxA”); strcat(myRemotePara.pMessageBox,”hello\0”); //写进目标进程 RemotePara pRemotePara =(RemotePara ) ::VirtualAllocEx (hWnd ,0,sizeof(RemotePara),MEM_COMMIT,PAGE_READWRITE);//注意申请空间时的页面属性 if(!pRemotePara)return 0; if(!::WriteProcessMemory (hWnd ,pRemotePara,&myRemotePara;,sizeof myRemotePara,0))return 0;

//启动线程 HANDLE hThread = ::CreateRemoteThread (hWnd ,0,0,(DWORD (__stdcall )(void ))pRemoteThread ,pRemotePara,0,&byte;_write); if(!hThread){ return 0; } return 0; }

void EnableDebugPriv( void ) { HANDLE hToken; LUID sedebugnameValue; TOKEN_PRIVILEGES tkp;

if ( ! OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken; ) ) return; if ( ! LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &sedebugnameValue; ) ){ CloseHandle( hToken ); return; } tkp.PrivilegeCount = 1; tkp.Privileges[0].Luid = sedebugnameValue; tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if ( ! AdjustTokenPrivileges( hToken, FALSE, &tkp;, sizeof tkp, NULL, NULL ) ) CloseHandle( hToken ); }

调整当前进程权限并关机

发表于 2004-10-21 | 分类于 技术控 | | 阅读次数:
字数统计: 182 字 | 阅读时长 ≈ 1 分钟

核心代码片断:

if (!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken;)) { MessageBox(“OpenProcessToken failed!”); }

LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME,&tkp.Privileges;[0].Luid); //获得本地机唯一的标识
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tkp;, 0,(PTOKEN_PRIVILEGES) NULL, 0); //调整获得的权限

if (GetLastError() != ERROR_SUCCESS)
{
    MessageBox("切换系统级权限失败!");
}

fResult =InitiateSystemShutdown(
     NULL,                                  // 要关的计算机用户名
     "关机时间已到,WINDOWS将在上面的时间内关机,请做好保存工作!",  // 显示的消息
     10,                                    // 关机所需的时间
     TRUE,                                 // ask user to close apps
     FALSE);                               //设为TRUE为重起,设为FALSE为关机
if(!fResult)
{
     MessageBox("初始化系统关机失败!");
}

tkp.Privileges[0].Attributes = 0;
AdjustTokenPrivileges(hToken, FALSE, &tkp;, 0,(PTOKEN_PRIVILEGES) NULL, 0);

if (GetLastError() != ERROR_SUCCESS)
{
     MessageBox("AdjustTokenPrivileges disable failed.");
}

ExitWindowsEx(EWX_SHUTDOWN,0);

枚举注册表键名与键值

发表于 2004-10-21 | 分类于 技术控 | | 阅读次数:
字数统计: 376 字 | 阅读时长 ≈ 2 分钟

枚举注册表键名与键值的一个函数:

// QueryKey - Enumerates the subkeys of key, and the associated // values, then copies the information about the keys and values // into a pair of edit controls and list boxes. // hDlg - Dialog box that contains the edit controls and list boxes. // hKey - Key whose subkeys and values are to be enumerated.

void QueryKey(HWND hDlg, HANDLE hKey) { CHAR achKey[MAX_PATH]; CHAR achClass[MAX_PATH] = “”; // buffer for class name DWORD cchClassName = MAX_PATH; // size of class string DWORD cSubKeys; // number of subkeys DWORD cbMaxSubKey; // longest subkey size DWORD cchMaxClass; // longest class string DWORD cValues; // number of values for key DWORD cchMaxValue; // longest value name DWORD cbMaxValueData; // longest value data DWORD cbSecurityDescriptor; // size of security descriptor FILETIME ftLastWriteTime; // last write time

DWORD i, j;
DWORD retCode, retValue;

CHAR  achValue[MAX_VALUE_NAME];
DWORD cchValue = MAX_VALUE_NAME;
CHAR  achBuff[80];

// Get the class name and the value count.
RegQueryInfoKey(hKey,        // key handle
    achClass,                // buffer for class name
    &cchClassName;,           // size of class string
    NULL,                    // reserved
    &cSubKeys;,               // number of subkeys
    &cbMaxSubKey;,            // longest subkey size
    &cchMaxClass;,            // longest class string
    &cValues;,                // number of values for this key
    &cchMaxValue;,            // longest value name
    &cbMaxValueData;,         // longest value data
    &cbSecurityDescriptor;,   // security descriptor
    &ftLastWriteTime;);       // last write time

SetDlgItemText(hDlg, IDE_CLASS, achClass);
SetDlgItemInt(hDlg, IDE_CVALUES, cValues, FALSE);

SendMessage(GetDlgItem(hDlg, IDL_LISTBOX),
    LB_ADDSTRING, 0, (LONG) "..");

// Enumerate the child keys, until RegEnumKeyEx fails. Then
// get the name of each child key and copy it into the list box.

SetCursor(LoadCursor(NULL, IDC_WAIT));
for (i = 0, retCode = ERROR_SUCCESS;
        retCode == ERROR_SUCCESS; i++)
{
    retCode = RegEnumKeyEx(hKey,
                 i,
                 achKey,
                 MAX_PATH,
                 NULL,
                 NULL,
                 NULL,
                 &ftLastWriteTime;);
    if (retCode == (DWORD)ERROR_SUCCESS)
    {
        SendMessage(GetDlgItem(hDlg, IDL_LISTBOX),
            LB_ADDSTRING, 0, (LONG) achKey);
    }
}
SetCursor(LoadCursor (NULL, IDC_ARROW));

// Enumerate the key values.
SetCursor(LoadCursor(NULL, IDC_WAIT));

if (cValues)
{
    for (j = 0, retValue = ERROR_SUCCESS;
            j < cValues; j++)
    {
        cchValue = MAX_VALUE_NAME;
        achValue[0] = '\0';
        retValue = RegEnumValue(hKey, j, achValue,
            &cchValue;,
            NULL,
            NULL,    // &dwType;,
            NULL,    // &bData;,
            NULL);   // &bcData;

        if (retValue == (DWORD) ERROR_SUCCESS )
        {
            achBuff[0] = '\0';

            // Add each value to a list box.
            if (!lstrlen(achValue))
                lstrcpy(achValue, "");
            wsprintf(achBuff, "%d) %s ", j, achValue);
            SendMessage(GetDlgItem(hDlg,IDL_LISTBOX2),
                LB_ADDSTRING, 0, (LONG) achBuff);
        }
    }

SetCursor(LoadCursor(NULL, IDC_ARROW));

}

删除非空目录下所有文件

发表于 2004-10-17 | 分类于 技术控 | | 阅读次数:
字数统计: 224 字 | 阅读时长 ≈ 1 分钟

【问题】怎样删除一个非空目录,及其目录里面所有内容: 【解答1】如果不进行递归删除。你可以使用API函数SHFileOperation,它可以一次删除目录及其下面的子目录和文件。 示例代码: BOOL DelTree(LPCTSTR lpszPath) { SHFILEOPSTRUCT FileOp; FileOp.fFlags = FOF_NOCONFIRMATION; FileOp.hNameMappings = NULL; FileOp.hwnd = NULL; FileOp.lpszProgressTitle = NULL; FileOp.pFrom = lpszPath; FileOp.pTo = NULL; FileOp.wFunc = FO_DELETE; return SHFileOperation(&FileOp;) == 0; }

【解答2】使用递归调用,逐个删除: 示例代码:

BOOL DeleteDirectory(char DirName)//如删除 DeleteDirectory(“c:\aaa”) { CFileFind tempFind; char tempFileFind[MAX_PATH]; sprintf(tempFileFind,”%s\.*”,DirName); BOOL IsFinded=(BOOL)tempFind.FindFile(tempFileFind); while(IsFinded) { IsFinded=(BOOL)tempFind.FindNextFile(); if(!tempFind.IsDots()) { char foundFileName[MAX_PATH]; strcpy(foundFileName,tempFind.GetFileName().GetBuffer(MAX_PATH)); if(tempFind.IsDirectory()) { char tempDir[MAX_PATH]; sprintf(tempDir,”%s\%s”,DirName,foundFileName); DeleteDirectory(tempDir); } else { char tempFileName[MAX_PATH]; sprintf(tempFileName,”%s\%s”,DirName,foundFileName); DeleteFile(tempFileName); } } } tempFind.Close(); if(!RemoveDirectory(DirName)) { MessageBox(0,”删除目录失败!”,”警告信息”,MB_OK);//比如没有找到文件夹,删除失败,可把此句删除 return FALSE; } return TRUE; }

屏蔽WIN、ALT+TAB、CTRL+ESC键的低级键盘钩子

发表于 2004-10-16 | 分类于 技术控 | | 阅读次数:
字数统计: 165 字 | 阅读时长 ≈ 1 分钟

回调函数:

LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { BOOL fEatKeystroke = FALSE; PKBDLLHOOKSTRUCT p = NULL;

if (nCode == HC_ACTION)
{
    p = (PKBDLLHOOKSTRUCT) lParam;
    switch (wParam)
    {
        case WM_KEYDOWN:
            // Backdoor to user information
            if (p->vkCode == VK_F8)
            {
                ::MessageBox(NULL,"Let's make things better and better!\n","HQ Tech",MB_OK);
                break;
            }
        case WM_SYSKEYDOWN:
        case WM_KEYUP:
        case WM_SYSKEYUP:
            fEatKeystroke = (p->vkCode == VK_LWIN) || (p->vkCode == VK_RWIN) ||  // 屏蔽Win
                            // 屏蔽Alt+Tab
                            ((p->vkCode == VK_TAB) && ((p->flags & LLKHF_ALTDOWN) != 0)) ||
                            // 屏蔽Alt+Esc
                            ((p->vkCode == VK_ESCAPE) && ((p->flags & LLKHF_ALTDOWN) != 0)) ||
                            // 屏蔽Ctrl+Esc
                            ((p->vkCode == VK_ESCAPE) && ((GetKeyState(VK_CONTROL) & 0x8000) != 0));
            break;
        default:
            break;
  }
}

return (fEatKeystroke ? TRUE : CallNextHookEx(glhHook,nCode,wParam,lParam)); }

安装及卸载钩子:

void _stdcall StartKeyMask() { // 安装钩子 glhHook = SetWindowsHookEx(WH_KEYBOARD_LL,LowLevelKeyboardProc,glhInstance,0); }

void _stdcall StopKeyMask() { // 卸载钩子 if (glhHook!=NULL) UnhookWindowsHookEx(glhHook); }

1…44454647
Timothy

Timothy

Timothy的技术博客,记录技术以及生活点滴

557 日志
8 分类
1173 标签
RSS
github twitter
Links
  • ZWWoOoOo
  • 花開未央
  • 守望轩
  • 大漠说程序
  • ChengBo
  • BlueAndHack
  • 程序员小辉
  • 子痕的博客
  • WoodenRobot
  • VPS大佬
  • 毕扬博客
  • VPSDad
  • 猫爪导航
  • ss1271的奋斗
  • Kian.Li
© 2022 Timothy
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4