如何寫一個單機遊戲修改器?

不是破解器,是作弊修改器。

比如要如何定位金錢的內存地址?這個地址每次開遊戲是固定的嗎?


謝邀,其實我已經寫過這種教程了

大概刷了63個小時了, 終於刷完戰役了。

先說說這款遊戲, 再入正題。

反正對於一個喜歡玩遊戲,但是又時間不多的程序員來說, 不作弊要被AI虐哭。

這款遊戲我大概做了3個作弊功能

1、可以修改卡桌上的任意一張卡(比如說把隨意一張怪物卡,替換成任意一張怪物卡, 魔法,陷阱卡也可以替換)

2、替換手卡(僅限於自己牌組裡有的卡, 自己牌組沒有的卡, 有個參數沒研究怎麼生成的, 所以不能構造。 不過比如說知道卡牌的完整ID, 我就可以替換整個牌組都是這張牌……)

3、可以替換牌組(還沒抽的那部分) 的任意一張卡, 也可以替換順序。 比如說我可以設置下一張卡是哪張卡!(這個功能沒用上)

不過我只做了一點點小作弊, 也沒太誇張, 要是作弊得太猛的話, 就沒意思了。

比如說我初始手牌是

修改一下

然後將

替換成

這樣就開局無限抽2張卡。 基本這樣玩就可以了。 再作弊就顯得不太厚道了。

廢話不多說, 我們入正題吧! 因為這個遊戲是純64位的, OD2.0很難用, 所以我們就用github上的開源調試器 x64dbg. 還有x64的CE。

在我們開始動手之前, 我們需要把思路給順清楚……

1、我們需要做什麼作弊功能

2、做這些功能需要什麼樣的額外功能去實現這樣的作弊功能

3、從哪方面入手

比如說,

1、我們需要做一個替換手牌的功能

2、那就需要一個獲取當前所有的手牌,然後我們需要知道所有卡的內容(ID,Name,攻擊,防禦之類)。 所以關鍵點是2個, 當前手牌的指針地址 和 所有卡的內容

3、我們肯定先做第二個,先把所有卡的內容給遍歷出來, 那麼入手可以從剩餘牌組數量入手。 就是這個

因為這個牽扯到卡組的內容, 先入手ID。 再入手內容;

………………經過幾次抽牌, CE就可以搜到

轉到x64 dbg

下一個訪問斷點。 看反彙編。

我們斷點往下跟一下。大概可以看出

這個就是ID。

下面那一堆代碼是將ID經過一段左移和And運算後, 是否大於 r10的判斷。 大概是校驗這個ID是否合法的。

跳到這個地址瞄一下

數了一下, 有9個ID。 也就表示了9張卡。 那麼哪裡有9張卡呢?

額外卡組這裡…… 當然嘗試召喚後, 就會發現少一張,我就不演示了。 因為都到了這一步了, 接下去就是召喚一張額外卡組的卡, 看下這裡會不會從9個ID變成8個ID就知道了。 那這個就自己試好了。

然後接下去這一點就是經驗之談了.

1、先翻一下附近的內存, 看下有木有相似的結構。 我們來翻一下。

你會發現這段內存附近有一個非常相似的結構。 你數一下就知道是當前卡組的ID, 不算墓地和手牌。 就是即將抽取的卡組的ID。 看好。 現在最頂部就是0x00401246, 第二張是0x6002B96。 那麼我們抽一張試試

這是我當前的手牌。

你會發現少了前2個ID。 然後我手牌是

當然了, 因為我場上

這個的關係, 我每回合都可以抽2張牌的緣故。 從這一點上我們就可以下定論。 這個地址就是當前我們的卡組的地址

2、 如果這段內存裡面沒有相似的結構怎麼辦, 那你可以在這個函數, 或者在附近的函數查看一下, 有沒有相似的結構。 就是類似這樣的結構

mov edx, dword ptr [rbp + rax*4 + 0x558]

嘗試下斷點發現沒斷下來。 那沒關係, 我們繼續往上找。

又發現了2個。 我們斷下 [rbp+rax*4+7B0]那個斷點。 會發現這個地址指向的內存如下

就孤零零隻有一個ID。

這麼巧的是, 我們墓地也只有1張卡。 那我們再改變一下墓地的卡牌數量試試

從這裡可以得出。 這個地址就是墓地卡組

然後繼續往上翻

+198這個對應的是4個卡牌ID的地址。 而我們手牌剛好又有4張……這裡就不試給你們看了, 自己嘗試一下就可以確定是 當前手牌的地址

當前卡組地址。 +378就是了。

