読者です 読者をやめる 読者になる 読者になる

めいくりぷとのブログ

技術的なことやゲームのことやら・・・

JMS v358.2

コンパイラの設定が変わったみたいで、処理は変わらずだけどAobはほぼ全滅したので地道に更新していきます。

自動ログインのアドレスとオフセット

// 
#define GLOBAL_CLogin			0x02AED204

// CLogin
#define ADDRESS_CLogin__Update		0x0114D670 
#define ADDRESS_CCtrlEdit__SetText	0x00E56190 
#define ADDRESS_GetSelectedAvatarData	0x0115C900 
#define ADDRESS_SendLoginPacket		0x01152BF0 
#define OFFSET_m_pEditID		0x00000178 
#define OFFSET_m_bOfflineMode		0x0000042C 
#define OFFSET_m_nLoginStep		0x00000440 
#define OFFSET_m_nCharCount		0x00000468
#define OFFSET_m_nCharSelected		0x00000488
#define OFFSET_m_sSPW			0x000005DC

xc3 log viewer

f:id:mcrypt:20170422221858p:plain

すごい今更感あるんだが...

正直あんまり意味ないですけど。

AVA D3D hook

AVA SDK + WDDM Hook


まあWDDM hookなんてしないでも、普通にIDirect3DDevice9のfuction table書き換えでもいいのと、それのほうがBeginSceneとEndSceneに処理突っ込めれてやりやすいんだけど、書き直すのが面倒だから、また次やる気出たときに作り直します。


AVA SDKはUE3 SDK Generatorを用いて各自用意してください。
AVA SDKからクラスやら取得するのが割りと重いから(処理的に)、PFND3DDDI_PRESENTに入れるのはちょっとアレかな...?


余談ですが、オブジェクトの座標取得の基本的な流れとしては、プレイヤーを描画する時は他より比較的に頂点数が多い(?)ので、それを基に3D座標を取得して2D(スクリーン座標)に変換する~って感じなので、OpenGLの場合はglVertex3f/v関数でVertexCount(頂点数)が一定数以上の時に座標を格納して、変換するだけで簡単に取れるのですが、D3Dは自分が知っている限りでは、少し手間のかかる方法しか知りません...


DrawIndexedPrimitive関数にオブジェクトの種類によって渡される引数(PrimitiveCount?)があり、その値から判断するという方法もありますが、Loggerなどを用いてログを調べないといけないのと、ゲームによって違うというのが面倒です...
何か良い方法ありませんかね..? もう少し調べてみます。

FLOAT APIENTRY GetDistance(__in FLOAT *LocationA, __in FLOAT *LocationB)
{
	FLOAT DistanceX = LocationA[0] - LocationB[0];
	FLOAT DistanceY = LocationA[1] - LocationB[1];
	FLOAT DistanceZ = LocationA[2] - LocationB[2];

	return (FLOAT)sqrt((DistanceX * DistanceX) + (DistanceY * DistanceY) + (DistanceZ * DistanceZ));
}

HRESULT APIENTRY DrawBox(IDirect3DDevice9 *pDevice, FLOAT x, FLOAT y, FLOAT Width, FLOAT Height, D3DCOLOR Color)
{
	D3DRECT Rect = { x, y, x + Width, y + Height };

	return pDevice->Clear(1, &Rect, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, Color, 0, 0);
}

HRESULT APIENTRY DrawLine(ID3DXLine *pLine, FLOAT x, FLOAT y, FLOAT x2, FLOAT y2, D3DCOLOR Color)
{
	D3DXVECTOR2 vLine[2];
	vLine[0].x = x;
	vLine[0].y = y;
	vLine[1].x = x2;
	vLine[1].y = y2;

	if (pLine)
	{
		pLine->SetWidth(1);
		pLine->SetAntialias(FALSE);
		pLine->SetGLLines(FALSE);
		pLine->Begin();
		pLine->Draw(vLine, 2, Color);
		pLine->End();
	}

	return S_OK;
}

