-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMission.cpp
153 lines (124 loc) · 6.12 KB
/
Mission.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "Patcher.h"
#include "Util.h"
#include "Tethys/API/Unit.h"
#include "Tethys/API/TethysGame.h"
#include "Tethys/API/Trigger.h"
#include "Tethys/API/Enumerators.h"
#include "Tethys/Game/MissionManager.h"
#include "Tethys/Game/GameStartInfo.h"
#include <deque>
using namespace Tethys;
using namespace TethysAPI;
using namespace Patcher::Util;
using namespace Patcher::Registers;
// =====================================================================================================================
// Adds new callback entry points for mission DLLs.
// ** TODO This should be integrated with op2ext's event handlers
bool SetMissionCallbackPatch(
bool enable)
{
static Patcher::PatchContext patcher;
bool success = true;
if (enable) {
static struct {
ibool (__cdecl* pfnOnLoad)(OnLoadArgs&&);
ibool (__cdecl* pfnOnUnload)(OnUnloadArgs&&);
void (__cdecl* pfnOnEnd)(OnEndArgs&&);
void (__cdecl* pfnOnChat)(OnChatArgs&&);
void (__cdecl* pfnOnCreateUnit)(OnCreateUnitArgs&&);
void (__cdecl* pfnOnDestroyUnit)(OnDestroyUnitArgs&&);
} callbacks = { };
// In MissionManager::LoadScript()
patcher.LowLevelHook(0x402C0B, [](Esi<MissionManager*> pThis) {
(FARPROC&)(callbacks.pfnOnLoad) = GetProcAddress(pThis->hModule_, "OnLoad");
(FARPROC&)(callbacks.pfnOnUnload) = GetProcAddress(pThis->hModule_, "OnUnload");
(FARPROC&)(callbacks.pfnOnEnd) = GetProcAddress(pThis->hModule_, "OnEnd");
(FARPROC&)(callbacks.pfnOnChat) = GetProcAddress(pThis->hModule_, "OnChat");
(FARPROC&)(callbacks.pfnOnCreateUnit) = GetProcAddress(pThis->hModule_, "OnCreateUnit");
(FARPROC&)(callbacks.pfnOnDestroyUnit) = GetProcAddress(pThis->hModule_, "OnDestroyUnit");
return ((callbacks.pfnOnLoad == nullptr) || callbacks.pfnOnLoad({ sizeof(OnLoadArgs) })) ? 0 : 0x402B65;
});
// In MissionManager::Deinit()
// ** TODO Handle error returned from pfnOnUnload(), would need to trigger OP2 restart
patcher.LowLevelHook(0x402C29, []
{ if (callbacks.pfnOnUnload != nullptr) { callbacks.pfnOnUnload({ sizeof(OnUnloadArgs) }); } callbacks = { }; });
// In GameFrame::EndMission()
patcher.LowLevelHook(0x49CB3E, [](Eax<MissionResults*> pMissionResults)
{ if (callbacks.pfnOnEnd != nullptr) { callbacks.pfnOnEnd({ sizeof(OnEndArgs), pMissionResults }); } });
// Hook call to CheckChatForCheatCode() in PlayerImpl::ProcessCommandPacket()
patcher.HookCall(0x40FD85, SetCapturedTrampoline, [F = (void(*)(char*, int))0](char* pText, int playerNum) {
if (callbacks.pfnOnChat != nullptr) {
callbacks.pfnOnChat({ sizeof(OnChatArgs), pText, sizeof(ChatCommand::message), playerNum });
}
F(pText, playerNum);
});
// In MapObjectTypeManager::CreateMapUnit()
patcher.LowLevelHook(0x446A9A, [](Ebx<MapObject*> pMo) {
if (callbacks.pfnOnCreateUnit != nullptr) {
callbacks.pfnOnCreateUnit({ sizeof(OnCreateUnitArgs), Unit(pMo) });
}
});
// In MapObject::DoDeath()
patcher.LowLevelHook(0x43AA06, [](Esi<MapObject*> pThis) {
if (callbacks.pfnOnDestroyUnit != nullptr) {
callbacks.pfnOnDestroyUnit({ sizeof(OnDestroyUnitArgs), Unit(pThis) });
}
});
// Pass extended params to triggers, i.e. function signature is now of the form void __cdecl(OnTriggerArgs*).
// Because trigger callbacks always used cdecl, this is ABI-compatible with existing callbacks (caller cleanup).
// In MissionManager::ProcessScStubs()
patcher.LowLevelHook(0x403251, [] {
using PfnTrigger = void(__cdecl*)(OnTriggerArgs&&);
std::deque<std::pair<TriggerImpl*, PfnTrigger>> firedTriggers;
for (TriggerImpl* pTrigger = TriggerImpl::GetTriggerList(); pTrigger != nullptr; pTrigger = pTrigger->pNext_) {
if (pTrigger->isEnabled_ && pTrigger->HasFired()) {
if (auto* pfn = pTrigger->GetCallbackFunction(); pfn != nullptr) {
firedTriggers.emplace_back(pTrigger, reinterpret_cast<PfnTrigger>(pfn));
}
}
}
// Defer calling user callbacks in case they have side effects that would cause other triggers to fire.
for (auto [pTrigger, pfnOnTrigger] : firedTriggers) {
pfnOnTrigger({ sizeof(OnTriggerArgs), Trigger(pTrigger) });
}
return 0x40329C;
});
// Hook FuncReference::SetData() to allow trigger callback function of nullptr.
patcher.Hook(0x4757D0, SetCapturedTrampoline, ThiscallFunctor(
[F = (ibool(__thiscall*)(ScBase*, char*, ibool))0](ScBase* pThis, char* pFuncName, ibool useLevelDLL) -> ibool {
return ((pFuncName == nullptr) || F(pThis, pFuncName, useLevelDLL));
}));
success = (patcher.GetStatus() == PatcherStatus::Ok);
}
if ((enable == false) || (success == false)) {
success &= (patcher.RevertAll() == PatcherStatus::Ok);
}
return success;
}
// =====================================================================================================================
// When running multiplayer missions from the debug "run script" dialog, don't automatically trigger victory when the
// mission loads.
bool SetMissionDebugNoInstantWin(
bool enable)
{
static Patcher::PatchContext patcher;
bool success = true;
static const auto& flags = g_gameImpl.gameStartInfo_.startupFlags;
if (enable) {
// In MissionManager::ProcessScStubs()
patcher.LowLevelHook(0x4033AA, [] { return flags.isMultiplayer ? 0 : 0x4033B9; });
// In LastOneStandingTriggerImpl::HasFired()
patcher.LowLevelHook(0x492A61, [](Ebp<ibool*> pIsHuman) {
Unit u;
auto*const p = PtrDec<PlayerImpl*>(pIsHuman, offsetof(PlayerImpl, isHuman_));
return flags.isMultiplayer ? 0 : PlayerBuildingEnum(p->playerNum_, MapID::CommandCenter).GetNext(u) ? 0x492A72: 0;
});
success = (patcher.GetStatus() == PatcherStatus::Ok);
}
if ((enable == false) || (success == false)) {
success &= (patcher.RevertAll() == PatcherStatus::Ok);
}
return success;
}