好了。 我們從這裡就不單單可以確認了卡牌的ID。 還能確認一些卡組的地址。 當然在這個過程中, 我們還可以試試修改ID。

比如說, 我們修改一下, 當前卡組地址的下一張卡是 我們持有手牌的某個ID。 比如說

我們當前手牌

對應這4張牌。 我們就把下一張卡, 改成第一個ID, 就是0xCC0275E

為什麼我要這樣修改試試呢? 就是因為這個卡組的方式, 雖然是數組的方式保存的, 但是它操作的動作, 非常像隊列, 卡組裡抽牌就是把隊列的頭給拿出來。 所以我們修改第一個卡組的ID, 只要他是到這裡來取ID的話, 這個過程應該是可以生效的。 廢話不多說, 我們試試吧!

改成

注意了。 這裡的內存地址是按位元組順序來寫的的。 所以我們也要按照這個方式寫進去。

他的對應方式, 自己稍微一看就可以知道規律了, 哪怕你不懂內存位元組的對齊方式。

考慮到我們是抽2張卡的, 那麼索性就改2個好了。

當然了, 你還會發現貌似哪裡有問題。 因為

這卡貌似在不斷跑來跑去…… 這個時候就知道。 雖然抽下一張卡的效果貌似是對的, 實際上還是哪裡有衝突了。

我們再抽2張卡後查看一下手牌。

發現剛好有2張一毛一樣的手牌, 那麼此時我們手牌地址是內容是:

你會發現, 雖然這2個都表示「光子聖域」, 但是2個ID竟然不一樣!

但是仔細觀察你會發現, 只是前4位不一樣, 後4位還是一樣的, 帶著這個問題,我們查看一下上一個手牌「夜攝」在卡組看下有木有0x275E的

從上面的圖你會發現, 都是有2個275E, 但是前4位卻不一樣! 所以前4位應該是表示某種狀態, 後4位才是ID。 但是必須要完整的ID才能正確顯示。 當然了, 我也沒興趣研究他前4位的ID到底有什麼用 是怎樣生成的, 只要我把我需要的卡,組到卡組裡面, 我自然就知道這個卡的完整ID。

所以修改下一張卡的手段對於我們來說只有一種。 比如說我們要修改「雷擊」這張卡為下一張卡。 那麼首先我需要在當前手牌裡面知道"雷擊"的ID是0x02810F7, 也就是0x10F7

去卡組裡看一下, 哪些包含了0x10F7

我們嘗試替換一下。

變成了

抽卡試試!

這次就正常了。 不管怎麼鬧, 這些卡都是可以用的。 所以修改卡組的下一張卡, 必須是卡組裡存在的卡。 至於怎麼修改卡組不存在的卡的話。 就當是我布置給你們的作業吧~ 你們自己研究一下, 我就沒這個需求就不弄了。

接下去就是如何遍歷所有的卡。 畢竟我們每次都要看內存地址, 看當前手牌才能知道ID, 這也太麻煩了。

如果看過我搞魔獸爭霸那篇文章的話, 我們再次用那個方法, 就是搜索文本。 這個遊戲是Unicode字元串編碼, 所以我們搜一下;

第一個地址下硬體訪問斷點。然後用滑鼠選上那個卡片, 讓它被選中

需要這樣選中

成功斷下來了。

可以看到這個rax就是名字, 而這段代碼是找到名字的結束符。

可以從這裡看。 rdi=-1, 然後一直+=1

inc rdi

一直循環到字元串結束。 我們把下面內存的字元串顯示出來看看.

很明顯這個不是我們需要的名字。

圓圈內才是我們需要的名字。 為什麼呢…… 因為這個名字是會變的

比如說那個鷹身女郎三姐妹之類的卡片, 就名字帶有N長, 但是卡片的說明會說, 這張卡會當鷹身女郎使用, 所以這裡的名字是會變的。 比如說我想搜索鷹身女郎三姐妹, 結果這裡只能顯示[鷹身女郎]. 所以下一個~

自己翻上面的圖。 第二個地址跟第一個幾乎是一樣的。 所以排除。

第三個地址就是我顯示的圖。 這裡就是完全一樣的, 我們來試試

這裡的RAX就是Name, 很明顯是從上面那個CALL返回的。 而且這個CALL需要一個ECX作為參數。 那麼看下ECX是什麼

RCX=2DE5. 這也另一種程度上證明了我上述的推論。 只有後4位才是ID。

到這裡。 其實我們可以直接調用彙編代碼, 就可以返回名字的

