AddyLocations.h 解析:GMS v83 地址表
本篇定位
這是 Phase 3 的第 3 篇。
AddyLocations.h是整個登入器的「地圖」:所有需要修改的遊戲函式和資料,都在這裡以記憶體地址的形式登記。 讀完這篇,你會明白:為什麼地址是硬式編碼的,以及如何讀懂這份地址清單。
一、這個檔案的角色
MapleEzorsia-v2 修改遊戲行為的方式,是直接在記憶體裡找到目標位置並改動它。但每次操作都把地址寫死在程式碼裡,會讓程式碼變得混亂且難以維護:
// 沒有地址表時 — 四散各處的魔法數字
Memory::WriteProtected<BYTE>(0x009F5239, 0xE9); // 這是什麼?為什麼是這個地址?
Memory::WriteProtected<BYTE>(0x009F4E54, 0x90); // 和上面有什麼關係?AddyLocations.h 把所有地址集中在一處,並給每個地址一個有意義的名稱:
// 有地址表時 — 清楚的意圖
Memory::WriteProtected<BYTE>(addys::CWvsApp_SetUp, 0xE9);
Memory::WriteProtected<BYTE>(addys::Crc32_Check, 0x90);二、檔案結構:命名空間 + 常數
#pragma once
#include <cstdint>
namespace addys { // 用命名空間避免和其他地址定義衝突
// ── Windows API Hook 目標 ────────────────────────────────────────────
// 這些是遊戲呼叫的 Windows API,我們在 Import Address Table 裡攔截
// ── 遊戲初始化流程 ───────────────────────────────────────────────────
constexpr uintptr_t CWvsApp_Ctor = 0x009F4FDA; // CWvsApp 建構子
constexpr uintptr_t CWvsApp_SetUp = 0x009F5239; // CWvsApp::SetUp
constexpr uintptr_t CWvsApp_Run = 0x009F5C50; // CWvsApp::Run(主迴圈)
constexpr uintptr_t CWvsApp_InitInput = 0x009F7CE1; // CWvsApp::InitializeInput
constexpr uintptr_t CWvsApp_CallUpdate = 0x009F84D0; // CWvsApp::CallUpdate
// ── 網路連線 ─────────────────────────────────────────────────────────
constexpr uintptr_t CClientSocket_Connect1 = 0x00494CA3; // CClientSocket::Connect
constexpr uintptr_t CClientSocket_Connect2 = 0x00494D07; // CClientSocket::Connect(部分)
constexpr uintptr_t CClientSocket_Connect3 = 0x00494D2F; // CClientSocket::Connect (sockaddr_in)
// ── 資源系統 ─────────────────────────────────────────────────────────
constexpr uintptr_t IWzFileSystem_Init = 0x009F7964; // IWzFileSystem::Init
constexpr uintptr_t CWvsApp_InitResMan = 0x009F7159; // CWvsApp::InitializeResMan
constexpr uintptr_t StringPool_GetString = 0x0077A230; // StringPool::GetString
// ── 安全性 / 反作弊 ──────────────────────────────────────────────────
constexpr uintptr_t Crc32_Check = 0x009F4E54; // CRC32 完整性檢查
constexpr uintptr_t MyGetProcAddress = 0x0044E88E; // 自訂 GetProcAddress
// ── 解析度相關資料地址 ────────────────────────────────────────────────
constexpr uintptr_t GameWidth_Addr = 0x00C4FCA4; // 遊戲視窗寬度(int)
constexpr uintptr_t GameHeight_Addr = 0x00C4FCA8; // 遊戲視窗高度(int)
constexpr uintptr_t BackBuffer_Width = 0x00C4FCC0; // 後緩衝區寬度(Direct3D)
constexpr uintptr_t BackBuffer_Height = 0x00C4FCC4; // 後緩衝區高度(Direct3D)
// ── EXP 表 ───────────────────────────────────────────────────────────
constexpr uintptr_t ExpTable_Addr = 0x0078C8A6; // 自訂 EXP 曲線起始地址
} // namespace addys三、為什麼用 constexpr uintptr_t?
每個關鍵字都有理由:
| 關鍵字 | 意思 | 為什麼用它 |
|---|---|---|
constexpr | 編譯期常數 | 讓編譯器在建置時就把數字寫死,零執行期開銷;也防止地址被意外修改 |
uintptr_t | 無符號整數,大小和指標相同 | 在 32 位元程式中等同 uint32_t(4 byte),足以表示任何記憶體地址;在 64 位元自動升級為 8 byte |
為什麼不用
#define?
#define CWvsApp_SetUp 0x009F5239是純文字替換,沒有型別資訊,編譯器無法幫你做型別檢查。constexpr是真正的 C++ 常數,有型別、有作用域(命名空間限制),不會污染全域命名空間。
四、地址來自哪裡?逆向工程流程
這些地址不是憑空想出來的,是逆向工程師分析 MapleStory.exe 後得到的:
flowchart TD A[MapleStory.exe] --> B[IDA Pro / Ghidra 載入] B --> C[反組譯:機器碼 → 組合語言] C --> D[識別函式邊界] D --> E[分析函式行為<br/>如:這個函式初始化遊戲視窗] E --> F[命名函式<br/>CWvsApp::SetUp] F --> G[記錄函式起始地址<br/>0x009F5239] G --> H[寫入 AddyLocations.h]
什麼是 IDA Pro?
Industry-standard 的反組譯工具。它把 EXE 的機器碼(CPU 直接執行的 0/1)翻譯回可讀的組合語言,並自動分析函式邊界、交叉引用、字串等。費用昂貴,但有免費版 IDA Free 和開源替代品 Ghidra(NSA 開發)。
五、地址解讀指南:每個地址代表什麼?
5.1 函式起始地址
constexpr uintptr_t CWvsApp_SetUp = 0x009F5239;這個地址指向 CWvsApp::SetUp 函式在記憶體中的第一個 byte,也就是函式的機器碼開頭。
記憶體:
0x009F5239: 55 PUSH EBP ← 函式開頭(Prologue)
0x009F523A: 8B EC MOV EBP, ESP
0x009F523C: 83 EC 14 SUB ESP, 0x14
0x009F523F: ... ... ← 函式主體
安裝 Hook 時,我們把開頭幾個 byte 換成跳轉指令(JMP),讓 CPU 執行到這裡就跳到我們的程式碼。
5.2 資料地址
constexpr uintptr_t GameWidth_Addr = 0x00C4FCA4;這個地址指向一個 int 型別的資料,裡面儲存遊戲當前的視窗寬度。
記憶體:
0x00C4FCA4: 00 05 00 00 → 0x00000500 = 1280(預設解析度寬度)
修改解析度時,我們直接向這個地址寫入新值:
Memory::Write<int>(addys::GameWidth_Addr, 1920);六、版本綁定:地址和 EXE 版本強耦合
這是使用地址表最重要的注意事項
AddyLocations.h 裡的所有地址只對特定版本的 MapleStory.exe 有效。如果 EXE 換了一個版本(即使只是重新編譯,未改動任何功能),所有地址都可能失效:
MapleStory.exe v83.1(北美正式服):CWvsApp::SetUp = 0x009F5239 ← 地址表用的
MapleStory.exe v83.1(重編譯版): CWvsApp::SetUp = 0x009F6040 ← 完全不同
這就是為什麼:
- 登入器開發者必須明確指定「只支援哪個確切版本的 EXE」
- MapleEzorsia-v2 在 README 中會列出相容的客戶端 MD5 或版本號
- 如果私服更新了客戶端,登入器的地址表也必須重新逆向工程
七、如何找到一個新地址?(實務流程)
假設你想讓遊戲的聊天框字體變大,需要找到控制字體大小的地址:
1. 在 Cheat Engine 中附加到 MapleStory.exe
2. 搜尋「聊天字體大小」的值(例如 16)
3. 改變字體大小,再搜尋新值
4. 縮小到單一地址
5. 在 IDA Pro 中找到這個地址
6. 查看哪些函式讀寫這個地址(交叉引用分析)
7. 確認這是靜態地址(不是堆積上的動態地址)
8. 加入 AddyLocations.h
Cheat Engine 是什麼?
一個開源的遊戲記憶體掃描工具,可以搜尋特定值、追蹤值的變化、找到動態指標鏈。是找到遊戲記憶體地址的入門工具。找到地址後,再用 IDA Pro 分析周邊程式碼確認其意義。
八、常見問題
Q:
addys::前綴有什麼作用?
namespace addys { ... }讓所有地址常數都需要加addys::前綴才能使用,避免和其他檔案的變數名稱衝突。例如,如果另一個地方也有一個叫Crc32_Check的變數,它和addys::Crc32_Check是完全不同的東西,不會互相干擾。
Q:地址
0x009F5239,EXE 載入時真的在那個位置嗎?是的,前提是 EXE 的**映像基址(Image Base)**是
0x00400000(這是 v83 MapleStory.exe 的預設基址,可在 PE 標頭中確認)。如果有 ASLR,映像基址會隨機化,地址就需要加上基址偏移。v83 沒有 ASLR,所以直接用就行。
延伸閱讀
- 01_什麼是記憶體位址與指標 — 地址的基礎概念
- 02_Memory.cpp_讀寫記憶體工具 — 使用這些地址進行讀寫的工具函式
- 04_AutoTypes.h_MapleStory型別定義 — 配合地址使用的型別系統
- 01_什麼是函式掛鉤(Hooking) — 函式地址的最重要用途:安裝 Hook