【前言】
夜里失眠,到这个论坛上泡,发现这个编程版块不是很正规,因为我认为的好的编程版块应该是以讨论分享及解决具体问题而存在的,而不是想咱论坛这样:
救助的问题不明确,分享就是单纯的分享源码,分享XX成品之类……
【废话】
前几天研究外挂了,就我这点破水平,脱机啊,封包挂啊之类的与我无缘了,只能做做内挂,满足一下自己的虚荣心……
这里大概的给大家分享一下做内挂编程方面的知识,高手飘过,跟我一样菜的朋友多多提出您的宝贵意见,相互学习、共勉!!!
当然了,也希望这篇文章能骗个精华之类的东东,作为我炫耀的资本~~~~
内挂,其实不难的,这里主要分享远程线程注入相关的东西,至于其它什么游戏找CALL,功能CALL调用相关的东西,可以参考一下我写的:
赚取权限第三贴,一个有意思的keygenMe的逆向分析
其实道理都是相同的,当然,如果您非常想要了解相关的东西,请参考别人大牛写的文章,这里不做详细讨论。
【基础】
我理解的远程线程注入,包括注入代码,注入DLL分为这两种(如果有不对的地方希望指正,谢谢),注入DLL就不多废话了,就是把DLL及所在目录的名字一些通过写内存的API写到目标进程中,我今天主要讨论的是向目标进程写代码,是获取目标进程的句柄的方法有很多的,比如创建进程镜像,然后遍历进程,再如通过遍历窗口标题,类名之类得到窗口句柄,然后在由窗口句柄得到进程的句柄,这里只举最常用到的通过窗口名的方法,至于遍历进程的方法有很多的,您可以百度一下就能找到……
这样我们就可以得到同一个游戏的所有窗口句柄、进程句柄,游戏角色名等信息(这里只以纵横时空这个游戏来说,由于不同的游戏,游戏数据的存储用的数据结构各不相同,所以,这个取角色名的位置不能通用!)如果你有其它的方法,欢迎跟帖子分享!
【内容一:简单的代码执行】
到这里,我们已经得到目标窗口的句柄了,然后接下来就是调用游戏中的函数,让游戏去执行它,以达到我们的通过程序控制的目的!
比如,我先要让我们的程序能够控制游戏,比如让我们程序控制的角色简单的跳一下。假设我们通过分析游戏的代码,找到,跳,这个动作的代码如下:
只要我们能让游戏顺利的执行我们的上述代码,我们就可以通过传入不同的参数,来达到拾取物品的目的!
怎么样来让游戏执行这个代码呢?
游戏的进程跟我们自己的进程是两个独立的实例,自然的,游戏不会跑到我们的进程空间内来执行我们程序的代码,否则Windows就乱套了~~~,我们要做的,就只有两步!
第一步 : 把要让游戏执行的代码写入游戏的进程空间内!
第二步 : 给游戏进程创建一个线程,让它执行上述的代码!
其实要完成上述的两步不难的,写目标进程的API函数是:WriteProcessMemory ,具体的定义如下:
这样,我们只要在函数里调用:InjectToProcess(JumpA)
就可以搞定了!
【内容一:关于参数的传递】
其实,绝大多数游戏里的函数都是带有参数的,远程线程里参数的传递其实也不难的,直接举个例子吧,希望对卡在这里的朋友有所帮助!
这里举自动的拾取物品的例子,比如我们通过对游戏的分析,找到游戏拾取物品的代码如下:
这里需要的参数有两个,一个是地上箱子的ID,一个是箱子中物品的位置,这里的远程注入的函数当然也不像上一个函数那么简单了,我改成了下面的样子:
初始化部分:
注入部分:
我们调用的方式也就变成了:
P1_STR 的结构体定义如下:
【结尾】
今天要说的东西就这么多,一些没什么技术含量的东西,希望对正需要的朋友带来帮助,也十分的感谢大家能看完我这又臭又长的破烂文章!
感觉,不管汇编也好,Delphi也好,VB,JAVA也罢,还是C顺手,极力推荐大家学C,因为很多的东西都可以自己控制,而且又简单明了,真的是简约而不简单的·~~~
好了,去就说这么多,希望各位大牛不要笑话,同我一样的菜菜继续努力,去睡觉去了~~~~
游戏的半成品外挂连接如下:
/Files/bester/ZH_Boot/ZHPlg_src.rar
夜里失眠,到这个论坛上泡,发现这个编程版块不是很正规,因为我认为的好的编程版块应该是以讨论分享及解决具体问题而存在的,而不是想咱论坛这样:
救助的问题不明确,分享就是单纯的分享源码,分享XX成品之类……
【废话】
前几天研究外挂了,就我这点破水平,脱机啊,封包挂啊之类的与我无缘了,只能做做内挂,满足一下自己的虚荣心……
这里大概的给大家分享一下做内挂编程方面的知识,高手飘过,跟我一样菜的朋友多多提出您的宝贵意见,相互学习、共勉!!!
当然了,也希望这篇文章能骗个精华之类的东东,作为我炫耀的资本~~~~
内挂,其实不难的,这里主要分享远程线程注入相关的东西,至于其它什么游戏找CALL,功能CALL调用相关的东西,可以参考一下我写的:
赚取权限第三贴,一个有意思的keygenMe的逆向分析
其实道理都是相同的,当然,如果您非常想要了解相关的东西,请参考别人大牛写的文章,这里不做详细讨论。
【基础】
我理解的远程线程注入,包括注入代码,注入DLL分为这两种(如果有不对的地方希望指正,谢谢),注入DLL就不多废话了,就是把DLL及所在目录的名字一些通过写内存的API写到目标进程中,我今天主要讨论的是向目标进程写代码,是获取目标进程的句柄的方法有很多的,比如创建进程镜像,然后遍历进程,再如通过遍历窗口标题,类名之类得到窗口句柄,然后在由窗口句柄得到进程的句柄,这里只举最常用到的通过窗口名的方法,至于遍历进程的方法有很多的,您可以百度一下就能找到……
void
CZHGameDlg::OnShuaxin()
{
// 载入游戏
DWORD dwZHThreadId;
DWORD dwZHProcessId;
HWND hZHWnd;
DWORD BASE;
char tmpName[ 65 ];
int i = 0 ;
CString sClassName = "" ;
hZHWnd = ::FindWindow( " 纵横时空 " ,NULL);
UpdateData(TRUE);
m_nChioceGM.ResetContent();
if (hZHWnd != NULL)
{
while (hZHWnd > 0 )
{
if (i >= 5 )
{
::AfxMessageBox( " 因挂机效率考虑,本挂最多可以控制5个角色!请关闭一些角色然后点刷新按钮! " );
break ;
}
int hFunc = GetClassName(hZHWnd,sClassName.GetBuffer( 0 ), 2000 );
if (hFunc != 0 && 0 <= sClassName.Find( " 纵横时空 " ))
{
if ( dwZHThreadId = ::GetWindowThreadProcessId(hZHWnd, & dwZHProcessId))
{
try
{
m_hZHProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwZHProcessId + 1 );
if (m_hZHProcess != NULL)
{
ReadProcessMemory(m_hZHProcess,(LPVOID)ZH_BASE_ADDR, & BASE, 4 ,NULL);
ReadProcessMemory(m_hZHProcess,(LPVOID)(BASE + 0xAC ),tmpName, 20 ,NULL);
ProcName[i].Name = tmpName;
ProcName[i].hZHhwnd = hZHWnd;
ProcName[i].hZHProcess = m_hZHProcess;
if ( 0 > m_nChioceGM.FindStringExact( 0 ,ProcName[i].Name))
m_nChioceGM.AddString(ProcName[i].Name);
SetDlgItemText(IDC_TISHI, " 提示: 遍历游戏角色完毕,请选择游戏角色 " );
i ++ ;
UpdateData(FALSE);
}
} catch ()
{
::AfxMessageBox( " 出现未知错误 " );
}
}
}
hZHWnd = ::GetNextWindow(hZHWnd,GW_HWNDNEXT);
}
}
else
{
SetDlgItemText(IDC_TISHI, " 提示: 没有找到游戏窗口,请确认游戏是否已经开启 " );
}
}
{
// 载入游戏
DWORD dwZHThreadId;
DWORD dwZHProcessId;
HWND hZHWnd;
DWORD BASE;
char tmpName[ 65 ];
int i = 0 ;
CString sClassName = "" ;
hZHWnd = ::FindWindow( " 纵横时空 " ,NULL);
UpdateData(TRUE);
m_nChioceGM.ResetContent();
if (hZHWnd != NULL)
{
while (hZHWnd > 0 )
{
if (i >= 5 )
{
::AfxMessageBox( " 因挂机效率考虑,本挂最多可以控制5个角色!请关闭一些角色然后点刷新按钮! " );
break ;
}
int hFunc = GetClassName(hZHWnd,sClassName.GetBuffer( 0 ), 2000 );
if (hFunc != 0 && 0 <= sClassName.Find( " 纵横时空 " ))
{
if ( dwZHThreadId = ::GetWindowThreadProcessId(hZHWnd, & dwZHProcessId))
{
try
{
m_hZHProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwZHProcessId + 1 );
if (m_hZHProcess != NULL)
{
ReadProcessMemory(m_hZHProcess,(LPVOID)ZH_BASE_ADDR, & BASE, 4 ,NULL);
ReadProcessMemory(m_hZHProcess,(LPVOID)(BASE + 0xAC ),tmpName, 20 ,NULL);
ProcName[i].Name = tmpName;
ProcName[i].hZHhwnd = hZHWnd;
ProcName[i].hZHProcess = m_hZHProcess;
if ( 0 > m_nChioceGM.FindStringExact( 0 ,ProcName[i].Name))
m_nChioceGM.AddString(ProcName[i].Name);
SetDlgItemText(IDC_TISHI, " 提示: 遍历游戏角色完毕,请选择游戏角色 " );
i ++ ;
UpdateData(FALSE);
}
} catch ()
{
::AfxMessageBox( " 出现未知错误 " );
}
}
}
hZHWnd = ::GetNextWindow(hZHWnd,GW_HWNDNEXT);
}
}
else
{
SetDlgItemText(IDC_TISHI, " 提示: 没有找到游戏窗口,请确认游戏是否已经开启 " );
}
}
这样我们就可以得到同一个游戏的所有窗口句柄、进程句柄,游戏角色名等信息(这里只以纵横时空这个游戏来说,由于不同的游戏,游戏数据的存储用的数据结构各不相同,所以,这个取角色名的位置不能通用!)如果你有其它的方法,欢迎跟帖子分享!
【内容一:简单的代码执行】
到这里,我们已经得到目标窗口的句柄了,然后接下来就是调用游戏中的函数,让游戏去执行它,以达到我们的通过程序控制的目的!
比如,我先要让我们的程序能够控制游戏,比如让我们程序控制的角色简单的跳一下。假设我们通过分析游戏的代码,找到,跳,这个动作的代码如下:
//
--------->跳<---------
//
DWORD _stdcall JumpA()
{
DWORD RealAddress,Addr;
Sell_STR SellParam;
RealAddress = 0x00409C80 ; // 真正CALL地址
Addr = 0x004336F0 ; // 加密CALL地址
_asm
{
push 0
mov eax, dword ptr [ 0x00833C5C ] // 游戏基址
add eax, 108
push eax
lea ecx,SellParam
call Addr
push eax
MOV ecx, 0x0084B908
call RealAddress
}
return 0 ;
}
DWORD _stdcall JumpA()
{
DWORD RealAddress,Addr;
Sell_STR SellParam;
RealAddress = 0x00409C80 ; // 真正CALL地址
Addr = 0x004336F0 ; // 加密CALL地址
_asm
{
push 0
mov eax, dword ptr [ 0x00833C5C ] // 游戏基址
add eax, 108
push eax
lea ecx,SellParam
call Addr
push eax
MOV ecx, 0x0084B908
call RealAddress
}
return 0 ;
}
只要我们能让游戏顺利的执行我们的上述代码,我们就可以通过传入不同的参数,来达到拾取物品的目的!
怎么样来让游戏执行这个代码呢?
游戏的进程跟我们自己的进程是两个独立的实例,自然的,游戏不会跑到我们的进程空间内来执行我们程序的代码,否则Windows就乱套了~~~,我们要做的,就只有两步!
第一步 : 把要让游戏执行的代码写入游戏的进程空间内!
第二步 : 给游戏进程创建一个线程,让它执行上述的代码!
其实要完成上述的两步不难的,写目标进程的API函数是:WriteProcessMemory ,具体的定义如下:
BOOL WriteProcessMemory(
HANDLE hProcess, // handle to process whose memory is written to
LPVOID lpBaseAddress,
// address to start writing to
LPVOID lpBuffer, // pointer to buffer to write data to
DWORD nSize, // number of bytes to write
LPDWORD lpNumberOfBytesWritten
// actual number of bytes written
);
至于在目标进程创建个线程的API及定义如下:
HANDLE hProcess, // handle to process whose memory is written to
LPVOID lpBaseAddress,
// address to start writing to
LPVOID lpBuffer, // pointer to buffer to write data to
DWORD nSize, // number of bytes to write
LPDWORD lpNumberOfBytesWritten
// actual number of bytes written
);
HANDLE CreateRemoteThread(
HANDLE hProcess, // handle to process to create thread in
LPSECURITY_ATTRIBUTES lpThreadAttributes, // pointer to security attributes
DWORD dwStackSize, // initial thread stack size, in bytes
LPTHREAD_START_ROUTINE lpStartAddress, // pointer to thread function
LPVOID lpParameter, // argument for new thread
DWORD dwCreationFlags, // creation flags
LPDWORD lpThreadId // pointer to returned thread identifier
);
好了,把我写的一个远程线程注入代码的函数贴一下:
HANDLE hProcess, // handle to process to create thread in
LPSECURITY_ATTRIBUTES lpThreadAttributes, // pointer to security attributes
DWORD dwStackSize, // initial thread stack size, in bytes
LPTHREAD_START_ROUTINE lpStartAddress, // pointer to thread function
LPVOID lpParameter, // argument for new thread
DWORD dwCreationFlags, // creation flags
LPDWORD lpThreadId // pointer to returned thread identifier
);
void
InjectToProcess(DWORD (
*
FunName)())
{
HANDLE TmpHandle;
DWORD dwThreadId;
DWORD dwProcessId;
CString sClassName = "" ;
m_hPwnd = ::FindWindow( " TForm1 " , " 游戏找CALL练习实例one " ); // 得到窗口句柄
if (m_hPwnd == NULL)
{
MessageBox( " 没有找到主程序,请先运行主程序 " );
}
int hFunc = GetClassName(m_hPwnd,sClassName.GetBuffer( 0 ), 2000 );
if (hFunc != 0 && 0 <= sClassName.Find( " TForm1 " ))
{
if ( dwThreadId = ::GetWindowThreadProcessId(m_hPwnd, & dwProcessId))
{
m_hProcess = ::OpenProcess(PROCESS_ALL_ACCESS,FALSE, dwProcessId);
if (m_hProcess != NULL)
{
// 在目标进程建立内存空间
LPVOID ThreadAdd = ::VirtualAllocEx(m_hProcess, NULL, 0x1024 , MEM_COMMIT, PAGE_EXECUTE_READWRITE);
::WriteProcessMemory(m_hProcess,ThreadAdd ,FunName, 0x1024 ,NULL);
TmpHandle = CreateRemoteThread(m_hProcess,
NULL, 0 , (LPTHREAD_START_ROUTINE)ThreadAdd, NULL,
0 , NULL); // 获得注入后过程的句柄ID
if (WaitForSingleObject(TmpHandle,INFINITE) != WAIT_OBJECT_0)
{
CString StrTmp;
StrTmp.Format( " %d " ,GetLastError());
MessageBox(StrTmp);
}
CloseHandle(TmpHandle);
CloseHandle(m_hProcess);
VirtualFreeEx(m_hProcess,ThreadAdd, 0x1024 ,MEM_RELEASE);
}
}
}
}
{
HANDLE TmpHandle;
DWORD dwThreadId;
DWORD dwProcessId;
CString sClassName = "" ;
m_hPwnd = ::FindWindow( " TForm1 " , " 游戏找CALL练习实例one " ); // 得到窗口句柄
if (m_hPwnd == NULL)
{
MessageBox( " 没有找到主程序,请先运行主程序 " );
}
int hFunc = GetClassName(m_hPwnd,sClassName.GetBuffer( 0 ), 2000 );
if (hFunc != 0 && 0 <= sClassName.Find( " TForm1 " ))
{
if ( dwThreadId = ::GetWindowThreadProcessId(m_hPwnd, & dwProcessId))
{
m_hProcess = ::OpenProcess(PROCESS_ALL_ACCESS,FALSE, dwProcessId);
if (m_hProcess != NULL)
{
// 在目标进程建立内存空间
LPVOID ThreadAdd = ::VirtualAllocEx(m_hProcess, NULL, 0x1024 , MEM_COMMIT, PAGE_EXECUTE_READWRITE);
::WriteProcessMemory(m_hProcess,ThreadAdd ,FunName, 0x1024 ,NULL);
TmpHandle = CreateRemoteThread(m_hProcess,
NULL, 0 , (LPTHREAD_START_ROUTINE)ThreadAdd, NULL,
0 , NULL); // 获得注入后过程的句柄ID
if (WaitForSingleObject(TmpHandle,INFINITE) != WAIT_OBJECT_0)
{
CString StrTmp;
StrTmp.Format( " %d " ,GetLastError());
MessageBox(StrTmp);
}
CloseHandle(TmpHandle);
CloseHandle(m_hProcess);
VirtualFreeEx(m_hProcess,ThreadAdd, 0x1024 ,MEM_RELEASE);
}
}
}
}
这样,我们只要在函数里调用:InjectToProcess(JumpA)
就可以搞定了!
【内容一:关于参数的传递】
其实,绝大多数游戏里的函数都是带有参数的,远程线程里参数的传递其实也不难的,直接举个例子吧,希望对卡在这里的朋友有所帮助!
这里举自动的拾取物品的例子,比如我们通过对游戏的分析,找到游戏拾取物品的代码如下:
//
--------->捡取物品<---------
//
_OK
void _stdcall JianWuA(PP1_STR P)
{
DWORD SendAddr = ZH_CALL_SEND;
DWORD SendEcx = ZH_CALL_ECX;
DWORD Addr = ZH_CALL_SHIQU;
DWORD WuPos;
DWORD XiangZiID;
Sell_STR SellParam;
DWORD Mubiao = ZH_SQMUBIAO_ADDR;
WuPos = P -> Param1;
XiangZiID = P -> Param2;
_asm
{
push WuPos // 在物品栏的位置
push XiangZiID // 要拾取的物品的ID
lea ecx,SellParam
Call Addr // 简单的封包加密
push eax
MOV ecx, SendEcx
call SendAddr // 发包CALL
MOV BYTE PTR [Mubiao], 0
}
}
void _stdcall JianWuA(PP1_STR P)
{
DWORD SendAddr = ZH_CALL_SEND;
DWORD SendEcx = ZH_CALL_ECX;
DWORD Addr = ZH_CALL_SHIQU;
DWORD WuPos;
DWORD XiangZiID;
Sell_STR SellParam;
DWORD Mubiao = ZH_SQMUBIAO_ADDR;
WuPos = P -> Param1;
XiangZiID = P -> Param2;
_asm
{
push WuPos // 在物品栏的位置
push XiangZiID // 要拾取的物品的ID
lea ecx,SellParam
Call Addr // 简单的封包加密
push eax
MOV ecx, SendEcx
call SendAddr // 发包CALL
MOV BYTE PTR [Mubiao], 0
}
}
这里需要的参数有两个,一个是地上箱子的ID,一个是箱子中物品的位置,这里的远程注入的函数当然也不像上一个函数那么简单了,我改成了下面的样子:
初始化部分:
void
CInjectCall::InitPorcess(HANDLE _hProcess)
{
m_hProcess = _hProcess;
if (m_hProcess != NULL)
{
// 在目标进程中为函数名创建空间
ThreadAdd = ::VirtualAllocEx(m_hProcess, NULL, 0x128 , MEM_COMMIT, PAGE_EXECUTE_READWRITE);
// 在目标进程中为函数名创建空间
ParamAddr = ::VirtualAllocEx(m_hProcess, NULL, 0x128 , MEM_COMMIT, PAGE_EXECUTE_READWRITE);
JiNengAddr = ::VirtualAllocEx(m_hProcess, NULL, 0x128 , MEM_COMMIT,PAGE_EXECUTE_READWRITE);
}
}
{
m_hProcess = _hProcess;
if (m_hProcess != NULL)
{
// 在目标进程中为函数名创建空间
ThreadAdd = ::VirtualAllocEx(m_hProcess, NULL, 0x128 , MEM_COMMIT, PAGE_EXECUTE_READWRITE);
// 在目标进程中为函数名创建空间
ParamAddr = ::VirtualAllocEx(m_hProcess, NULL, 0x128 , MEM_COMMIT, PAGE_EXECUTE_READWRITE);
JiNengAddr = ::VirtualAllocEx(m_hProcess, NULL, 0x128 , MEM_COMMIT,PAGE_EXECUTE_READWRITE);
}
}
注入部分:
void
CInjectCall::InjectFunc(LPVOID pFun, DWORD
*
Param, DWORD ParamSize)
{
HANDLE TmpHandle;
DWORD lpNumberOfBytes;
if (m_hProcess != NULL)
{
// ---- 写入函数地址
::WriteProcessMemory(m_hProcess, ThreadAdd, pFun, 0x128 , & lpNumberOfBytes);
// ---- 写入参数地址
::WriteProcessMemory(m_hProcess, ParamAddr, Param, ParamSize, & lpNumberOfBytes);
TmpHandle = ::CreateRemoteThread(m_hProcess,
NULL, 0 , (LPTHREAD_START_ROUTINE)ThreadAdd,
ParamAddr, // 有参数就写参数,无参数就为NULL
0 , & lpNumberOfBytes); // 获得注入后过程的句柄ID
::WaitForSingleObject(TmpHandle,INFINITE);
::CloseHandle(TmpHandle);
}
}
内存释放部分:
{
HANDLE TmpHandle;
DWORD lpNumberOfBytes;
if (m_hProcess != NULL)
{
// ---- 写入函数地址
::WriteProcessMemory(m_hProcess, ThreadAdd, pFun, 0x128 , & lpNumberOfBytes);
// ---- 写入参数地址
::WriteProcessMemory(m_hProcess, ParamAddr, Param, ParamSize, & lpNumberOfBytes);
TmpHandle = ::CreateRemoteThread(m_hProcess,
NULL, 0 , (LPTHREAD_START_ROUTINE)ThreadAdd,
ParamAddr, // 有参数就写参数,无参数就为NULL
0 , & lpNumberOfBytes); // 获得注入后过程的句柄ID
::WaitForSingleObject(TmpHandle,INFINITE);
::CloseHandle(TmpHandle);
}
}
void
CInjectCall::TheEndPorcess()
{
if (m_hProcess != NULL)
{
::VirtualFreeEx(m_hProcess,JiNengAddr, 0x128 ,MEM_RELEASE);
::VirtualFreeEx(m_hProcess,ThreadAdd, 0x128 ,MEM_RELEASE);
::VirtualFreeEx(m_hProcess,ParamAddr, 0x128 ,MEM_RELEASE);
}
}
{
if (m_hProcess != NULL)
{
::VirtualFreeEx(m_hProcess,JiNengAddr, 0x128 ,MEM_RELEASE);
::VirtualFreeEx(m_hProcess,ThreadAdd, 0x128 ,MEM_RELEASE);
::VirtualFreeEx(m_hProcess,ParamAddr, 0x128 ,MEM_RELEASE);
}
}
我们调用的方式也就变成了:
void
CInjectCall::JianWu(
int
WuPosInList,
int
XiangZiID)
{
P1_STR MyParam;
DWORD ParamSum;
BYTE tmpid = 0 ;
MyParam.Param1 = WuPosInList;
MyParam.Param2 = XiangZiID;
ParamSum = sizeof (MyParam);
InjectFunc(JianWuA, (DWORD * ) & MyParam,ParamSum);
}
{
P1_STR MyParam;
DWORD ParamSum;
BYTE tmpid = 0 ;
MyParam.Param1 = WuPosInList;
MyParam.Param2 = XiangZiID;
ParamSum = sizeof (MyParam);
InjectFunc(JianWuA, (DWORD * ) & MyParam,ParamSum);
}
P1_STR 的结构体定义如下:
typedef
struct
_P1_STR{
int Param1;
int Param2;
int Param3;
}P1_STR, * PP1_STR;
这样就可以了~~~~
int Param1;
int Param2;
int Param3;
}P1_STR, * PP1_STR;
【结尾】
今天要说的东西就这么多,一些没什么技术含量的东西,希望对正需要的朋友带来帮助,也十分的感谢大家能看完我这又臭又长的破烂文章!
感觉,不管汇编也好,Delphi也好,VB,JAVA也罢,还是C顺手,极力推荐大家学C,因为很多的东西都可以自己控制,而且又简单明了,真的是简约而不简单的·~~~
好了,去就说这么多,希望各位大牛不要笑话,同我一样的菜菜继续努力,去睡觉去了~~~~
游戏的半成品外挂连接如下:
/Files/bester/ZH_Boot/ZHPlg_src.rar