WCHAR* GetNameById(DWORD dwId)
{
DWORD64 dwRetValue = 0;
__asm
{
PUSH dwId;
MOV RAX, 0x140875b20;
CALL RAX;
MOV dwRetValue,RAX;
}
return reinterpret_cast&(dwRetValue);
}

然而VS 並不支持內聯彙編在x64模式下。 需要自己建asm文件。 當然我們也可以完全把這個CALL自己解析出來寫一遍。

我們進去看下這個CALL

代碼翻譯一下如下:

大概就是這樣。

0000000140875469 | B8 3C 0F 00 00 | mov eax,F3C |
000000014087546E | 66 2B C8 | sub cx,ax |
0000000140875471 | B8 55 21 00 00 | mov eax,2155 |
0000000140875476 | 66 3B C8 | cmp cx,ax |

這段代碼可以看出來卡的有效範圍就是 CardID &> 0xF3C CardId &< (0xF3C+0x2155)=0x3091

當然了, 除了卡的名字, 我們還能獲取一些更有趣的。 比如說攻擊力, 防禦力, 介紹。

要記得一點: 遊戲內的結構都是驚人的相似

所以, 從這段代碼

返回名字的, 那麼我們來找一下這個函數裡面還有沒有類似的代碼。

沒有…… 那麼我們返回函數的上一層看看

Mark上這個CALL的作用。

然後你跟蹤調試一下附近的函數。 你會發現很多有趣的東西。 比如說

裡面的內容我就忽略不寫了。 可以自己去看。

如果你不想這樣一個一個函數的去翻, 看CALL的返回值的話。 那你就用我上述如何搜索名字的方法精確定位。 我就不一一演示了。

修改手牌的方法和修改卡組的方法一樣。 都是互相替換CardId。 必須是8位的卡ID。後4位是不可以的。

修改卡桌什麼的思路跟找卡組和手牌的一樣, 我就不演示了。 思路都是一樣的, 過程也是一樣的, 無非就是代碼稍微有一點點不一樣。

上一下關鍵代碼

Card.h

#ifndef __NEWYUGIOH_CHEATDLL_X64_CARD_CARD_H__
#define __NEWYUGIOH_CHEATDLL_X64_CARD_CARD_H__

#include "GameEnv.h"

class CCard
{
public:
CCard();
CCard(_In_ DWORD64 ulCardId);
~CCard() = default;

// Get Card Attribute
em_CardAttribute GetCardAttribute() CONST;
CONST std::wstring GetCardAttributeText() CONST;

// Get Power
DWORD GetPower() CONST;

// Get Defence
DWORD GetDefence() CONST;

// Get Count of Star
DWORD GetStarCount() CONST;

// Card Name
CONST std::wstring GetCardName() CONST;

// Card Detail
CONST std::wstring GetCardDetail() CONST;

DWORD64 GetCardID() CONST;
DWORD64 GetFullID() CONST;

bool IsCard() CONST;

private:
VOID SetCardName();

VOID SetCardDetail();
private:
DWORD64 m_dwCardID;
std::wstring m_wsCardName;
std::wstring m_wsCardDetail;
mutable em_CardAttribute m_emCardAttribute;
};

#endif // !__NEWYUGIOH_CHEATDLL_X64_CARD_CARD_H__

Card.cpp

#include "stdafx.h"
#include "Card.h"
#include &
#include &

CCard::CCard() : m_dwCardID(NULL), m_emCardAttribute(em_CardAttribute::em_CardAttribute_Unknow)
{

}

CCard::CCard(_In_ DWORD64 dwCardId) : m_dwCardID(dwCardId), m_emCardAttribute(em_CardAttribute::em_CardAttribute_Unknow)
{
if (GetCardAttribute() != em_CardAttribute::em_CardAttribute_Unknow)
{
SetCardName();
SetCardDetail();
}
}

DWORD64 CCard::GetCardID() CONST
{
return m_dwCardID 0xFFFF;
}

CONST std::wstring CCard::GetCardDetail() CONST
{
return m_wsCardDetail;
}

CONST std::wstring CCard::GetCardName() CONST
{
return m_wsCardName;
}

em_CardAttribute CCard::GetCardAttribute() CONST
{
if (m_emCardAttribute != em_CardAttribute::em_CardAttribute_Unknow)
return m_emCardAttribute;

if (!IsCard())
return em_CardAttribute::em_CardAttribute_Unknow;

DWORD64 ulValue = GetCardID() * 3;
ulValue *= 2;

m_emCardAttribute = static_cast&(ReadDWORD64(CARD_ATTRIBUTE_BASE + ulValue * 8 + 0x14));
return m_emCardAttribute;
}