HRESULT APIENTRY DDICreateQuery(__in HANDLE hDevice, __in_opt D3DDDIARG_CREATEQUERY *pCreateQuery)
{
	if (pCreateQuery && pCreateQuery->QueryType == D3DDDIQUERYTYPE_OCCLUSION)
		pCreateQuery->QueryType = D3DDDIQUERYTYPE_EVENT;

	return _DDICreateQuery(hDevice, pCreateQuery);
}

HRESULT APIENTRY DDIDrawIndexedPrimitive(__in HANDLE hDevice, __in_opt const D3DDDIARG_DRAWINDEXEDPRIMITIVE *pDPI)
{
	CGraphicsContext *pGraphicsContext = CGraphicsContext::GetInstance();
	IDirect3DDevice9 *pDevice = Direct3D9::GetInstance()->GetDevice();

	// get entity 2D position
	for (int i = 0; i < MaxEnts; i++)
	{
		D3DXMATRIX mProjection, mViewProj, mWorldProj;
		D3DXVECTOR3 vScreen2D, vWorld3D;

		vWorld3D.x = Ents[i].Origin[0];
		vWorld3D.y = Ents[i].Origin[1];
		vWorld3D.z = Ents[i].Origin[2];

		if (pDevice)
		{
			pDevice->GetViewport(&Viewport);
			pDevice->GetVertexShaderConstantF(0, mProjection, 4);
			pDevice->GetVertexShaderConstantF(231, mViewProj, 4);

			D3DXMatrixIdentity(&mWorldProj);
			D3DXVec3Project(&vScreen2D, &vWorld3D, &Viewport, &mProjection, &mViewProj, &mWorldProj);

			if (vScreen2D.z < 1.0f && mProjection._44 > 1.0f)
			{
				Ents[i].ESP.x = vScreen2D.x;
				Ents[i].ESP.y = vScreen2D.y;
			}
		}
	}

	// draw esp
	for (int i = 0; i < MaxEnts; i++)
	{
		FLOAT x = Ents[i].ESP.x;
		FLOAT y = Ents[i].ESP.y;
		UCHAR r, g, b, a;

		if (Ents[i].IsVisible == TRUE)
		{
			r = 20; g = 255; b = 20; a = 200;
		}
		else
		{
			r = 255; g = 255; b = 20; a = 200;
		}

		// draw esp box on target
		if (pGraphicsContext->IsDrawEspBoxActivated())
		{
			DrawBox(pDevice, Ents[i].ESP.x, Ents[i].ESP.y, 5, 5, D3DCOLOR_RGBA(r, g, b, a));
		}

		// draw esp line on foot to target
		if (pGraphicsContext->IsDrawEspLineActivated())
		{
			ID3DXLine *pLine = NULL;

			D3DXCreateLine(pDevice, &pLine);
			if (pLine)
			{
				DrawLine(pLine, x, y, Viewport.Width / 2.0f, Viewport.Height, D3DCOLOR_RGBA(r, g, b, a));
			}
		}
	}

	// reset
	MaxEnts = 0;

	return _DDIDrawIndexedPrimitive(hDevice, pDPI);
}

HRESULT APIENTRY DDIPresent(__in HANDLE hDevice, __in_opt const D3DDDIARG_PRESENT *pPresent)
{
	CGameManager *pGameManager = CGameManager::GetInstance();
	UGameEngine* pGameEngine;
	AavaPlayerController* pAvaPC;
	AavaPawn* pAvaPawn;

	pGameEngine = pGameManager->GetEngine();

	if (pGameEngine && pGameEngine->GamePlayers.Data)
	{
		pAvaPC = (AavaPlayerController*)pGameEngine->GamePlayers.Data[0]->Actor;

		if (pAvaPC && pAvaPC->IsA(AavaPlayerController::StaticClass()))
		{
			pAvaPawn = (AavaPawn*)pAvaPC->Pawn;

			// Validate the pawn and the WorldInfo pointer required for pawn looping
			if (pAvaPawn && pAvaPawn->IsA(AavaPawn::StaticClass()) && pAvaPawn->WorldInfo)
			{
				if (MaxEnts = 0)
				{
					// Loop through the pawns
					for (APawn* pTarget = pAvaPawn->WorldInfo->PawnList; pTarget; pTarget = pTarget->NextPawn)
					{
						// Validate the current target
						if (pTarget != pAvaPawn && pTarget->IsA(AavaPawn::StaticClass()) 
							&& pGameManager->IsValidTarget(pTarget))
						{
							// player hp
							Ents[MaxEnts].Health = (INT)pTarget->Health;

							// player position
							Ents[MaxEnts].Origin[0] = pTarget->Location.X;
							Ents[MaxEnts].Origin[1] = pTarget->Location.Y;
							Ents[MaxEnts].Origin[2] = pTarget->Location.Z;

							// Next Entity
							MaxEnts++;
						}
					}
				}
			}
		}
	}

	return _DDIPresnet(hDevice, pPresent);
}

