Client.cpp 整體架構導覽
本篇定位
這是 Phase 5「Client.cpp 深度解析」的第一篇,也是整個 Client 模組的地圖。
Client.cpp是 MapleEzorsia-v2 裡最核心的業務邏輯檔案,負責所有「改變遊戲視覺呈現」的操作:解析度、UI 位置、登入畫面、選角畫面。 讀完這篇,你會看到整個模組的輪廓,後三篇再逐一深入。
一、Client 模組在專案中的位置
MapleEzorsia-v2/
├── dllmain.cpp ← DLL 入口,呼叫 Client 的函式
├── dinput8.cpp ← Proxy 載入器
├── MainMain.cpp ← 初始化流程、讀設定檔
├── Client.cpp ← 【本模組】遊戲畫面修改邏輯
├── Client.h ← Client 的宣告
├── Memory.cpp ← 記憶體工具(Client 大量使用)
├── AddyLocations.h ← 地址常數(Client 的修改目標)
├── AutoTypes.h ← 型別定義(Client 使用)
├── codecaves.h ← Code Cave 工具(Client 用於複雜修改)
└── Detours/ ← Hook 函式庫
dllmain.cpp 的 MainFunc() 在安裝完所有 Hook 後,最後呼叫兩個 Client 函式:
// dllmain.cpp — MainFunc() 末尾
Client::UpdateGameStartup(); // 修改遊戲啟動流程
Client::UpdateResolution(); // 套用自訂解析度二、Client.h:模組的公開介面
#pragma once
#include <Windows.h>
#include <string>
class Client {
public:
// ── 靜態配置資料(從 config.ini 讀入)────────────────────────────────
static int m_nGameWidth; // 目標視窗寬度
static int m_nGameHeight; // 目標視窗高度
static bool m_bWindowed; // 視窗模式 vs 全螢幕
// ── 主要功能函式 ──────────────────────────────────────────────────────
static void UpdateGameStartup(); // 修改遊戲初始化啟動參數
static void UpdateResolution(); // 套用解析度設定到遊戲記憶體
// ── UI 位置修正 ────────────────────────────────────────────────────────
static void FixLoginScreen(); // 修正登入畫面元素位置
static void FixSelectCharScreen(); // 修正選角畫面元素位置
static void FixUIElements(); // 修正遊戲內 HUD 元素位置
private:
// ── 內部輔助函式 ──────────────────────────────────────────────────────
static void PatchResolutionInCode(); // 在機器碼中直接 patch 解析度常數
static void PatchViewport(); // 修正 Direct3D Viewport 設定
};所有成員都是 static:Client 是一個「工具類別(Utility Class)」,不需要建立物件,直接用類別名稱呼叫(Client::UpdateResolution())。
三、Client.cpp 的整體結構
// Client.cpp
#include "stdafx.h" // 預編譯標頭(Windows.h、常用標準庫)
#include "Client.h"
#include "Memory.h"
#include "AddyLocations.h"
#include "AutoTypes.h"
#include "codecaves.h"
#include "MainMain.h" // 讀取設定(解析度、IP 等)
// ── 靜態成員初始化 ──────────────────────────────────────────────────────────
int Client::m_nGameWidth = 1280; // 預設值,後由 MainMain 覆寫
int Client::m_nGameHeight = 720;
bool Client::m_bWindowed = true;
// ── UpdateGameStartup ──────────────────────────────────────────────────────
void Client::UpdateGameStartup() {
// 修改遊戲的啟動參數:
// 1. 強制視窗模式(關閉全螢幕)
// 2. 停用多螢幕偵測
// 3. 修正字型渲染設定
// (詳見 Phase5_02_解析度修改原理與實作)
}
// ── UpdateResolution ────────────────────────────────────────────────────────
void Client::UpdateResolution() {
// 讀取 MainMain 儲存的解析度設定
// 寫入遊戲記憶體中的解析度相關地址
// Patch 遊戲程式碼中的硬式編碼解析度值
// (詳見 Phase5_02_解析度修改原理與實作)
}
// ── FixLoginScreen ──────────────────────────────────────────────────────────
void Client::FixLoginScreen() {
// 把登入畫面的 UI 元素從 800x600 座標系換算到新解析度的座標
// (詳見 Phase5_04_登入畫面與選角畫面修正)
}
// ...(其餘函式)四、Client 修改的三個層次
graph TD A[Client 模組的修改] --> B[層次一:記憶體資料] A --> C[層次二:機器碼 Patch] A --> D[層次三:UI 元素位置] B --> B1["寫入解析度到遊戲的全域變數<br/>Memory::Write<int>(GameWidth_Addr, 1920)"] C --> C1["把遊戲程式碼裡硬式編碼的<br/>800/600 數值替換成新解析度<br/>Memory::WriteProtected<DWORD>(addr, 1920)"] D --> D1["把登入按鈕、角色選擇框<br/>從 800x600 座標換算到新座標<br/>Memory::Write<int>(btnAddr, newX)"]
五、為什麼需要三個層次的修改?
MapleStory v83 的原始解析度是 800×600。這個數字在遊戲裡出現的地方遠比你想像的多:
層次一:全域變數
遊戲有幾個存放「目前解析度」的全域變數,Direct3D 用它們來設定 Back Buffer 大小:
0x00C4FCA4: int gameWidth = 800 ← 寫新值最直接
0x00C4FCA8: int gameHeight = 600
層次二:硬式編碼在機器碼裡
遊戲有些地方直接把 800、600 這些數字寫死在指令的立即值(Immediate)裡:
0x009F52A0: 68 20 03 00 00 PUSH 0x320 ; 0x320 = 800,硬式編碼!
0x009F52A5: 68 58 02 00 00 PUSH 0x258 ; 0x258 = 600
如果不 patch 這些地方,即使全域變數改了,遊戲在某些函式裡還是會用 800×600。
層次三:UI 座標
遊戲的 UI 元素位置也是按照 800×600 計算的硬式座標(例如「登入按鈕在 (400, 450)」)。解析度放大後,這些元素如果不重新定位,就會擠在螢幕左上角。
六、執行時序:Client 函式什麼時候被呼叫?
sequenceDiagram participant DLL as dllmain.cpp participant Main as MainMain participant Client as Client participant Game as MapleStory.exe DLL->>Main: CreateInstance(MainFunc) Main->>Main: 讀取 config.ini(解析度 / IP) Main->>Main: 等待 Themida 解壓縮完成 Main->>DLL: 呼叫 MainFunc() DLL->>DLL: 安裝所有 Hook(Detours) DLL->>Client: Client::UpdateGameStartup() Client->>Game: 修改啟動參數(記憶體 patch) DLL->>Client: Client::UpdateResolution() Client->>Game: 寫入新解析度到全域變數 Client->>Game: Patch 硬式編碼的 800/600 Note over Game: 遊戲繼續初始化... Game->>Client: 觸發 Hook_CWvsApp_SetUp Client->>Client: FixLoginScreen() Client->>Game: 修正登入畫面 UI 座標
七、常見問題
Q:Client.cpp 和 dllmain.cpp 的分工是什麼?
dllmain.cpp負責「控制流程」:決定 Hook 的安裝順序、等待時機、呼叫誰。Client.cpp負責「業務邏輯」:知道要去記憶體哪些地方、改什麼值、如何計算新座標。 這是很標準的關注點分離(Separation of Concerns)。
Q:解析度修改要在 Hook 安裝前還是後?
必須在 Hook 安裝之後。因為解析度修改需要用到 Code Cave Hook(攔截遊戲的 Direct3D 初始化函式),如果先改記憶體但 Hook 還沒裝,遊戲之後的初始化函式會把值覆蓋回去。
延伸閱讀
- 02_解析度修改原理與實作 — 下一篇:UpdateResolution 的完整實作細節
- 03_UI元素位置修正 — 遊戲內 HUD 元素的座標換算
- 04_登入畫面與選角畫面修正 — 登入 / 選角畫面的特殊修正
- 03_AddyLocations.h_地址表解析 — Client 使用的所有地址常數