VOID CCard::SetCardName()
{
DWORD64 r8 = ReadDWORD64(ReadDWORD64(CARD_NAME_BASE) + 0x18);
DWORD64 rdx = ReadDWORD64(ReadDWORD64(CARD_NAME_BASE) + 0x28);
if (r8 == NULL || rdx == NULL)
return;

if (!IsCard())
return;

auto ulCardID = GetCardID() - 0xF3C;
DWORD64 ulNamePtr = ReadDWORD64((ReadDWORD64(CARD_NAME_INDEX_BASE + ulCardID * 2) 0xFFFF) * 8 + rdx) + r8;
if (ulNamePtr == NULL)
return;

WCHAR wszCardName[512] = { 0 };
memcpy(wszCardName, reinterpret_cast&(ulNamePtr 0xFFFFFFFF), _countof(wszCardName) - 1);
m_wsCardName = wszCardName;
}

bool CCard::IsCard() CONST
{
return GetCardID() &<= 0x3091 GetCardID() &>= 0xF3C;
}

VOID CCard::SetCardDetail()
{
DWORD64 r8 = ReadDWORD64(ReadDWORD64(CARD_NAME_BASE) + 0x38);
DWORD64 rdx = ReadDWORD64(ReadDWORD64(CARD_NAME_BASE) + 0x28);
if (r8 == NULL || rdx == NULL)
return;

if (!IsCard())
return;

auto ulCardID = GetCardID() - 0xF3C;
DWORD64 ulDetailPtr = ReadDWORD64((ReadDWORD64(CARD_NAME_INDEX_BASE + ulCardID * 2) 0xFFFF) * 8 + 0x4 + rdx) + r8;
if (ulDetailPtr == NULL)
return;

WCHAR wszDetailText[512] = { 0 };
memcpy(wszDetailText, reinterpret_cast&(ulDetailPtr 0xFFFFFFFF), _countof(wszDetailText) - 1);
m_wsCardDetail = wszDetailText;
}

DWORD CCard::GetPower() CONST
{
if (!IsCard())
return 0;

auto RCX = GetCardID() * 3;
RCX &<&<= 0x4; auto EAX = ReadDWORD64(CARD_ATTRIBUTE_BASE + RCX + 0x4); if (EAX == 0x1FF) return 0; EAX *= 5; EAX *= 2; return static_cast&(EAX);
}

DWORD CCard::GetDefence() CONST
{
if (!IsCard())
return 0;

auto RCX = GetCardID() * 3;
RCX &<&<= 0x4; auto EAX = ReadDWORD64(CARD_ATTRIBUTE_BASE + RCX + 0x8); if (EAX == 0x1FF) return 0; EAX *= 5; EAX *= 2; return static_cast&(EAX);
}

DWORD CCard::GetStarCount() CONST
{
if (!IsCard())
return 0;

auto RCX = GetCardID() * 3;
RCX &<&<= 0x4; auto EAX = ReadDWORD64(CARD_ATTRIBUTE_BASE + RCX + 0xC) 0xFFFFFFFF; return static_cast&(ReadDWORD64(CARD_ATTRIBUTE_BASE + RCX + 0x18) 0xFFFFFFFF);
}

CONST std::wstring CCard::GetCardAttributeText() CONST
{
struct AttributeText
{
em_CardAttribute emCardAttribute;
std::wstring wsText;
};

CONST static std::vector& VecAttributeText =
{
{ em_CardAttribute::em_CardAttribute_Magic, L"魔法卡" },
{ em_CardAttribute::em_CardAttribute_Trap, L"陷阱卡" },
{ em_CardAttribute::em_CardAttribute_Land, L"地" },
{ em_CardAttribute::em_CardAttribute_Dark, L"暗" },
{ em_CardAttribute::em_CardAttribute_Light, L"光" },
{ em_CardAttribute::em_CardAttribute_Water, L"水" },
{ em_CardAttribute::em_CardAttribute_Wind, L"風" },
{ em_CardAttribute::em_CardAttribute_Fire, L"炎" },
{ em_CardAttribute::em_CardAttribute_God, L"神" },
{ em_CardAttribute::em_CardAttribute_Dark2, L"暗" },
};
CONST static std::wstring wsUnknow = L"未知";

auto itr = std::find_if(VecAttributeText.begin(), VecAttributeText.end(), [this](CONST AttributeText AttributeText_){ return AttributeText_.emCardAttribute == m_emCardAttribute; });
return itr == VecAttributeText.end() ? wsUnknow : itr-&>wsText;
}

DWORD64 CCard::GetFullID() CONST
{
return m_dwCardID;
}