ゴミコード投下2

3,4ヶ月前に書いた雑&糞コード、現在動くかは不明

まあ糞コードだけど、一応 creditつけておく
[C++] CodeMon copy module bypass - Pastebin.com

VirtualAllocをフックして、コピーされるモジュールのサイズから決め打ちで判別して、各APIをオリジナルのAPIに飛ばします。
コピーされるタイミングで飛ばしてるから、コピーされたモジュールに対してのCRCには引っかからない。
あとは各検出に使われてるオリジナルのAPIにフックかけて...
クライアントにかけられてるCRCは前記事参考に。
基本的に何もかもがマニュアルマッピングでロードされるから、色々と手間

ゴミコード投下

3,4ヶ月前に書いたコードだからあんまり覚えてないけど、CodeMonのCRCが最初からあるんじゃなくて、CodeMonの初期化時にUrlからCRCモジュール?がダウンロードされてきて、マニュアルマッピング?かなんかでロード(MapViewOfFileExフックすればモジュール取得できたと思う)されるから、そのタイミングでフックしてクライアントのCRCだけ潰せば、CodeMon自体のCRCはどうにかしなくても大丈夫だったはず。。?
(crc bypassのコードはテンプレです。)

(DWORD)lpCrcModule + 0xFDBB このオフセットは前回から更新がなければそのままだと思うけど、まあすぐ見つかるから自分で探してください。

#include "stdafx.h"

#include "CodeMonCRC.hpp"
#include "detour.hpp"

DWORD dwMemoryStart = NULL, dwMemoryEnd = NULL, dwFakeMemory = NULL;
DWORD CMCRC_Client_1_Ret, CMCRC_Client_2_Ret;

void __declspec(naked) CMCRC_Client_1_Asm() 
{
	__asm 
	{
		cmp edi,[dwMemoryStart]
		jb Ending
		cmp edi,[dwMemoryEnd]
		ja Ending
		sub edi,[dwMemoryStart]	
		add edi,[dwFakeMemory]
Ending:
		mov eax,[edi]
		xor eax,ecx
		add edi,04
		jmp dword ptr[CMCRC_Client_1_Ret]
	}
}

void __declspec(naked) CMCRC_Client_2_Asm()
{
	__asm
	{
		cmp esi,[dwMemoryStart]
		jb Ending
		cmp esi,[dwMemoryEnd]
		ja Ending
		sub esi,[dwMemoryStart]
		add esi,[dwFakeMemory]
Ending:
		mov eax,[esi]
		imul eax, eax, 5BD1E995h
		jmp dword ptr[CMCRC_Client_2_Ret]
	}
}

