-
Notifications
You must be signed in to change notification settings - Fork 452
Expand file tree
/
Copy pathmain.cpp
More file actions
469 lines (398 loc) · 19 KB
/
main.cpp
File metadata and controls
469 lines (398 loc) · 19 KB
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
#include <Geode/DefaultInclude.hpp>
#include "../load.hpp"
#include <Windows.h>
#include "loader/LoaderImpl.hpp"
#include "loader/console.hpp"
using namespace geode::prelude;
void updateGeode() {
const auto workingDir = dirs::getGameDir();
const auto geodeDir = dirs::getGeodeDir();
const auto updatesDir = geodeDir / "update";
bool bootstrapperExists = std::filesystem::exists(workingDir / "GeodeBootstrapper.dll");
bool updatesDirExists = std::filesystem::exists(geodeDir) && std::filesystem::exists(updatesDir);
if (!bootstrapperExists && !updatesDirExists)
return;
// update updater
if (std::filesystem::exists(updatesDir) &&
std::filesystem::exists(updatesDir / "GeodeUpdater.exe"))
std::filesystem::rename(updatesDir / "GeodeUpdater.exe", workingDir / "GeodeUpdater.exe");
utils::game::restart(true);
}
void patchDelayLoad() {
#ifdef GEODE_IS_WINDOWS64
// clang has a stupid issue where its tailmerge does not allocate
// the correct space for xmm registers, causing them to be overwritten by the delayLoadHelper2 function
// See: https://github.com/llvm/llvm-project/issues/51941
// based off addresser.cpp followThunkFunction
// get some function thats not virtual
auto address = geode::cast::reference_cast<uintptr_t>(&cocos2d::CCNode::convertToNodeSpace);
static constexpr auto checkByteSequence = [](uintptr_t address, const std::initializer_list<uint8_t>& bytes) {
for (auto byte : bytes) {
if (*reinterpret_cast<uint8_t*>(address++) != byte) {
return false;
}
}
return true;
};
// check if first instruction is a jmp qword ptr [rip + ...], i.e. if the func is a thunk
// FF 25 xxxxxxxx
if (address && checkByteSequence(address, {0xFF, 0x25})) {
const auto offset = *reinterpret_cast<int32_t*>(address + 2);
// rip is at address + 6 (size of the instruction)
address = *reinterpret_cast<uintptr_t*>(address + 6 + offset);
}
// if it starts with lea eax,..., it's a delay loaded func
// 48 8D 05 xxxxxxxx
if (address && checkByteSequence(address, {0x48, 0x8d, 0x05})) {
// follow the jmp to the tailMerge func and grab the ImgDelayDescr pointer from there
// do it this way instead of grabbing it from the NT header ourselves because
// we don't know the dll name
auto leaAddress = address + 7 + *reinterpret_cast<int32_t*>(address + 3);
auto jmpOffset = *reinterpret_cast<int32_t*>(address + 7 + 1);
auto tailMergeAddr = address + 7 + jmpOffset + 5;
// see https://github.com/llvm/llvm-project/blob/main/lld/COFF/DLL.cpp#L207
if (checkByteSequence(tailMergeAddr, {0x51, 0x52, 0x41, 0x50, 0x41, 0x51, 0x48, 0x83, 0xEC, 0x48})) {
// ok we are probably in the broken lld-link tailMerge, time to patch it
auto allocated = reinterpret_cast<uintptr_t>(VirtualAlloc(nullptr, 0x100, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READ));
if (!allocated) {
log::warn("Failed to allocate memory for xmm0 fix");
static constexpr uint8_t patch1[] = {
0x48, 0x83, 0xEC, 0x68, // sub rsp, 68h
0x66, 0x0F, 0x7F, 0x04, 0x24, // movdqa xmmword ptr [rsp], xmm0
0x66, 0x0F, 0x7F, 0x4C, 0x24, 0x30, // movdqa xmmword ptr [rsp+30h], xmm1
0x66, 0x0F, 0x7F, 0x54, 0x24, 0x40, // movdqa xmmword ptr [rsp+40h], xmm2
0x66, 0x0F, 0x7F, 0x5C, 0x24, 0x50, // movdqa xmmword ptr [rsp+50h], xmm3
};
(void) tulip::hook::writeMemory(reinterpret_cast<void*>(tailMergeAddr + 6), patch1, sizeof(patch1));
static constexpr uint8_t patch2[] = {
0x66, 0x0F, 0x6F, 0x04, 0x24, // movdqa xmm0, xmmword ptr [rsp]
0x66, 0x0F, 0x6F, 0x4C, 0x24, 0x30, // movdqa xmm1, xmmword ptr [rsp+30h]
0x66, 0x0F, 0x6F, 0x54, 0x24, 0x40, // movdqa xmm2, xmmword ptr [rsp+40h]
0x66, 0x0F, 0x6F, 0x5C, 0x24, 0x50, // movdqa xmm3, xmmword ptr [rsp+50h]
0x48, 0x83, 0xC4, 0x68, // add rsp, 68h
};
(void) tulip::hook::writeMemory(reinterpret_cast<void*>(tailMergeAddr + 48), patch2, sizeof(patch2));
}
else {
std::array<uint8_t, 27> patch1 = {
0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp qword ptr [rip + ...]
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90
};
uintptr_t jmpAddr = allocated;
std::memcpy(patch1.data() + 6, &jmpAddr, sizeof(jmpAddr));
(void) tulip::hook::writeMemory(reinterpret_cast<void*>(tailMergeAddr + 6), patch1.data(), sizeof(patch1));
std::array<uint8_t, 48> patch2 = {
0x48, 0x83, 0xEC, 0x68, // sub rsp, 68h
0x66, 0x0F, 0x7F, 0x44, 0x24, 0x20, // movdqa xmmword ptr [rsp+20h], xmm0
0x66, 0x0F, 0x7F, 0x4C, 0x24, 0x30, // movdqa xmmword ptr [rsp+30h], xmm1
0x66, 0x0F, 0x7F, 0x54, 0x24, 0x40, // movdqa xmmword ptr [rsp+40h], xmm2
0x66, 0x0F, 0x7F, 0x5C, 0x24, 0x50, // movdqa xmmword ptr [rsp+50h], xmm3
0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp qword ptr [rip + ...]
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x90, 0x90, 0x90, 0x90, 0x90, 0x90
};
jmpAddr = tailMergeAddr + 6 + 27;
std::memcpy(patch2.data() + 34, &jmpAddr, sizeof(jmpAddr));
(void) tulip::hook::writeMemory(reinterpret_cast<void*>(allocated), patch2.data(), sizeof(patch2));
jmpAddr = allocated + 42;
std::memcpy(patch1.data() + 6, &jmpAddr, sizeof(jmpAddr));
(void) tulip::hook::writeMemory(reinterpret_cast<void*>(tailMergeAddr + 48), patch1.data(), sizeof(patch1));
std::array<uint8_t, 48> patch3 = {
0x66, 0x0F, 0x6F, 0x44, 0x24, 0x20, // movdqa xmm0, xmmword ptr [rsp+20h]
0x66, 0x0F, 0x6F, 0x4C, 0x24, 0x30, // movdqa xmm1, xmmword ptr [rsp+30h]
0x66, 0x0F, 0x6F, 0x54, 0x24, 0x40, // movdqa xmm2, xmmword ptr [rsp+40h]
0x66, 0x0F, 0x6F, 0x5C, 0x24, 0x50, // movdqa xmm3, xmmword ptr [rsp+50h]
0x48, 0x83, 0xC4, 0x68, // add rsp, 68h
0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp qword ptr [rip + ...]
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x90, 0x90, 0x90, 0x90, 0x90, 0x90
};
jmpAddr = tailMergeAddr + 48 + 27;
std::memcpy(patch3.data() + 34, &jmpAddr, sizeof(jmpAddr));
(void) tulip::hook::writeMemory(reinterpret_cast<void*>(allocated + 42), patch3.data(), sizeof(patch3));
}
}
}
#endif
}
void* mainTrampolineAddr;
#include "gdTimestampMap.hpp"
unsigned int gdTimestamp = 0;
// In case the game is launched from a different directory through command line
// this function will set the current working directory to the game's directory
// to avoid the game crashing due to not being able to find the resources
void fixCurrentWorkingDirectory() {
std::array<WCHAR, MAX_PATH> cwd;
auto size = GetModuleFileNameW(nullptr, cwd.data(), cwd.size());
if (size == cwd.size()) return;
SetCurrentDirectoryW(std::filesystem::path(cwd.data()).parent_path().c_str());
}
bool cleanModeCheck() {
if (
(GetAsyncKeyState(VK_MENU) & (1 << 15)) &&
(GetAsyncKeyState(VK_SHIFT) & (1 << 15))
) {
auto choice = MessageBoxW(
NULL,
L"(This has been triggered because you were holding ALT+SHIFT)\n"
L"Do you want to open Geometry Dash without Geode?",
L"Attention",
MB_YESNO | MB_ICONINFORMATION
);
return choice == IDYES;
}
return false;
}
int WINAPI gdMainHook(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) {
// MessageBoxW(NULL, L"Hello from gdMainHook!", L"Hi", 0);
updateGeode();
fixCurrentWorkingDirectory();
if (!cleanModeCheck()) {
if (versionToTimestamp(GEODE_STR(GEODE_GD_VERSION)) > gdTimestamp) {
console::messageBox(
"Unable to Load Geode!",
fmt::format(
"Geometry Dash is outdated!\n"
"Geode requires GD {} but you have {}.\n"
"Please, update Geometry Dash to {}.",
GEODE_STR(GEODE_GD_VERSION),
LoaderImpl::get()->getGameVersion(),
GEODE_STR(GEODE_GD_VERSION)
)
);
// TODO: should geode FreeLibrary itself here?
} else {
patchDelayLoad();
int exitCode = geodeEntry(hInstance);
if (exitCode != 0)
return exitCode;
}
}
return reinterpret_cast<decltype(&wWinMain)>(mainTrampolineAddr)(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
// incase we're desperate again
#if 0
#define MSG_BOX_DEBUG(...) MessageBoxW(NULL, utils::string::utf8ToWide(std::format(GEODE_STR(__LINE__) " - " __VA_ARGS__)).c_str(), L"Geode", 0)
#else
#define MSG_BOX_DEBUG(...)
#endif
std::string loadGeode() {
auto process = GetCurrentProcess();
auto dosHeader = reinterpret_cast<IMAGE_DOS_HEADER*>(geode::base::get());
auto ntHeader = reinterpret_cast<PIMAGE_NT_HEADERS>(geode::base::get() + dosHeader->e_lfanew);
gdTimestamp = ntHeader->FileHeader.TimeDateStamp;
constexpr size_t trampolineSize = GEODE_WINDOWS64(32) GEODE_WINDOWS32(12);
mainTrampolineAddr = VirtualAlloc(
nullptr, trampolineSize,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE
);
const uintptr_t entryAddr = geode::base::get() + ntHeader->OptionalHeader.AddressOfEntryPoint;
bool panicMode = false;
#ifdef GEODE_IS_WINDOWS64
// somehow, there are like 2 people where the code at the entry is just nonsense
// i do not understand it either
if (*reinterpret_cast<uint32_t*>(entryAddr) != 0x28ec8348) {
MSG_BOX_DEBUG("Going into emergency mode");
panicMode = true;
}
#endif
MSG_BOX_DEBUG("Entry address: {:x}, {:x}", entryAddr, entryAddr - geode::base::get());
// function that calls main
// 32 bit:
// e8 xx xx xx xx call (security)
// e9 xx xx xx xx jmp
// 64 bit:
// 48 83 ec 28 sub rsp, 0x28
// e8 xx xx xx xx call (security)
// 48 83 c4 28 add rsp, 0x28
// e9 xx xx xx xx jmp
constexpr ptrdiff_t mainCallJmpOffset = GEODE_WINDOWS64(13) GEODE_WINDOWS32(5);
const uintptr_t mainCallJmpAddress = entryAddr + mainCallJmpOffset;
const int32_t mainCallJmpValue = panicMode ? 0 : *reinterpret_cast<int32_t*>(mainCallJmpAddress + 1);
uintptr_t preWinMainAddr = mainCallJmpAddress + mainCallJmpValue + 5;
// the search bytes for the main function
// 32 bit:
// 6a 00 push 0
// 68 00 00 40 00 push geode::base::get()
// e8 ... call ...
// 64 bit:
// 44 8b cb mov r9d, ebx
// 4c 8b c0 mov r8, rax
// 33 d2 xor edx, edx
// 48 8d 0d xx xx xx xx lea rcx, [rip + ...]
// e8 ... call ...
constexpr uint64_t mainSearchBytes = GEODE_WINDOWS64(0xd233c08b4ccb8b44) GEODE_WINDOWS32(0x004000006a006800);
constexpr ptrdiff_t mainSearchCallOffset = GEODE_WINDOWS64(15) GEODE_WINDOWS32(7);
#ifdef GEODE_IS_WINDOWS32
mainSearchBytes |= static_cast<uint64_t>(geode::base::get()) << 24;
#endif
#ifdef GEODE_IS_WINDOWS64
if (panicMode) {
// at least in 2.206 it seems to be before the entry,
// so do this and if it fails oh well
preWinMainAddr = entryAddr - 0x200;
}
#endif
MSG_BOX_DEBUG("Searching from {:x}, aka {:x}", preWinMainAddr, preWinMainAddr - geode::base::get());
uintptr_t patchAddr = 0;
// 0x1000 should be enough of a limit here..
for (auto searchAddr = preWinMainAddr; searchAddr < preWinMainAddr + 0x1000; searchAddr++) {
if (*reinterpret_cast<uint64_t*>(searchAddr) != mainSearchBytes)
continue;
// follow near call address, this is the call to main
const uintptr_t callAddress = searchAddr + mainSearchCallOffset;
const int32_t callValue = *reinterpret_cast<int32_t*>(callAddress + 1);
patchAddr = callAddress + callValue + 5;
break;
}
if (patchAddr == 0)
return "Geode could not find the main function, not loading Geode.\nYour PC is very likely infected with malware. It's recommended you reinstall Windows.";
#define JMP_ADDR(from, to) (std::bit_cast<uintptr_t>(to) - std::bit_cast<uintptr_t>(from) - 5)
#define JMP_BYTES(from, to) \
static_cast<uint8_t>((JMP_ADDR(from, to) >> 0) & 0xFF), \
static_cast<uint8_t>((JMP_ADDR(from, to) >> 8) & 0xFF), \
static_cast<uint8_t>((JMP_ADDR(from, to) >> 16) & 0xFF), \
static_cast<uint8_t>((JMP_ADDR(from, to) >> 24) & 0xFF)
#ifdef GEODE_IS_WINDOWS64
constexpr size_t patchSize = 15;
uintptr_t jumpAddr = patchAddr + patchSize;
uint8_t trampolineBytes[trampolineSize] = {
// mov [rsp + 8], rbx
0x48, 0x89, 0x5c, 0x24, 0x08,
// mov [rsp + 10], rsi
0x48, 0x89, 0x74, 0x24, 0x10,
// mov [rsp + 18], rdi
0x48, 0x89, 0x7c, 0x24, 0x18,
// jmp [rip + 0]
0xff, 0x25, 0x00, 0x00, 0x00, 0x00,
// pointer to main + 15
static_cast<uint8_t>((jumpAddr >> 0) & 0xFF), static_cast<uint8_t>((jumpAddr >> 8) & 0xFF), static_cast<uint8_t>((jumpAddr >> 16) & 0xFF), static_cast<uint8_t>((jumpAddr >> 24) & 0xFF),
static_cast<uint8_t>((jumpAddr >> 32) & 0xFF), static_cast<uint8_t>((jumpAddr >> 40) & 0xFF), static_cast<uint8_t>((jumpAddr >> 48) & 0xFF), static_cast<uint8_t>((jumpAddr >> 56) & 0xFF),
// nop to pad it out, helps the asm to show up properly on debuggers
0x90, 0x90, 0x90
};
std::memcpy(mainTrampolineAddr, trampolineBytes, trampolineSize);
auto jmpAddr = reinterpret_cast<uintptr_t>(&gdMainHook);
uint8_t patchBytes[patchSize] = {
// jmp [rip + 0]
0xff, 0x25, 0x00, 0x00, 0x00, 0x00,
// pointer to gdMainHook
static_cast<uint8_t>((jmpAddr >> 0) & 0xFF), static_cast<uint8_t>((jmpAddr >> 8) & 0xFF), static_cast<uint8_t>((jmpAddr >> 16) & 0xFF), static_cast<uint8_t>((jmpAddr >> 24) & 0xFF),
static_cast<uint8_t>((jmpAddr >> 32) & 0xFF), static_cast<uint8_t>((jmpAddr >> 40) & 0xFF), static_cast<uint8_t>((jmpAddr >> 48) & 0xFF), static_cast<uint8_t>((jmpAddr >> 56) & 0xFF),
// nop to pad it out, helps the asm to show up properly on debuggers
0x90
};
#else
constexpr size_t patchSize = 6;
uint8_t trampolineBytes[trampolineSize] = {
// push ebp
0x55,
// mov ebp, esp
0x8b, 0xec,
// and esp, ...
0x83, 0xe4, 0xf8,
// jmp main + 6 (after our jmp detour)
0xe9, JMP_BYTES(reinterpret_cast<uintptr_t>(mainTrampolineAddr) + 6, patchAddr + patchSize)
};
std::memcpy(mainTrampolineAddr, trampolineBytes, trampolineSize);
uint8_t patchBytes[patchSize] = {
// jmp gdMainHook
0xe9, JMP_BYTES(patchAddr, &gdMainHook),
// nop to pad it out, helps the asm to show up properly on debuggers
0x90
};
#endif
MSG_BOX_DEBUG("found the main address {:x}", patchAddr - geode::base::get());
DWORD oldProtect;
if (!VirtualProtectEx(process, reinterpret_cast<void*>(patchAddr), patchSize, PAGE_EXECUTE_READWRITE, &oldProtect))
return "Geode could not hook the main function, not loading Geode.";
std::memcpy(reinterpret_cast<void*>(patchAddr), patchBytes, patchSize);
VirtualProtectEx(process, reinterpret_cast<void*>(patchAddr), patchSize, oldProtect, &oldProtect);
return "";
}
DWORD WINAPI upgradeThread(void*) {
updateGeode();
return 0;
}
void earlyError(std::string message) {
// try to write a file and display a message box
// wine might not display the message box but *should* write a file
std::ofstream fout("_geode_early_error.txt");
fout << message;
fout.close();
console::messageBox("Unable to Load Geode!", message);
}
BOOL WINAPI DllMain(HINSTANCE module, DWORD reason, LPVOID) {
if (reason != DLL_PROCESS_ATTACH)
return TRUE;
// Prevents threads from notifying this DLL on creation or destruction.
// Kind of redundant for a game that isn't multi-threaded but will provide
// some slight optimizations if a mod frequently creates and deletes threads.
DisableThreadLibraryCalls(module);
// if we find the old bootstrapper dll, don't load geode, copy new updater and let it do the rest
auto workingDir = dirs::getGameDir();
std::error_code error;
bool oldBootstrapperExists = std::filesystem::exists(workingDir / "GeodeBootstrapper.dll", error);
if (error) {
earlyError("There was an error checking whether the old GeodeBootstrapper.dll exists: " + error.message());
return FALSE;
}
else if (oldBootstrapperExists)
CreateThread(nullptr, 0, upgradeThread, nullptr, 0, nullptr);
else if (auto error = loadGeode(); !error.empty()) {
earlyError(error);
return TRUE;
}
return TRUE;
}
// TODO v5: below is a _Throw_Cpp_error reimpl, this is temp for debugging
static constexpr const char* msgs[] = {
// error messages
"device or resource busy",
"invalid argument",
"no such process",
"not enough memory",
"operation not permitted",
"resource deadlock would occur",
"resource unavailable try again",
};
using errc = std::errc;
static constexpr errc codes[] = {
// system_error codes
errc::device_or_resource_busy,
errc::invalid_argument,
errc::no_such_process,
errc::not_enough_memory,
errc::operation_not_permitted,
errc::resource_deadlock_would_occur,
errc::resource_unavailable_try_again,
};
[[noreturn]] void GEODE_DLL __cdecl throw_cpp_error_hook(int code) {
throw std::system_error((int) codes[code], std::generic_category(), msgs[code]);
}
static void fixThrowCppErrorStub() {
auto address = reinterpret_cast<void*>(&std::_Throw_Cpp_error);
// movabs rax, <hook>
std::vector<uint8_t> patchBytes = {
0x48, 0xb8
};
for (auto byte : geode::toBytes(&throw_cpp_error_hook)) {
patchBytes.push_back(byte);
}
// jmp rax
patchBytes.push_back(0xff);
patchBytes.push_back(0xe0);
if (auto res = Mod::get()->patch(address, patchBytes)) {} else {
log::warn("_Throw_Cpp_error hook failed: {}", res.unwrapErr());
}
}
static bool isWine() {
auto dll = LoadLibraryW(L"ntdll.dll");
return GetProcAddress(dll, "wine_get_version") != nullptr;
}
$execute {
if (isWine()) {
fixThrowCppErrorStub();
}
}