CardExtend.h

#ifndef __NEWYUGIOH_CHEATDLL_X64_CARD_CARDEXTEND_H__
#define __NEWYUGIOH_CHEATDLL_X64_CARD_CARDEXTEND_H__

#include "Card.h"
#include &

class CCardExtend
{
public:
CCardExtend() = defau< ~CCardExtend() = defau< static CCardExtend GetInstance(); // Get ALL Card UINT64 GetALLCard(_Out_opt_ std::vector& VecCard) CONST;

// Get Count of CurrentCardGroup
UINT64 GetCurrentCardGroup(_Out_opt_ std::vector& VecCard) CONST;

// Set Next Card by CardID
BOOL SetNextCard(_In_ DWORD dwCardId) CONST;

// Set Card To Desk
BOOL SetCardToDesk(_In_ DWORD dwCardId) CONST;

BOOL SetIniazleCard(_In_ CONST std::vector& VecCard) CONST;
private:
BOOL ExistCardInCardGroup(_In_ DWORD dwCardId) CONST;
};

#endif // !__NEWYUGIOH_CHEATDLL_X64_CARD_CARDEXTEND_H__

#include "stdafx.h"
#include "CardExtend.h"
#include &
#include &
#include &

#define _SELF L"CardExtend.cpp"
CCardExtend CCardExtend::GetInstance()
{
static CCardExtend CCardExtend_;
return CCardExtend_;
}

UINT64 CCardExtend::GetALLCard(_Out_opt_ std::vector& VecCard) CONST
{
for (DWORD dwCardId = 0xF3C; dwCardId &< 0x3091; ++dwCardId) { CCard Card(dwCardId); if (Card.GetCardAttribute() != em_CardAttribute::em_CardAttribute_Unknow) VecCard.push_back(std::move(Card)); } return VecCard.size(); } UINT64 CCardExtend::GetCurrentCardGroup(_Out_opt_ std::vector& VecCard) CONST
{
auto ulAddr = CURRENT_CARD_GROUP_BASE - 0x438;
for (int i = 0; i &< 60; ++i, ulAddr += 4) { if (ReadDWORD64(ulAddr) == NULL) continue; CCard Card(CCharacter::ReadDWORD(ulAddr) 0xFFFFFFFF); if (Card.GetCardAttribute() != em_CardAttribute::em_CardAttribute_Unknow) VecCard.push_back(std::move(Card)); } return VecCard.size(); } BOOL CCardExtend::SetNextCard(_In_ DWORD dwCardId) CONST { /*if (!ExistCardInCardGroup(dwCardId)) { //::MessageBoxW(NULL, L"該ID不存在卡組裡面!", L"", NULL); return FALSE; }*/ auto ulAddr = CURRENT_CARD_GROUP_BASE - 0x438; auto dwValue = ReadDWORD64(ulAddr); dwValue &>&>= 0x10;
dwValue &<&<= 0x10; dwValue += dwCardId; *reinterpret_cast&(ulAddr) = dwValue;
return FALSE;
}

BOOL CCardExtend::ExistCardInCardGroup(_In_ DWORD dwCardId) CONST
{
std::vector& VecCard;
GetCurrentCardGroup(VecCard);

auto itr = std::find_if(VecCard.begin(), VecCard.end(), [dwCardId](CONST CCard Card){ return static_cast&(Card.GetFullID()) == dwCardId; });
return itr != VecCard.end();
}

BOOL CCardExtend::SetCardToDesk(_In_ DWORD dwCardId) CONST
{
auto ulAddr = CURRENT_CARD_DESK_BASE + 0x30 + 0x48;
for (DWORD64 i = 0; i &<= 20; ++i) { DWORD64 ulCardAddr = ulAddr + i * 4 * 6; LOG_CF_D(L"ulCardAddr=%lX, CardId=%lX", ulCardAddr, (ReadDWORD64(ulCardAddr) 0xFFFF)); if ((ReadDWORD64(ulCardAddr) 0xFFFF) == NULL) continue; dwCardId += static_cast&(ReadDWORD64(ulCardAddr) 0xFFFF0000);
LOG_CF_D(L"Set Value ulCardAddr=%lX, CardId=%X", ulCardAddr, dwCardId);
WriteValue(ulCardAddr, dwCardId);
return TRUE;
}
return FALSE;
}