BOOL WINAPI Detour_MapViewOfFileEx()
{
	typedef LPVOID(WINAPI *pfnMapViewOfFileEx)(
		_In_     HANDLE hFileMappingObject,
		_In_     DWORD  dwDesiredAccess,
		_In_     DWORD  dwFileOffsetHigh,
		_In_     DWORD  dwFileOffsetLow,
		_In_     SIZE_T dwNumberOfBytesToMap,
		_In_opt_ LPVOID lpBaseAddress
		);

	static LPVOID lpCrcModule = NULL;

	static pfnMapViewOfFileEx _MapViewOfFileEx =
		reinterpret_cast<pfnMapViewOfFileEx>(GetProcAddress(GetModuleHandle(_T("KERNELBASE.dll")), "MapViewOfFileEx"));
	pfnMapViewOfFileEx MapViewOfFileEx_Hook = [](
		_In_     HANDLE hFileMappingObject,
		_In_     DWORD  dwDesiredAccess,
		_In_     DWORD  dwFileOffsetHigh,
		_In_     DWORD  dwFileOffsetLow,
		_In_     SIZE_T dwNumberOfBytesToMap,
		_In_opt_ LPVOID lpBaseAddress) -> LPVOID
	{
		LPVOID lpvAddress = _MapViewOfFileEx(hFileMappingObject, 
			FILE_MAP_ALL_ACCESS, dwFileOffsetHigh, dwFileOffsetLow, dwNumberOfBytesToMap, lpBaseAddress);

		if (lpvAddress)
		{
			if (lpCrcModule == NULL)
			{
				lpCrcModule = lpvAddress;

                                DWORD CMCRC_Client_1 = (DWORD)lpCrcModule + 0xFDBB;
	                        DWORD CMCRC_Client_2 = (DWORD)lpCrcModule + 0xFE34;

				CMCRC_Client_1_Ret = CMCRC_Client_1 + 0x7;
				CMCRC_Client_2_Ret = CMCRC_Client_2 + 0x8;

				DetourFunction(TRUE, reinterpret_cast<LPVOID*>(&CMCRC_Client_1), CMCRC_Client_1_Asm);
				DetourFunction(TRUE, reinterpret_cast<LPVOID*>(&CMCRC_Client_2), CMCRC_Client_2_Asm);
			}
		}

		return lpvAddress;
	};

	return DetourFunction(TRUE, reinterpret_cast<LPVOID*>(&_MapViewOfFileEx), MapViewOfFileEx_Hook);
}

BOOL codemon_crc_bypass()
{
        HMODULE hModule = GetModuleHandle(NULL);

	IMAGE_NT_HEADERS32 *pNtHeaders = reinterpret_cast<IMAGE_NT_HEADERS32*>(
		reinterpret_cast<PBYTE>(hModule) + PIMAGE_DOS_HEADER(hModule)->e_lfanew);

        if (pNtHeaders)
        {
		DWORD dwSize = pNtHeaders->OptionalHeader.SizeOfImage;
		dwMemoryStart = pNtHeaders->OptionalHeader.ImageBase;
		dwMemoryEnd = pNtHeaders->OptionalHeader.ImageBase + pNtHeaders->OptionalHeader.SizeOfImage;

        	if (dwMemoryStart && dwMemoryEnd)
        	{
	        	BYTE *pbFakeMemory = (BYTE*)malloc(dwSize);
                	RtlCopyMemory(pbFakeMemory, (LPVOID)dwMemoryStart, dwSize);

	        	dwFakeMemory = (DWORD)pbFakeMemory;

	        	return Detour_MapViewOfFileEx();
        	}
	}

	return FALSE;
}

記事ネタが無いので、ゴミコード投下ということでした。

久しぶりにCSO

なぜ今更こんな糞ゲーについての記事を書くと言いますと...

いつのアップデートかは知りませんが、OpenGLでのプレイヤーの描画方法に少し変更があったそうな。

何のためにかはハッキリ分かりませんが、jacky14, delayzero? などのOpenGLをベースにしたチート対策でもあるのかな?..
処理速度云々も考えられなくもないですが、おそらく前者でしょう。

で、何がどう変更したかって言いますと、今まではプレイヤーを描画(プレイヤーに限らず)する際にglVertex3fで描画していましたが、変更後はプレイヤーの無敵時間時は今まで通りglVertex3fで描画して、通常時(ダメージが通る時)はglVertex3fvで描画されるようになりました。

この変更で、jacky14, delayzeroは試していませんが使えなくなったのではないでしょうか..(?)
(根拠はありませんが、自作してる物が使えなくなったので...)

