-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMod.cpp
More file actions
293 lines (239 loc) · 10.6 KB
/
Mod.cpp
File metadata and controls
293 lines (239 loc) · 10.6 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
#include "pch.h"
#include "Helpers.h"
#include <stdio.h>
#include <cstdint>
#include <SigScan.h>
#include <detours.h>
#include <toml.hpp>
#include <thread>
#include <string>
#include <curl/curl.h>
#include <iostream>
#include <nlohmann/json.hpp>
#include <isteamuser.h>
#include <steam_api.h>
#include <inttypes.h>
#include <windows.h>
#include <csignal>
#include "Diva.h"
#include "discord/discord.h"
// MegaMix+ addresses
const int64 ShareDivaBotDiscordId = 1004894321067184169;
const uint64_t DivaScoreBaseAddress = 0x00000001412EF568;
const uint64_t DivaScoreCompletionRateAddress = 0x00000001412EF634;
const uint64_t DivaScoreWorstCounterAddress = 0x00000001416E2D40; // For whatever reason the "worst" counter is stored separately from the rest of the hit counters
const uint64_t DivaScoreGradeAddress = 0x00000001416E2D00;
const uint64_t DivaCurrentPVTitleAddress = 0x00000001412EF228;
const uint64_t DivaCurrentPVIdAddress = 0x00000001412C2340;
// Non-SongLimitPatch 1.02
//const uint64_t DivaCurrentPVDifficultyAddress = 0x00000001412B634C;
// SongLimitPatch 1.02 ONLY
const uint64_t DivaCurrentPVDifficultyAddress = 0x00000001423157AC;
const std::string ApiEndpoint = "<REDACTED>";
const std::string ConfigFileName = "config.toml";
const std::string ConfigDiscordUidName = "discordUid";
uint64 steamId64 = 0;
uint64 discordUid = 0;
int DiscordSdkTimeoutCounterLimit = 10;
bool consoleEnabled = false;
void* DivaScoreTrigger = sigScan(
"\x48\x89\x5C\x24\x00\x48\x89\x74\x24\x00\x48\x89\x7C\x24\x00\x55\x41\x54\x41\x55\x41\x56\x41\x57\x48\x8B\xEC\x48\x83\xEC\x60\x48\x8B\x05\x00\x00\x00\x00\x48\x33\xC4\x48\x89\x45\xF8\x48\x8B\xF9\x80\xB9\x00\x00\x00\x00\x00\x0F\x85\x00\x00\x00\x00",
"xxxx?xxxx?xxxx?xxxxxxxxxxxxxxxxxxx????xxxxxxxxxxxx?????xx????"
);
void curlOperation(std::string resultsString)
{
CURL* curl;
CURLcode res;
if (GetConsoleWindow()) {
if (GetConsoleOutputCP() != CP_UTF8) {
SetConsoleOutputCP(CP_UTF8);
}
consoleEnabled = freopen("CONOUT$", "w", stdout) != NULL;
}
if (consoleEnabled)
printf(resultsString.c_str());
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if (curl) {
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
const char* endpoint = ApiEndpoint.c_str();
curl_easy_setopt(curl, CURLOPT_URL, endpoint);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_DEFAULT_PROTOCOL, "https");
curl_easy_setopt(curl, CURLOPT_CAINFO, "mods/ShareDiva/curl-ca-bundle.crt");
struct curl_slist* headers = NULL;
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, resultsString.length());
// Add the results body to the request
const char* data = resultsString.c_str();
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
// Send request, read the result, print any errors or confirm successful send
res = curl_easy_perform(curl);
if (consoleEnabled)
{
if (res != CURLE_OK)
{
printf("[ShareDiva] ERROR: Failed to send PV results to ShareDiva Bot: %s\n", curl_easy_strerror(res));
}
else
{
printf("[ShareDiva] Successfully sent PV results to ShareDiva Bot.\n");
}
}
curl_easy_cleanup(curl);
}
curl_global_cleanup();
}
HOOK(int, __fastcall, _PrintResult, DivaScoreTrigger, int a1) {
DIVA_SCORE DivaScore = *(DIVA_SCORE*)DivaScoreBaseAddress;
int DivaScoreWorst = *(int*)DivaScoreWorstCounterAddress;
DIVA_STAT DivaStat = *(DIVA_STAT*)DivaScoreCompletionRateAddress;
std::string& DivaTitle = *(std::string*)DivaCurrentPVTitleAddress;
DIVA_PV_ID DivaPVId = *(DIVA_PV_ID*)DivaCurrentPVIdAddress;
DIVA_DIFFICULTY DivaDif = *(_DIVA_DIFFICULTY*)DivaCurrentPVDifficultyAddress;
DIVA_GRADE DivaGrade = *(_DIVA_GRADE*)DivaScoreGradeAddress;
// Can't be performed in init() as Steam API hasn't initialised within the game by then
if (steamId64 == 0)
{
CSteamID steamId = SteamUser()->GetSteamID();
steamId64 = steamId.ConvertToUint64();
}
// Client-side processing of whether or not to send the results to ShareDiva bot
bool postScore = false;
switch (DivaDif)
{
case Normal:
if (DivaStat.CompletionRate >= 50.0F)
postScore = true;
break;
case Hard:
if (DivaStat.CompletionRate >= 55.0F)
postScore = true;
break;
case Extreme:
case ExExtreme:
if (DivaStat.CompletionRate >= 65.0F)
postScore = true;
break;
case Easy:
default:
break;
}
if (!postScore)
return original_PrintResult(a1);
// Create JSON with all results that will be sent to the bot
nlohmann::json results = {
{"shareDivaUser", {
{"steamId", steamId64},
{"discordUid", discordUid},
}},
{"pvId", DivaPVId.Id},
{"pvName", DivaTitle},
{"pvDifficulty", DivaDif},
{"totalScore", DivaScore.TotalScore},
{"completionRate", DivaStat.CompletionRate},
{"scoreGrade", DivaGrade},
{"combo", DivaScore.Combo},
{"cool", DivaScore.Cool},
{"fine", DivaScore.Fine},
{"safe", DivaScore.Safe},
{"sad", DivaScore.Sad},
{"worst", DivaScoreWorst}
};
// Dump JSON into a string
std::string resultsString = results.dump();
// Detach a thread that will be sending the result so the game doesn't hang
std::thread curlThread(curlOperation, resultsString);
curlThread.detach();
return original_PrintResult(a1);
};
extern "C"
{
namespace {
volatile bool done{ false };
}
struct DiscordState {
discord::User currentUser;
std::unique_ptr<discord::Core> core;
};
void handleConfigInvalidUidException()
{
if (consoleEnabled)
printf("[ShareDiva] ERROR: Failed to read Discord UID from config file: invalid UID provided.\n");
MessageBoxA(NULL, "Hello there!\nThe 'discorduid' you provided is not a valid number - it must be a numerical number, such as 123456789123456789. Please check the configuration file and try again.\nThe game will now close.", "ShareDiva configuration error", MB_OK);
exit(EXIT_SUCCESS);
}
void handleConfigMissingUid()
{
if (consoleEnabled)
printf("[ShareDiva] ERROR: Failed to read Discord UID from config file: Discord UID was not provided.\n");
MessageBoxA(NULL, "Hello there!\n\nIt appears Discord is unable to co-operate with me and I'm unable to retrieve your Discord UID through it. :( I apologise for this inconvenience, but this out your or my control, this is a fault on Discord's end.\n\nPlease update the 'discorduid' value in ShareDiva's 'config.toml' configuration file with your Discord UID.\nShareDiva configuration file can be found in 'mods/ShareDiva/config.toml'.\n\nInstructions on how to retrieve your Discord UID are provided in the README file in mod's folder.\n\nThe game will now close.", "ShareDiva configuration error", MB_OK);
exit(EXIT_SUCCESS);
}
void __declspec(dllexport) Init()
{
if (GetConsoleWindow()) {
if (GetConsoleOutputCP() != CP_UTF8) {
SetConsoleOutputCP(CP_UTF8);
}
consoleEnabled = freopen("CONOUT$", "w", stdout) != NULL;
}
DiscordState state{};
discord::Core* core{};
auto result = discord::Core::Create(ShareDivaBotDiscordId, DiscordCreateFlags_Default, &core);
state.core.reset(core);
if (!state.core)
{
if (consoleEnabled)
printf("[ShareDiva] ERROR: Failed to load Discord SDK client core, unable to retrieve your Discord UID. Perhaps your Discord client is offline?\n");
}
// Catch any errors on Discord SDK's side and output them to console
core->SetLogHook(discord::LogLevel::Debug, [&](discord::LogLevel log, const char* message)
{
if (consoleEnabled)
printf("[ShareDiva] ERROR (Discord SDK): %s\n", message);
});
// Get the Discord ID of currently logged-in user when the client creates successfully
core->UserManager().OnCurrentUserUpdate.Connect([&state]() {
state.core->UserManager().GetCurrentUser(&state.currentUser);
discordUid = state.currentUser.GetId();
done = true;
if (consoleEnabled)
printf("[ShareDiva] Successfully retrieved currently logged-in Discord user - ShareDiva is now listening to your high-scores, ready to send your results to ShareDiva Discord bot!\n");
});
INSTALL_HOOK(_PrintResult);
std::signal(SIGINT, [](int) { done = true; });
int timeoutCounter = 0;
// Loop until the callback returns successfully, which will then trigger on retrieval of currently logged-in Discord user
do {
state.core->RunCallbacks();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
timeoutCounter += 1;
if (timeoutCounter == DiscordSdkTimeoutCounterLimit) // 10 = 5s
{
done = true;
if (consoleEnabled)
printf("[ShareDiva] Could not retrieve Discord user using Discord SDK. Reading the Discord UID from mod config file instead.\n");
const toml::value config = toml::parse(ConfigFileName);
const toml::value discordUid_ = toml::get<toml::table>(config).at(ConfigDiscordUidName);
try {
discordUid = toml::get<uint64>(discordUid_);
if (consoleEnabled)
printf("[ShareDiva] Using Discord UID retrieved from config file: %" PRIu64 ". If this is incorrect, please update it prior to submitting any song results.\n", discordUid);
}
catch (toml::type_error) {
handleConfigInvalidUidException();
}
catch (std::out_of_range) {
handleConfigInvalidUidException();
}
// 0 is default value in the config, meaning that the user hasn't updated their config file
if (discordUid == 0)
{
handleConfigMissingUid();
}
}
} while (!done);
}
}