BOOL CCardExtend::SetIniazleCard(_In_ CONST std::vector& VecCard) CONST
{
if (VecCard.size() &> 5)
{
MsgBoxLog(L"VecCard.size=%d &> 5!", VecCard.size());
return FALSE;
}

std::vector& VecGroupCard;
GetCurrentCardGroup(VecGroupCard);

DWORD64 ulGroupAddr = CURRENT_CARD_GROUP_BASE - 0x438;
for (size_t i = 0;i &< VecCard.size(); ++i) { DWORD dwCardId = VecCard.at(i); auto p = CLPublic::Vec_find_if(VecGroupCard, [dwCardId](CONST CCard Card) { return static_cast&(Card.GetCardID() 0xFFFF) == (dwCardId 0xFFFF); });
if (p == nullptr)
continue;

// swap
for (size_t j = 0; j &< 60; ++j) { DWORD dwGroupCardId = static_cast&(CCharacter::ReadDWORD(ulGroupAddr + j * 4));
if ((dwGroupCardId 0xFFFF) == dwCardId)
{
DWORD dwHandCardId = static_cast&(CCharacter::ReadDWORD(CURRENT_HAND_CARD_BASE + i * 4));
LOG_CF_D(L"OldHandID=%X, OldGroupID=%X", dwHandCardId, dwGroupCardId);

// Set Select Card Id to Hand Card
LOG_CF_D(L"SetHandCardId=%X", dwGroupCardId);
WriteValue(CURRENT_HAND_CARD_BASE + i * 4, dwGroupCardId);

// Set Old HandCard to Group
LOG_CF_D(L"SetGroupCardId=%X", dwHandCardId);
WriteValue(ulGroupAddr + j * 4, dwHandCardId);

LOG_CF_D(L"NewHandID=%X, NewGroupID=%X", static_cast&(CCharacter::ReadDWORD(CURRENT_HAND_CARD_BASE + i * 4)), static_cast&(CCharacter::ReadDWORD(ulGroupAddr + j * 4)));
break;
}
}
}

return TRUE;
}

然後來測試一下。 測試代碼

Expr.h

#ifndef __NEWYUGIOH_CHEATDLL_X64_EXPR_EXPR_H__
#define __NEWYUGIOH_CHEATDLL_X64_EXPR_EXPR_H__

#include &
#include &
#include &

class CExpr : public CExprFunBase, virtual public CClassInstance&
{
public:
CExpr();
virtual ~CExpr();
public:
virtual VOID Release();

virtual std::vector& GetVec();

private:
virtual VOID Help(_In_ CONST std::vector&);

//
VOID FindCard(_In_ CONST std::vector&);

// Set Next Card
VOID SetDeskCard(_In_ CONST std::vector&);

// Set 5 Card when Start Game
VOID SetInitialCard(_In_ CONST std::vector&);
};

#endif // !__NEWYUGIOH_CHEATDLL_X64_EXPR_EXPR_H__

#include "stdafx.h"
#include "Expr.h"
#include &
#include &
#include "CardExtend.h"

#define _SELF L"Expr.cpp"
CExpr::CExpr()
{

}

CExpr::~CExpr()
{

}

VOID CExpr::Release()
{

}

std::vector& CExpr::GetVec()
{
static std::vector& Vec =
{
{ std::bind(CExpr::Help, this, std::placeholders::_1), L"Help" },
{ std::bind(CExpr::FindCard, this, std::placeholders::_1), L"FindCard" },
{ std::bind(CExpr::SetDeskCard, this, std::placeholders::_1), L"SetDeskCard" },
{ std::bind(CExpr::SetInitialCard, this, std::placeholders::_1), L"SetInitialCard" },
};
return Vec;
}

VOID CExpr::Help(_In_ CONST std::vector&)
{
auto Vec = GetVec();
for (CONST auto itm : Vec)
LOG_C(CLog::em_Log_Type::em_Log_Type_Custome, L"FunctionName=%s", itm.wsFunName.c_str());
}

