免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!
如果看不懂、不知道现在做的什么,那就跟着做完看效果,代码看不懂是正常的,只要会抄就行,抄着抄着就能懂了
内容参考于:易道云信息技术研究院
上一个内容:94.利用数据包实现使用技能
码云版本号:2827cd4dfd810afb514b838b6a7c1ee0a11e639e
代码下载地址,在 titan 目录下,文件名为:titan-ui的设计与架构.zip
链接:https://pan.baidu.com/s/1W-JpUcGOWbSJmMdmtMzYZg
提取码:q9n5
--来自百度网盘超级会员V4的分享
HOOK引擎,文件名为:黑兔sdk升级版.zip
链接:https://pan.baidu.com/s/1IB-Zs6hi3yU8LC2f-8hIEw
提取码:78h8
--来自百度网盘超级会员V4的分享
以 94.利用数据包实现使用技能 它的代码为基础进行修改
现在实现了好多功能,但是没有一个界面显示都有什么功能,没有界面感觉有一点黑灯瞎火的,所以接下来先把ui给实现了,再搞其它的
下方的ui界面是黑兔sdk模板里的(去百度网盘下载)
tab控件的属性,属性可不改,改它只是为了好看点的
添加一个list控件,属性可不改,改它只是为了好看点的
![]()
给控件添加一个变量
![]()
输入个变量名,直接点完成,推荐变量名与下图中一致(lstlog)方便以后跟节奏
![]()
效果图:下图的窗口在游戏中按键盘上的Home键就会弹出,实现弹出的代码在htdMfcDll.cpp里的KeyCallBack函数中实现
CUIWnd_0.cpp文件也修改了,它修改的内容不重要,所以不在这把代码贴出来了
TextManger.h文件的修改:新加 TextManger构造函数
#pragma once
#include <iostream>
#include <fstream>
/*
03E1B73B | 68 58120304 | push fxgamelogic.4031258 | 4031258:"gui"
03E1B740 | E8 7BEF0F00 | call fxgamelogic.3F1A6C0 |
03E1B745 | 83C4 04 | add esp,4 |
03E1B748 | 85C0 | test eax,eax |
03E1B74A | 0F84 10090000 | je fxgamelogic.3E1C060 |
03E1B750 | 68 4C120304 | push fxgamelogic.403124C | 403124C:"TextManager"
03E1B755 | 50 | push eax |
03E1B756 | 8D4C24 64 | lea ecx,dword ptr ss:[esp+64] |
03E1B75A | 51 | push ecx |
03E1B75B | E8 00091000 | call fxgamelogic.3F1C060 |
03E1B760 | 8D5424 68 | lea edx,dword ptr ss:[esp+68] |
03E1B764 | 52 | push edx |
03E1B765 | E8 16EF0F00 | call fxgamelogic.3F1A680 | 111111111111
03E1B76A | 83C4 10 | add esp,10 |
03E1B76D | 85C0 | test eax,eax |
03E1B76F | 894424 14 | mov dword ptr ss:[esp+14],eax | [esp+14]:CD3DDDIDX10_DrawIndexedPrimitive+113
*/
// 游戏中的中文表结构是 中文名英文id 这样的
typedef class TextTable {
public:
int next = ((int)0xFFFFFFFF);
int un = sizeof(TextTable);
wchar_t* Txt;// 中文
char TxtId[0x24];// 中文对应的英文id
}*PTextTable;
typedef class TextManger
{
public:
static unsigned ReadTextProc;
static const wchar_t NullText[2];
TextManger(const char* file);
private:
//int un[0x20];// 0x20 * 0x4 = 0x80
int un[0x1E];
union {
char* TextBuff;
unsigned* Count;
};
PTextTable textDatas = nullptr; // 中文结构
public:
PTextTable* textTable = nullptr; // 暂时无用
private:
int un1;
public:
unsigned hashCount;
unsigned* HashIndexTable = nullptr; // 哈希表
public:
bool CreateTextDat(const char* filename); // 导出语言包
bool LoadTextData(const char* _filename); // 加载 CreateTextDat函数写出的文件
const wchar_t* ReadTextById(const char* _id); // 根据id获取中文名
unsigned Hashcode(const char* id);// 哈希函数
// 获取一个未使用的中文与对应id结构,用与存放从文件中读取的中文与对应id
PTextTable GetNullText(PTextTable _table);
}*PTextManger;
TextManger.cpp文件的修改:新加 TextManger构造函数
#include "pch.h"
#include "TextManger.h"
unsigned TextManger::ReadTextProc = 0x10295FB0;
const wchar_t TextManger::NullText[2]{};
TextManger::TextManger(const char* file)
{
LoadTextData(file);
}
bool TextManger::CreateTextDat(const char* _filename)
{
if (!textTable) {
return false;
}
std::ofstream out(_filename, std::ios::out|std::ios::binary);
if (out.bad())return false;
out.write((char*)&hashCount, sizeof(hashCount));
for (int i = 0; i < hashCount; i++) {
// 游戏中的中文表结构是 中文名英文id 这样的
int lenId = strlen(textTable[i]->TxtId) + 1;
// 宽字节一个字是2字节,wcslen函数返回值是字的个数
int lenTxt = wcslen(textTable[i]->Txt) + 1;
lenTxt = lenTxt * 2;
out.write(textTable[i]->TxtId, lenId);
out.write((char*)textTable[i]->Txt, lenTxt);
}
out.close();
return true;
}
bool TextManger::LoadTextData(const char* _filename)
{
CString txt;
if (textDatas)return false;
std::ifstream file(_filename, std::ios::in | std::ios::binary|std::ios::ate);
if(file.bad()) {
return false;
}
unsigned buffSize = file.tellg();
TextBuff = new char[buffSize];
file.seekg(std::ios::beg);
file.read(TextBuff, buffSize);
// textTable = new PTextTable[Count[0]];
textDatas = new TextTable[Count[0]];
txt.Format(L"%X", &textDatas);
AfxMessageBox(txt);
char* buffStart = TextBuff + 4;
char* buffEnd = buffStart + buffSize;
int icount = 0;
while(buffStart < buffEnd){// 读取语言文件的数据到 textDatas 变量里
int lenId = strlen(buffStart) + 1;
memcpy(textDatas[icount].TxtId, buffStart, 0x24);
buffStart = buffStart + lenId;
int lenTxt = wcslen((wchar_t*)buffStart) + 1;
lenTxt = lenTxt * 2;
textDatas[icount].Txt = (wchar_t*)buffStart;
buffStart = buffStart + lenTxt;
icount++;
}
hashCount = Count[0];
HashIndexTable = new unsigned[hashCount];
memset(HashIndexTable, 0xFF, hashCount * sizeof(unsigned));
for (int i = 0; i < Count[0]; i++) {// 给 textDatas 变量添加哈希算法索引(哈希表存放 textDatas的下标)
unsigned hash = Hashcode(textDatas[i].TxtId);
hash = hash % (hashCount + 1);
if (HashIndexTable[hash] == -1) {
HashIndexTable[hash] = i;
}
else {
PTextTable nullTable = GetNullText(&textDatas[HashIndexTable[hash]]);
nullTable->next = i;
}
}
return false;
}
const wchar_t* TextManger::ReadTextById(const char* _id)
{
// unsigned callProc = 0x10295FB0;
if (textTable) {
int index;
_asm {
push ebx
mov ebx, this
push _id
call ReadTextProc
mov index, eax
pop ebx
}
if (index < 0)return NullText;
PTextTable* _table = textTable;
return (wchar_t*)_table[index]->Txt;
}
else {
unsigned hash = Hashcode(_id);
hash = hash % (hashCount + 1);
unsigned index = HashIndexTable[hash];
if (index > hashCount) {// index的值如果是-1那么它肯定比hashCount大
return NullText;
}
while (strcmp(textDatas[index].TxtId, _id)) {
index = textDatas[index].next;
if (index > hashCount)return NullText;
}
return textDatas[index].Txt;
}
}
unsigned TextManger::Hashcode(const char* id)
{
unsigned hash = 0;
for (int i = 0; id[i]; i++)
{
hash = hash * 131 + id[i];
}
return hash;
}
PTextTable TextManger::GetNullText(PTextTable _table)
{
while (_table->next != -1) {// 从链表中找出最后一个(next的值是-1的)
_table = &textDatas[_table->next];
}
return _table;
}
htdMfcDll.cpp文件的修改:修改了 InitInstance函数、KeyCallBack函数
// htdMfcDll.cpp: 定义 DLL 的初始化例程。这是一个MFC的dll
//
#include "pch.h"
#include "framework.h"
#include "htdMfcDll.h"
#include "extern_all.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// 注释掉下方宏,以入口点注入的方式注入
// #define WNDHOOK
#ifdef WNDHOOK
typedef struct htdDll
{
HHOOK keyHook;
unsigned KbdProc;
unsigned SetDll;
}*PHtdDll;
void htdSetDll(htdDll hDll);
htdDll mDll;
#endif
BEGIN_MESSAGE_MAP(ChtdMfcDllApp, CWinApp)
END_MESSAGE_MAP()
// ChtdMfcDllApp 构造
ChtdMfcDllApp::ChtdMfcDllApp()
{
}
ChtdMfcDllApp theApp;
ChtdMfcDllApp* PtheApp;
HHOOK keyHook;
LRESULT CALLBACK KeyCallBack(int nCode, WPARAM w, LPARAM l);
BOOL ChtdMfcDllApp::InitInstance()
{
CWinApp::InitInstance();
PtheApp = this;
#ifdef WNDHOOK
mDll.KbdProc = (DWORD)(KeyCallBack);
mDll.SetDll = 0;
#else
keyHook = SetWindowsHook(WH_KEYBOARD, KeyCallBack);// 代码一开始,在这注册一个键盘事件的钩子
#endif
PGameProc = new GameProc();
wndMain = new CUI();
wndMain->Create(IDD_MAIN);
Client = wndMain;
return TRUE;
}
// 钩子处理事件
LRESULT CALLBACK KeyCallBack(int nCode, WPARAM w, LPARAM l)
{
if (nCode == 0)
{
if ((l & (1 << 31)) == 0)
{
switch (w)
{
case VK_HOME:
PtheApp->wndMain->ShowWindow(TRUE);
break;
}
}
}
return CallNextHookEx(keyHook, nCode, w, l);
}
#ifdef WNDHOOK
void htdInit(htdDll* hDll)
{
hDll->KbdProc = mDll.KbdProc;
hDll->keyHook = mDll.keyHook;
hDll->SetDll = mDll.SetDll;
}
void htdSetDll(htdDll hDll)
{
mDll = hDll;
}
#else
void htdInit()
{
}
#endif
GameProc.cpp文件的修改:修改了 Init函数
#include "pch.h"
#include "GameProc.h"
#include "extern_all.h"
// typedef bool(GameWinSock::* U)(char*, unsigned);
bool _OnComprase(HOOKREFS2) {
return WinSock->OnRecv((char*)_EDI, _EBP);
}
bool _OnRecv(HOOKREFS2) {
_EAX = WinSock->RecvPoint;
return true;
}
bool _OnConnect(HOOKREFS2) {
/*
根据虚函数表做HOOK的操作
截取 ecx 获取 winsock 的值(指针)
*/
unsigned* vtable = (unsigned*)_EDX;
//WinSock = (GameWinSock *)_ECX;
/*
联合体的特点是共用一个内存
由于 GameWinSock::OnConnect 的 OnConnect函数是 GameWinSock类的成员函数
直接 vtable[0x34 / 4] = (unsigned)&GameWinSock::OnConnect; 这样写语法不通过
所以使用联合体,让语法通过
*/
union {
unsigned value;
bool(GameWinSock::* _proc)(char*, unsigned);
} vproc;
DWORD oldPro, backProc;
VirtualProtect(vtable, 0x100, PAGE_EXECUTE_READWRITE, &oldPro);
/*
vproc._proc = &GameWinSock::OnConnect; 这一句是把我们自己写的调用connect函数的地址的出来
*/
vproc._proc = &GameWinSock::OnConnect;
/*
InitClassProc函数里做的是给指针赋值的操作
InitClassProc(&GameWinSock::_OnConnect, vtable[0x34/4]);这一句的意思是把
GameWinSock类里的_OnConnect变量的值赋值成vtable[0x34/4],这个 vtable[0x34/4] 是虚表里的函数
vtable[0x34/4]是游戏中调用connect函数的函数地址,经过之前的分析调用connect是先调用了虚表中的
一个函数,然后从这个函数中调用了connect函数
*/
InitClassProc(&GameWinSock::_OnConnect, vtable[0x34/4]);
vtable[0x34 / 4] = vproc.value;
vproc._proc = &GameWinSock::OnSend;
InitClassProc(&GameWinSock::_OnSend, vtable[0x3C / 4]);
vtable[0x3C / 4] = vproc.value;
VirtualProtect(vtable, 0x100, oldPro, &backProc);
PGameProc->InitTextManger();
return true;
}
GameProc::GameProc()
{
hooker = new htd::hook::htdHook2();
Init();
InitInterface();
}
void GameProc::LoadBase()
{
LoadLibraryA("fxnet2.dll");
hlogic = LoadLibraryA("fxgamelogic.dll");
// 如果fxgamelogic.dll里用到了其它dll里面的东西,在这赋值可能会有问题
// 最好在游戏网络连接成功哪里赋值,或者进入到游戏中之后再搞
GetClassByName = (PGetClassByName)((unsigned)hlogic + 0x60A6C0);
GetClassModByName = (PGetClassModByName)((unsigned)hlogic + 0x60C060);
GetClassObject = (PGetClassObject)((unsigned)hlogic + 0x60A680);
}
void GameProc::InitTextManger()
{
CLASS_OFFSET offSet;
void* classPointer = GetClassByName("gui");
GetClassModByName(&offSet, classPointer, "TextManager");
txtManger = GetClassObject(&offSet);
}
void GameProc::Init()
{
#ifdef Anly
anly = new CAnly();
GameAnlyer = new GameAnly();
#endif
/*
这里的 WinSock 是0没有创建对象,但是还是能调用Init函数
这是因为Init函数里面没有用到this,没用到就不会报错
*/
// Client = new NetClient();
init_datadesc();
}
void GameProc::InitInterface()
{
LoadBase();
// MessageBoxA(0, "1", "1", MB_OK);
// 只会HOOK一次,一次性的HOOK
hooker->SetHook((LPVOID)0x10617046, 0x1, _OnConnect, 0, true);
/*
第一个参数是HOOK的位置
第二个参数是HOOK的位置的汇编代码的长度(用于保证执行的汇编代码完整)
第三个参数是HOOK之后当游戏执行到第一个参数的位置的时候跳转的位置
第四个参数是 _OnRecv 函数返回 false 之后跳转的位置
*/
hooker->SetHook((LPVOID)0x10618480, 0x1, _OnRecv, 0);
hooker->SetHook((LPVOID)0x1061161D, 0x3, _OnComprase, (LPVOID)0x10611602);
/*
在这里绑定游戏处理数据包函数(0x10618480函数)
然后0x10618480函数在上面一行代码已经进行了HOOK
所以在调用_OnRecv函数指针时,它就会进入我们HOOK
*/
InitClassProc(&GameWinSock::_OnRecv, 0x10618480);
}
CUI.cpp文件的修改:新加 SetListView函数、getTxtManger函数,修改了 DoDataExchange函数、OnInitDialog函数、InstallPage函数
// CUI.cpp: 实现文件
//
#include "pch.h"
#include "htdMfcDll.h"
#include "CUI.h"
#include "afxdialogex.h"
#include "extern_all.h"
// CUI 对话框
IMPLEMENT_DYNAMIC(CUI, CDialogEx)
CUI::CUI(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_MAIN, pParent)
{
}
CUI::~CUI()
{
}
void CUI::SetListView(CListCtrl* lst)
{
auto lStyle = GetWindowLongPtr(lst->m_hWnd, GWL_STYLE); // 获取窗口样式
lStyle |= LVS_REPORT; // 设置为报表模式
SetWindowLongPtr(lst->m_hWnd, GWL_STYLE, lStyle);// 给窗口设置样式
auto dStyle = lst->GetExtendedStyle(); // 获取扩展样式
dStyle |= LVS_EX_FULLROWSELECT; // 设置选择时选择一行
dStyle |= LVS_EX_GRIDLINES; // 画网格线
lst->SetExtendedStyle(dStyle); // 设置扩展样式
}
void CUI::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_TAB1, mTab);
DDX_Control(pDX, IDC_LIST1, lstlog);
}
BOOL CUI::OnInitDialog()
{
CDialogEx::OnInitDialog();
SetListView(&lstlog);
InstallPage(new CUIWnd_0(), IDD_PAGE_0, L"角色", TRUE);
InstallPage(new CUIWnd_1(), IDD_PAGE_1, L"信息");
lstlog.InsertColumn(0, L"消息", 0, 70);
lstlog.InsertColumn(1, L"内容", 0, 700);
lstlog.InsertColumn(2, L"时间", 0, 130);
//PageINJ.Init(wAppPath);
//PageRAN.SetAppPath(wAppPath);
return TRUE;
}
bool CUI::InstallPage(CDialogEx* wnd, int IDD_WND, CString&& _Name, BOOL IsShow)
{
if (CurPage >= MAX_PAGE_MAIN) return false;
Pages[CurPage] = wnd;
Pages[CurPage]->Create(IDD_WND, this);
//Pages[CurPage]->SetParent(this);
Pages[CurPage]->ShowWindow(IsShow);
CRect rect;
mTab.GetClientRect(&rect);
rect.top += 28;
rect.left += 5;
rect.bottom -= 4;
rect.right -= 5;
Pages[CurPage]->MoveWindow(&rect);
mTab.InsertItem(CurPage, _Name);
CurPage++;
return true;
}
BEGIN_MESSAGE_MAP(CUI, CDialogEx)
ON_NOTIFY(TCN_SELCHANGE, IDC_TAB1, &CUI::OnTcnSelchangeTab1)
END_MESSAGE_MAP()
// CUI 消息处理程序
void CUI::OnTcnSelchangeTab1(NMHDR* pNMHDR, LRESULT* pResult)
{
// TODO: 在此添加控件通知处理程序代码
*pResult = 0;
int n = mTab.GetCurSel();
for (int i = 0; i < CurPage; i++)
{
Pages[i]->ShowWindow(i == n);
}
}
PTextManger CUI::getTxtManger()
{
if (!txtManger)txtManger = new TextManger("F:\\语言包.txt");
return txtManger;
}
CUI.h文件的修改:修改了 CUI类,引入 NetClient头文件、TextManger头文件,新加 SetListView函数、getTxtManger函数、lstlog变量
#pragma once
#include "afxdialogex.h"
#include "NetClient.h"
#include "TextManger.h"
//增加页面头文件
#include "CUIWnd_0.h"
#include "CUIWnd_1.h"
//游戏辅助UI类
// CUI 对话框
#define MAX_PAGE_MAIN 3
// 这里用了多重继承,这回有一个问题,函数名一样的会发生冲突
// 所以在继承的时候要注意函数名
class CUI : public CDialogEx,public NetClient
{
DECLARE_DYNAMIC(CUI)
public:
CUI(CWnd* pParent = nullptr); // 标准构造函数
virtual ~CUI();
// 对话框数据
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_MAIN };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
DECLARE_MESSAGE_MAP()
CDialogEx* Pages[MAX_PAGE_MAIN];
short CurPage = 0;
public:
CTabCtrl mTab;
virtual BOOL OnInitDialog();
bool InstallPage(CDialogEx* wnd, int IDD_WND, CString&& _Name, BOOL IsShow=FALSE);
afx_msg void OnTcnSelchangeTab1(NMHDR* pNMHDR, LRESULT* pResult);
public:
void SetListView(CListCtrl* lst);
PTextManger getTxtManger();
CListCtrl lstlog;
};