解決策

コードにもよりますが、オブジェクトを描画するのは基本的にはglVertex3fが使われているため、オブジェクトの座標を取得するのもそれをフックすればいいだけです。

なので、glVertex3fvも同じようにフックして取得するだけです。

gluProjectで3D座標から2D座標へ変換して、足場の座標、体、頭、、と取得しますが、glVertex3fが全く使われなくなった訳ではないため、VertexCount(頂点数)は関数別で分けて処理させたほうが良いでしょう。

gyazo.com

Have fun! XD

QXmlStreamReader/Writerでxmlにウィジェットのステータスを保存する

サンプルコード

CConfigManager.hpp

#pragma once

#include <qwidget.h>
#include <map>

class CConfigManager
{
public:
	CConfigManager();
	~CConfigManager();

public:
	// 
	void WriteXmlElement(__in QXmlStreamWriter &writer);
	void ReadFromXml(__in QXmlStreamReader &reader);

	// for qwidget
	QCheckBox *CreateCheckBox(__in const QString &name, __in QWidget *parent = 0);
	QSpinBox *CreateSpinBox(__in const QString &name, __in QWidget *parent = 0);
	QLineEdit *CreateLineEdit(__in const QString &name, __in QWidget *parent = 0);
	QComboBox *CreateComboBox(__in const QString &name, __in QWidget *parent = 0);
	QPushButton *CreatePushButton(__in const QString &name, __in bool checkable, __in QWidget *parent = 0);
	QRadioButton *CreateRudioButton(__in const QString &name, __in QWidget *parent = 0);
	QSlider *CreateSlider(__in const QString &name, __in QWidget *parent = 0);

	static CConfigManager *GetInstance()
	{
		static CConfigManager instance;
		return &instance;
	}

private:
	void WriteXmlSettingsElement(__in QXmlStreamWriter &writer);
	void ReadXmlSettings(__in QXmlStreamReader &reader);

	void ReadXmlCheckBox(__in QXmlStreamReader &reader);
	void ReadXmlSpinBox(__in QXmlStreamReader &reader);
	void ReadXmlLineEdit(__in QXmlStreamReader &reader);
	void ReadXmlComboBox(__in QXmlStreamReader &reader);
	void ReadXmlPushButton(__in QXmlStreamReader &reader);
	void ReadXmlRadioButton(__in QXmlStreamReader &reader);
	void ReadXmlSlider(__in QXmlStreamReader &reader);

	// for qwidget
	std::map<QString, QCheckBox*> m_mCheckBox;
	std::map<QString, QSpinBox*> m_mSpinBox;
	std::map<QString, QLineEdit*> m_mLineEdit;
	std::map<QString, QComboBox*> m_mComboBox;
	std::map<QString, QPushButton*> m_mPushButton;
	std::map<QString, QRadioButton*> m_mRadioButton;
	std::map<QString, QSlider*> m_mSlider;
};

CConfigManager.cpp

#include "stdafx.h"

#include "CConfigManager.hpp"

CConfigManager::CConfigManager()
{
}

CConfigManager::~CConfigManager()
{
}

void CConfigManager::WriteXmlElement(__in QXmlStreamWriter &writer)
{
	// settings
	this->WriteXmlSettingsElement(writer);
}

void CConfigManager::WriteXmlSettingsElement(__in QXmlStreamWriter &writer)
{
	writer.writeStartElement("settings");

	foreach(auto it, this->m_mCheckBox)
	{
		if (it.second && it.first.size() > 1)
		{
			writer.writeStartElement("checkbox");
			writer.writeTextElement("name", it.first);
			writer.writeTextElement("state", QString::number(it.second->isChecked()));
			writer.writeEndElement();
		}
	}

	writer.writeEndElement();
}

void CConfigManager::ReadFromXml(__in QXmlStreamReader &reader)
{
	while (!reader.atEnd() && !reader.hasError())
	{
		QXmlStreamReader::TokenType token = reader.readNext();
		if (token == QXmlStreamReader::StartElement)
		{
			if (reader.name() == "settings")
			{
				// settings
				this->ReadXmlSettings(reader);
			}
		}
	}
}