VOID CExpr::FindCard(_In_ CONST std::vector& VecParm)
{
if (VecParm.size() == 0)
{
LOG_CF_E(L"Parameter.size=0!");
return;
}

CONST std::wstring wsFindType = CCharacter::MakeTextToUpper(VecParm.at(0));
if (wsFindType == L"ALL")
{
std::vector& VecCard;
CCardExtend::GetInstance().GetALLCard(VecCard);
for (CONST auto itm : VecCard)
LOG_CF_D(L"ID=%X,Name=%s,Detail=%s", static_cast&(itm.GetCardID()), itm.GetCardName().c_str(), itm.GetCardDetail().c_str());
}
if (wsFindType == L"GROUP")
{
std::vector& VecCard;
CCardExtend::GetInstance().GetCurrentCardGroup(VecCard);
LOG_CF_D(L"GroupSize=%d", static_cast&(VecCard.size()));
for (CONST auto itm : VecCard)
LOG_CF_D(L"ID=%X,Name=%s,Detail=%s", static_cast&(itm.GetCardID()), itm.GetCardName().c_str(), itm.GetCardDetail().c_str());
}
else if (wsFindType == L"CARDNAME")
{
if (VecParm.size() == 1)
{
LOG_CF_E(L"Parameter.size=1!");
return;
}

CONST std::wstring wsFindText = VecParm.at(1);

std::vector& VecCard;
CCardExtend::GetInstance().GetALLCard(VecCard);
for (CONST auto itm : VecCard)
{
if (itm.GetCardName().find(wsFindText) != -1)
{
LOG_CF_D(L"ID=%X,Name=%s,Detail=%s", static_cast&(itm.GetCardID()), itm.GetCardName().c_str(), itm.GetCardDetail().c_str());
}
}
}
else if (wsFindType == L"CARDID")
{
if (VecParm.size() == 1)
{
LOG_CF_E(L"Parameter.size=1!");
return;
}

DWORD dwCardId = wcstol(VecParm.at(1).c_str(), nullptr, 16);

std::vector& VecCard;
CCardExtend::GetInstance().GetALLCard(VecCard);
for (CONST auto itm : VecCard)
{
if (static_cast&(itm.GetCardID()) == dwCardId)
{
LOG_CF_D(L"ID=%X,Name=%s,Detail=%s", static_cast&(itm.GetCardID()), itm.GetCardName().c_str(), itm.GetCardDetail().c_str());
}
}
}
}

VOID CExpr::SetDeskCard(_In_ CONST std::vector& VecParm)
{
if (VecParm.size() &< 2) { LOG_CF_E(L"Parameter.size&<2!"); return; } CONST std::wstring wsFindType = CCharacter::MakeTextToUpper(VecParm.at(0)); if (wsFindType == L"CARDID") { DWORD dwCardId = static_cast&(wcstol(VecParm.at(1).c_str(), NULL, 16));
CCardExtend::GetInstance().SetCardToDesk(dwCardId);
}
}

VOID CExpr::SetInitialCard(_In_ CONST std::vector& VecParm)
{
std::vector& VecCard;
for (CONST auto itm : VecParm)
VecCard.push_back(wcstol(itm.c_str(), nullptr, 16));

CCardExtend::GetInstance().SetIniazleCard(VecCard);
}

extern DWORD64 ReadDWORD64(_In_ DWORD64 dwAddr)
{
__try
{
if (!::IsBadCodePtr(FARPROC(dwAddr)))
return *reinterpret_cast&(dwAddr);
}
__except (1)
{

}
return NULL;
}