void CConfigManager::ReadXmlSettings(__in QXmlStreamReader &reader)
{
	while (!reader.atEnd() && !reader.hasError())
	{
		QXmlStreamReader::TokenType token = reader.readNext();
		if (reader.tokenType() == QXmlStreamReader::EndElement && reader.name() == "settings")
		{
			// element item end
			break;
		}
		if (token == QXmlStreamReader::StartElement)
		{
			if (reader.name() == "checkbox")
				this->ReadXmlCheckBox(reader);
		}
	}
}

void CConfigManager::ReadXmlCheckBox(__in QXmlStreamReader &reader)
{
	QString name = QString();
	while (!reader.atEnd() && !reader.hasError())
	{
		QXmlStreamReader::TokenType token = reader.readNext();
		if (reader.tokenType() == QXmlStreamReader::EndElement && reader.name() == "checkbox")
		{
			// element item end
			break;
		}
		if (token == QXmlStreamReader::StartElement)
		{
			if (reader.name() == "name")
				name = reader.readElementText();
			else if (reader.name() == "state" && name.length() > 1)
				this->m_mCheckBox[name]->setChecked(reader.readElementText().toInt());
		}
	}
}

// thanks to mahorori
QCheckBox *CConfigManager::CreateCheckBox(__in const QString &name, __in QWidget *parent)
{
	QCheckBox *checkBox = new QCheckBox(name, parent);
	this->m_mCheckBox[name] = checkBox;
	return checkBox;
}
//

...

使い方

CMainWindow::CMainWindow(__in QWidget *parent) : QMainWindow(parent)
{
        CConfigManager *pConfig = CConfigManager::GetInstance();
        QCheckBox *chkTestBox = pConfig->CreateCheckBox("Test Box");

        connect(chkTestBox, &QCheckBox::toggled, [=](bool toggled)
        {
                //
        }
        ...
}

void CMainWindow::OnLoad()
{
	QSettings settings;
	QString sFileName = QFileDialog::getOpenFileName(this, QString(), settings.value("filename").toString(), tr("Xml Files (*.xml)"));
	if (sFileName.isEmpty())
	{
		QMessageBox::warning(this, QString(), tr("Unknown error occurred."), QMessageBox::Ok);
		return;
	}

	QFile file(sFileName);
	if (!file.open(QIODevice::ReadOnly))
	{
		QMessageBox::warning(this, QString(), file.errorString(), QMessageBox::Ok);
		return;
	}

	CConfigManager::GetInstance()->ReadFromXml(QXmlStreamReader(&file));

	this->m_sXmlFileName = sFileName;
}

void CMainWindow::OnSave()
{
	if (this->m_sXmlFileName.isEmpty())
	{
		// no file exists, try to save as...
		this->OnSaveAs();
		return;
	}

	QFile file(this->m_sXmlFileName);
	if (!file.open(QIODevice::WriteOnly))
	{
		QMessageBox::warning(this, QString(), file.errorString(), QMessageBox::Ok);
		return;
	}

	QXmlStreamWriter writer(&file);
	writer.setAutoFormatting(true);
	writer.setAutoFormattingIndent(4);
	writer.writeStartDocument();

	// write elements
	writer.writeStartElement("XmlTester");
	CConfigManager::GetInstance()->WriteXmlElement(writer);
	writer.writeEndElement();

	file.close();

	QMessageBox::information(this, QString(), tr("Done."), QMessageBox::Ok);
}

void CMainWindow::OnSaveAs()
{
	//
	QString sFileName = QFileDialog::getSaveFileName(this, QString(), QString(), tr("Xml Files (*.xml)"));
	if (sFileName.isEmpty())
	{
		// QMessageBox::warning(this, QString(), tr("Unknown error occurred."), QMessageBox::Ok);
		return;
	}

	this->m_sXmlFileName = sFileName;
	this->OnSave();
}