extern VOID WriteValue(_In_ DWORD64 ulAddr, _In_ DWORD dwValue)
{
auto dwCardValue = ReadDWORD64(ulAddr);

// remove low Value
dwCardValue &>&>= 0x20;
dwCardValue &<&<= 0x20; dwCardValue += dwValue;

然後來試試;

然後試一下其他功能。 比如說設置「守護神的寶扎」到卡桌上;

首先找到這個ID=2B96

但是我們需要一張牌到桌面上,再替換它。 所以先獲取一張卡「光之護封劍」=1102

先發動卡

再替換

這樣就好了……

雖然可以繼續深入修改, 讓我更為方便。 但是我覺得這個修改方法是我可以接受的程度。 繼續深入的話, 可能要花更多時間去逆向……這樣我玩遊戲的時間就會相對減少……畢竟玩遊戲+逆向的時間是有限的……還有更多工作和學習等著我……

X64的DLL注入在VideoCardGuy/X64Injector

代碼: https://github.com/VideoCardGuy/NewYuGiOh_CheatDLL_x64

這篇文章在我差不多1年前寫的。

外掛編程入門-4-遊戲王 決鬥者遺產 x64彙編


先把 @無憂無求 的 答案 放進收藏夾,老司機的文章你現在看很可能會更加迷路……

然後下載 Cheat Engine 安裝包,如果樂意還可以下載對應的中文語言包。安裝包不清潔,附帶了第三方軟體,安裝時請注意。

安裝好CE後,把CE提供的教程從頭到尾學習一遍。一旦你獨立完成了全部教程,你就掌握了基本的遊戲修改知識了。剩下的無非就是思考,學彙編,以及積累經驗。

通過CE你可以很容易地生成自己的修改器。就以它的教程為例,我用的是64位的教程(CE 6.7附帶),其進程名為 「Tutorial-x86_64.exe」。我已經寫好了這個教程的修改腳本,如圖所示:

而其內容為:

[ENABLE]
//步驟2
"Tutorial-x86_64.exe"+2B423:
db 90 90
//步驟3
"Tutorial-x86_64.exe"+2B9B3:
db 90 90
//步驟4
"Tutorial-x86_64.exe"+2C168:
jmp Tutorial-x86_64.exe+2C180
//步驟5
"Tutorial-x86_64.exe"+2C602:
jmp Tutorial-x86_64.exe+2C606
//步驟6
"Tutorial-x86_64.exe"+2CDFD:
jmp Tutorial-x86_64.exe+2CE01
//步驟7
"Tutorial-x86_64.exe"+2D2A1:
db 90 90
//步驟8
"Tutorial-x86_64.exe"+2E272:
jmp Tutorial-x86_64.exe+2E276
//步驟9
"Tutorial-x86_64.exe"+2ECBA:
jmp Tutorial-x86_64.exe+2ED08
[DISABLE]
"Tutorial-x86_64.exe"+2B423:
jne Tutorial-x86_64.exe+2B45D
"Tutorial-x86_64.exe"+2B9B3:
jne Tutorial-x86_64.exe+2B9ED
"Tutorial-x86_64.exe"+2C168:
jp Tutorial-x86_64.exe+2C1B8
"Tutorial-x86_64.exe"+2C602:
je Tutorial-x86_64.exe+2C606
"Tutorial-x86_64.exe"+2CDFD:
je Tutorial-x86_64.exe+2CE01
"Tutorial-x86_64.exe"+2D2A1:
jne Tutorial-x86_64.exe+2D2BF
"Tutorial-x86_64.exe"+2E272:
je Tutorial-x86_64.exe+2E276
"Tutorial-x86_64.exe"+2ECBA:
jp Tutorial-x86_64.exe+2ECE2

請注意,這個腳本並不是教程的答案,你做練習的時候參考它是沒用的……它沒有完成教程的任務而是以「欺騙」的方式讓教程「認為」你完成了任務,當你學完全部教程之後,通過簡單的逆向分析很容易就能做到這一點。

閑話稍敘,當你把要修改的項目添加到了列表中,依次點擊CE的 「文件」——「從表單中生成通用修改器lua腳本」,就會彈出如下界面:

然後點擊「添加熱鍵」按鈕,選擇你要添加的修改條目:

接著修改條目的熱鍵、描述以及提示音等等:

完成全部工作後點擊修改器生成器窗口中的「生成修改器」按鈕,一個修改器就製作完成了。如果有必要,你還可以點擊「用戶自行設計界面」按鈕,自行設計修改器界面,具體操作方法和VS之類的可視化IDE類似,這裡不再贅述。

這是圖片種,另存為ZIP即可,內含生成的修改器和CT表單

CE中提供了強大的修改器製作功能,包含了各種常用的控制項,你甚至可以把它理解成一個lua語言的可視化IDE,詳細的了解後,通過配合lua腳本,你可以用它實現任何你想要的效果。當然你可以用任何你喜歡的編程語言去做修改器,思路都是一樣的,就是使用windows提供的幾個原生API,你稍微查一查就OK,相關的資料有很多。


謝邀

這個就要涉及到軟體逆向工程了

用OD、IDA通過彙編推出源程序

逆向分析出遊戲程序的關鍵函數和關鍵演算法,修改之後就是一個破解版的遊戲。

單機遊戲修改器的話,首先要明白,任何程序的運行都是要過內存的。修改器就是修改了內存中的一些數據。

這需要很多的知識儲備才可以做到,不光要懂彙編,會逆向,計算機組成原理、操作系統你也要懂(主要還是windows系統),它是如何管理內存的?什麼是內核?什麼是內核編程?什麼是API介面?遊戲加殼了該怎麼脫?什麼是PE結構?總之對於底層知識了解越多越好。

不光要懂底層,還要有一定的編程能力去實現它。數據結構和演算法、 C/C++、windows程序設計,這些都不能不會。

網遊外G不光要逆向出遊戲的介面,你還要懂,TCP/IP、通信封包分析這又是一個更深的領域了。


單機遊戲一般就是改內存中的數據。


邀請我一個朋友來答一下 @祝同學給你小神秘


搜內存,或者破解數據文件。


推薦閱讀:

認真的提問:如何看待舉報炸彈人這一行為?
如何當一名優秀的遊戲客服?
任天堂為什麼必須有自己的主機?
小孩8歲,越來越沉迷遊戲,求教育方法?
《絕地求生:大逃殺》有哪些不為人知的操作?

TAG:遊戲 | 單機遊戲 | 編程 |