Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rubberduckycooly
GitHub Repository: rubberduckycooly/Sonic-1-2-2013-Decompilation
Path: blob/main/RSDKv4/ModAPI.cpp
817 views
1
#include "RetroEngine.hpp"
2
3
#if RETRO_USE_MOD_LOADER || !RETRO_USE_ORIGINAL_CODE
4
char savePath[0x100];
5
#endif
6
7
char playerNames[PLAYER_COUNT][0x20];
8
byte playerCount = 0;
9
10
#if RETRO_USE_MOD_LOADER
11
std::vector<ModInfo> modList;
12
int activeMod = -1;
13
14
char modsPath[0x100];
15
16
bool redirectSave = false;
17
18
char modTypeNames[OBJECT_COUNT][0x40];
19
char modScriptPaths[OBJECT_COUNT][0x40];
20
byte modScriptFlags[OBJECT_COUNT];
21
byte modObjCount = 0;
22
23
#include <filesystem>
24
#include <locale>
25
26
void OpenModMenu()
27
{
28
Engine.gameMode = ENGINE_INITMODMENU;
29
Engine.modMenuCalled = true;
30
}
31
32
#if RETRO_PLATFORM == RETRO_ANDROID
33
namespace fs = std::__fs::filesystem; // this is so we can avoid using c++17, which causes a ton of warnings w asio and looks ugly
34
#else
35
namespace fs = std::filesystem;
36
#endif
37
38
fs::path resolvePath(fs::path given)
39
{
40
if (given.is_relative())
41
given = fs::current_path() / given; // thanks for the weird syntax!
42
for (auto &p : fs::directory_iterator{ given.parent_path() }) {
43
char pbuf[0x100];
44
char gbuf[0x100];
45
auto pf = p.path().filename();
46
auto pstr = pf.string();
47
StringLowerCase(pbuf, pstr.c_str());
48
auto gf = given.filename();
49
auto gstr = gf.string();
50
StringLowerCase(gbuf, gstr.c_str());
51
if (StrComp(pbuf, gbuf)) {
52
return p.path();
53
}
54
}
55
return given; // might work might not!
56
}
57
58
void InitMods()
59
{
60
modList.clear();
61
forceUseScripts = forceUseScripts_Config;
62
skipStartMenu = skipStartMenu_Config;
63
disableFocusPause = disableFocusPause_Config;
64
redirectSave = false;
65
Engine.forceSonic1 = false;
66
sprintf(savePath, "");
67
68
char modBuf[0x100];
69
sprintf(modBuf, "%smods", modsPath);
70
71
fs::path modPath = resolvePath(modBuf);
72
73
if (fs::exists(modPath) && fs::is_directory(modPath)) {
74
std::string mod_config = modPath.string() + "/modconfig.ini";
75
FileIO *configFile = fOpen(mod_config.c_str(), "r");
76
if (configFile) {
77
fClose(configFile);
78
IniParser modConfig(mod_config.c_str(), false);
79
80
for (int m = 0; m < modConfig.items.size(); ++m) {
81
bool active = false;
82
ModInfo info;
83
modConfig.GetBool("mods", modConfig.items[m].key, &active);
84
if (LoadMod(&info, modPath.string(), modConfig.items[m].key, active))
85
modList.push_back(info);
86
}
87
}
88
89
try {
90
auto rdi = fs::directory_iterator(modPath);
91
for (auto de : rdi) {
92
if (de.is_directory()) {
93
fs::path modDirPath = de.path();
94
95
ModInfo info;
96
97
std::string modDir = modDirPath.string().c_str();
98
const std::string mod_inifile = modDir + "/mod.ini";
99
std::string folder = modDirPath.filename().string();
100
101
bool flag = true;
102
for (int m = 0; m < modList.size(); ++m) {
103
if (modList[m].folder == folder) {
104
flag = false;
105
break;
106
}
107
}
108
109
if (flag) {
110
if (LoadMod(&info, modPath.string(), modDirPath.filename().string(), false))
111
modList.push_back(info);
112
}
113
}
114
}
115
} catch (fs::filesystem_error fe) {
116
PrintLog("Mods Folder Scanning Error: ");
117
PrintLog(fe.what());
118
}
119
}
120
121
forceUseScripts = forceUseScripts_Config;
122
skipStartMenu = skipStartMenu_Config;
123
disableFocusPause = disableFocusPause_Config;
124
redirectSave = false;
125
Engine.forceSonic1 = false;
126
sprintf(savePath, "");
127
for (int m = 0; m < modList.size(); ++m) {
128
if (!modList[m].active)
129
continue;
130
if (modList[m].useScripts)
131
forceUseScripts = true;
132
if (modList[m].skipStartMenu)
133
skipStartMenu = true;
134
if (modList[m].disableFocusPause)
135
disableFocusPause |= modList[m].disableFocusPause;
136
if (modList[m].redirectSave) {
137
sprintf(savePath, "%s", modList[m].savePath.c_str());
138
redirectSave = true;
139
}
140
if (modList[m].forceSonic1)
141
Engine.forceSonic1 = true;
142
}
143
144
ReadSaveRAMData();
145
ReadUserdata();
146
}
147
bool LoadMod(ModInfo *info, std::string modsPath, std::string folder, bool active)
148
{
149
if (!info)
150
return false;
151
152
info->fileMap.clear();
153
info->name = "";
154
info->desc = "";
155
info->author = "";
156
info->version = "";
157
info->folder = "";
158
info->active = false;
159
160
const std::string modDir = modsPath + "/" + folder;
161
162
FileIO *f = fOpen((modDir + "/mod.ini").c_str(), "r");
163
if (f) {
164
fClose(f);
165
IniParser modSettings((modDir + "/mod.ini").c_str(), false);
166
167
info->name = "Unnamed Mod";
168
info->desc = "";
169
info->author = "Unknown Author";
170
info->version = "1.0.0";
171
info->folder = folder;
172
173
char infoBuf[0x100];
174
// Name
175
StrCopy(infoBuf, "");
176
modSettings.GetString("", "Name", infoBuf);
177
if (!StrComp(infoBuf, ""))
178
info->name = infoBuf;
179
// Desc
180
StrCopy(infoBuf, "");
181
modSettings.GetString("", "Description", infoBuf);
182
if (!StrComp(infoBuf, ""))
183
info->desc = infoBuf;
184
// Author
185
StrCopy(infoBuf, "");
186
modSettings.GetString("", "Author", infoBuf);
187
if (!StrComp(infoBuf, ""))
188
info->author = infoBuf;
189
// Version
190
StrCopy(infoBuf, "");
191
modSettings.GetString("", "Version", infoBuf);
192
if (!StrComp(infoBuf, ""))
193
info->version = infoBuf;
194
195
info->active = active;
196
197
ScanModFolder(info);
198
199
info->useScripts = false;
200
modSettings.GetBool("", "TxtScripts", &info->useScripts);
201
if (info->useScripts && info->active)
202
forceUseScripts = true;
203
204
info->skipStartMenu = false;
205
modSettings.GetBool("", "SkipStartMenu", &info->skipStartMenu);
206
if (info->skipStartMenu && info->active)
207
skipStartMenu = true;
208
209
info->disableFocusPause = false;
210
modSettings.GetInteger("", "DisableFocusPause", &info->disableFocusPause);
211
if (info->disableFocusPause && info->active)
212
disableFocusPause |= info->disableFocusPause;
213
214
info->redirectSave = false;
215
modSettings.GetBool("", "RedirectSaveRAM", &info->redirectSave);
216
if (info->redirectSave) {
217
char path[0x100];
218
sprintf(path, "mods/%s/", folder.c_str());
219
info->savePath = path;
220
}
221
222
info->forceSonic1 = false;
223
modSettings.GetBool("", "ForceSonic1", &info->forceSonic1);
224
if (info->forceSonic1 && info->active)
225
Engine.forceSonic1 = true;
226
227
return true;
228
}
229
return false;
230
}
231
232
void ScanModFolder(ModInfo *info)
233
{
234
if (!info)
235
return;
236
237
char modBuf[0x100];
238
sprintf(modBuf, "%smods", modsPath);
239
240
fs::path modPath = resolvePath(modBuf);
241
242
const std::string modDir = modPath.string() + "/" + info->folder;
243
244
info->fileMap.clear();
245
246
// Check for Data/ replacements
247
fs::path dataPath = resolvePath(modDir + "/Data");
248
249
if (fs::exists(dataPath) && fs::is_directory(dataPath)) {
250
try {
251
auto data_rdi = fs::recursive_directory_iterator(dataPath);
252
for (auto &data_de : data_rdi) {
253
if (data_de.is_regular_file()) {
254
char modBuf[0x100];
255
StrCopy(modBuf, data_de.path().string().c_str());
256
char folderTest[4][0x10] = {
257
"Data/",
258
"Data\\",
259
"data/",
260
"data\\",
261
};
262
int tokenPos = -1;
263
for (int i = 0; i < 4; ++i) {
264
tokenPos = FindLastStringToken(modBuf, folderTest[i]);
265
if (tokenPos >= 0)
266
break;
267
}
268
269
if (tokenPos >= 0) {
270
char buffer[0x80];
271
for (int i = StrLength(modBuf); i >= tokenPos; --i) {
272
buffer[i - tokenPos] = modBuf[i] == '\\' ? '/' : modBuf[i];
273
}
274
275
// PrintLog(modBuf);
276
std::string path(buffer);
277
std::string modPath(modBuf);
278
char pathLower[0x100];
279
memset(pathLower, 0, sizeof(char) * 0x100);
280
for (int c = 0; c < path.size(); ++c) {
281
pathLower[c] = tolower(path.c_str()[c]);
282
}
283
284
info->fileMap.insert(std::pair<std::string, std::string>(pathLower, modBuf));
285
}
286
}
287
}
288
} catch (fs::filesystem_error fe) {
289
PrintLog("Data Folder Scanning Error: ");
290
PrintLog(fe.what());
291
}
292
}
293
294
// Check for Bytecode/ replacements
295
fs::path bytecodePath = resolvePath(modDir + "/Bytecode");
296
297
if (fs::exists(bytecodePath) && fs::is_directory(bytecodePath)) {
298
try {
299
auto data_rdi = fs::recursive_directory_iterator(bytecodePath);
300
for (auto &data_de : data_rdi) {
301
if (data_de.is_regular_file()) {
302
char modBuf[0x100];
303
StrCopy(modBuf, data_de.path().string().c_str());
304
char folderTest[4][0x10] = {
305
"Bytecode/",
306
"Bytecode\\",
307
"bytecode/",
308
"bytecode\\",
309
};
310
int tokenPos = -1;
311
for (int i = 0; i < 4; ++i) {
312
tokenPos = FindLastStringToken(modBuf, folderTest[i]);
313
if (tokenPos >= 0)
314
break;
315
}
316
317
if (tokenPos >= 0) {
318
char buffer[0x80];
319
for (int i = StrLength(modBuf); i >= tokenPos; --i) {
320
buffer[i - tokenPos] = modBuf[i] == '\\' ? '/' : modBuf[i];
321
}
322
323
// PrintLog(modBuf);
324
std::string path(buffer);
325
std::string modPath(modBuf);
326
char pathLower[0x100];
327
memset(pathLower, 0, sizeof(char) * 0x100);
328
for (int c = 0; c < path.size(); ++c) {
329
pathLower[c] = tolower(path.c_str()[c]);
330
}
331
332
info->fileMap.insert(std::pair<std::string, std::string>(pathLower, modBuf));
333
}
334
}
335
}
336
} catch (fs::filesystem_error fe) {
337
PrintLog("Bytecode Folder Scanning Error: ");
338
PrintLog(fe.what());
339
}
340
}
341
}
342
343
void SaveMods()
344
{
345
char modBuf[0x100];
346
sprintf(modBuf, "%smods", modsPath);
347
fs::path modPath = resolvePath(modBuf);
348
349
if (fs::exists(modPath) && fs::is_directory(modPath)) {
350
std::string mod_config = modPath.string() + "/modconfig.ini";
351
IniParser modConfig;
352
353
for (int m = 0; m < modList.size(); ++m) {
354
ModInfo *info = &modList[m];
355
356
modConfig.SetBool("mods", info->folder.c_str(), info->active);
357
}
358
359
modConfig.Write(mod_config.c_str(), false);
360
}
361
}
362
363
void RefreshEngine()
364
{
365
// Reload entire engine
366
Engine.LoadGameConfig("Data/Game/GameConfig.bin");
367
#if RETRO_USING_SDL2
368
if (Engine.window) {
369
char gameTitle[0x40];
370
sprintf(gameTitle, "%s%s", Engine.gameWindowText, Engine.usingDataFile_Config ? "" : " (Using Data Folder)");
371
SDL_SetWindowTitle(Engine.window, gameTitle);
372
}
373
#elif RETRO_USING_SDL1
374
char gameTitle[0x40];
375
sprintf(gameTitle, "%s%s", Engine.gameWindowText, Engine.usingDataFile_Config ? "" : " (Using Data Folder)");
376
SDL_WM_SetCaption(gameTitle, NULL);
377
#endif
378
379
ClearMeshData();
380
ClearTextures(true);
381
382
nativeEntityCountBackup = 0;
383
memset(backupEntityList, 0, sizeof(backupEntityList));
384
memset(objectEntityBackup, 0, sizeof(objectEntityBackup));
385
386
nativeEntityCountBackupS = 0;
387
memset(backupEntityListS, 0, sizeof(backupEntityListS));
388
memset(objectEntityBackupS, 0, sizeof(objectEntityBackupS));
389
390
for (int i = 0; i < FONTLIST_COUNT; ++i) {
391
fontList[i].count = 2;
392
}
393
394
ReleaseStageSfx();
395
ReleaseGlobalSfx();
396
LoadGlobalSfx();
397
InitLocalizedStrings();
398
399
for (nativeEntityPos = 0; nativeEntityPos < nativeEntityCount; ++nativeEntityPos) {
400
NativeEntity *entity = &objectEntityBank[activeEntityList[nativeEntityPos]];
401
entity->eventCreate(entity);
402
}
403
404
forceUseScripts = forceUseScripts_Config;
405
skipStartMenu = skipStartMenu_Config;
406
disableFocusPause = disableFocusPause_Config;
407
redirectSave = false;
408
Engine.forceSonic1 = false;
409
sprintf(savePath, "");
410
for (int m = 0; m < modList.size(); ++m) {
411
if (!modList[m].active)
412
continue;
413
if (modList[m].useScripts)
414
forceUseScripts = true;
415
if (modList[m].skipStartMenu)
416
skipStartMenu = true;
417
if (modList[m].disableFocusPause)
418
disableFocusPause |= modList[m].disableFocusPause;
419
if (modList[m].redirectSave) {
420
sprintf(savePath, "%s", modList[m].savePath.c_str());
421
redirectSave = true;
422
}
423
if (modList[m].forceSonic1)
424
Engine.forceSonic1 = true;
425
}
426
427
Engine.gameType = GAME_SONIC2;
428
if (strstr(Engine.gameWindowText, "Sonic 1") || Engine.forceSonic1) {
429
Engine.gameType = GAME_SONIC1;
430
}
431
432
achievementCount = 0;
433
if (Engine.gameType == GAME_SONIC1) {
434
AddAchievement("Ramp Ring Acrobatics",
435
"Without touching the ground,\rcollect all the rings in a\rtrapezoid formation in Green\rHill Zone Act 1");
436
AddAchievement("Blast Processing", "Clear Green Hill Zone Act 1\rin under 30 seconds");
437
AddAchievement("Secret of Marble Zone", "Travel though a secret\rroom in Marbale Zone Act 3");
438
AddAchievement("Block Buster", "Break 16 blocks in a row\rwithout stopping");
439
AddAchievement("Ring King", "Collect 200 Rings");
440
AddAchievement("Secret of Labyrinth Zone", "Activate and ride the\rhidden platform in\rLabyrinth Zone Act 1");
441
AddAchievement("Flawless Pursuit", "Clear the boss in Labyrinth\rZone without getting hurt");
442
AddAchievement("Bombs Away", "Defeat the boss in Starlight Zone\rusing only the see-saw bombs");
443
AddAchievement("Hidden Transporter", "Collect 50 Rings and take the hidden transporter path\rin Scrap Brain Act 2");
444
AddAchievement("Chaos Connoisseur", "Collect all the chaos\remeralds");
445
AddAchievement("One For the Road", "As a parting gift, land a\rfinal hit on Dr. Eggman's\rescaping Egg Mobile");
446
AddAchievement("Beat The Clock", "Clear the Time Attack\rmode in less than 45\rminutes");
447
}
448
else if (Engine.gameType == GAME_SONIC2) {
449
AddAchievement("Quick Run", "Complete Emerald Hill\rZone Act 1 in under 35\rseconds");
450
AddAchievement("100% Chemical Free", "Complete Chemical Plant\rwithout going underwater");
451
AddAchievement("Early Bird Special", "Collect all the Chaos\rEmeralds before Chemical\rPlant");
452
AddAchievement("Superstar", "Complete any Act as\rSuper Sonic");
453
AddAchievement("Hit it Big", "Get a jackpot on the Casino Night slot machines");
454
AddAchievement("Bop Non-stop", "Defeat any boss in 8\rconsecutive hits without\rtouching he ground");
455
AddAchievement("Perfectionist", "Get a Perfect Bonus by\rcollecting every Ring in an\rAct");
456
AddAchievement("A Secret Revealed", "Find and complete\rHidden Palace Zone");
457
AddAchievement("Head 2 Head", "Win a 2P Versus race\ragainst a friend");
458
AddAchievement("Metropolis Master", "Complete Any Metropolis\rZone Act without getting\rhurt");
459
AddAchievement("Scrambled Egg", "Defeat Dr. Eggman's Boss\rAttack mode in under 7\rminutes");
460
AddAchievement("Beat the Clock", "Complete the Time Attack\rmode in less than 45\rminutes");
461
}
462
463
SaveMods();
464
465
ReadSaveRAMData();
466
ReadUserdata();
467
}
468
469
void GetModCount() { scriptEng.checkResult = (int)modList.size(); }
470
void GetModName(int *textMenu, int *highlight, uint *id, int *unused)
471
{
472
if (*id >= modList.size())
473
return;
474
475
TextMenu *menu = &gameMenu[*textMenu];
476
menu->entryHighlight[menu->rowCount] = *highlight;
477
AddTextMenuEntry(menu, modList[*id].name.c_str());
478
}
479
480
void GetModDescription(int *textMenu, int *highlight, uint *id, int *unused)
481
{
482
if (*id >= modList.size())
483
return;
484
485
TextMenu *menu = &gameMenu[*textMenu];
486
menu->entryHighlight[menu->rowCount] = *highlight;
487
AddTextMenuEntry(menu, modList[*id].desc.c_str());
488
}
489
490
void GetModAuthor(int *textMenu, int *highlight, uint *id, int *unused)
491
{
492
if (*id >= modList.size())
493
return;
494
495
TextMenu *menu = &gameMenu[*textMenu];
496
menu->entryHighlight[menu->rowCount] = *highlight;
497
AddTextMenuEntry(menu, modList[*id].author.c_str());
498
}
499
500
void GetModVersion(int *textMenu, int *highlight, uint *id, int *unused)
501
{
502
if (*id >= modList.size())
503
return;
504
505
TextMenu *menu = &gameMenu[*textMenu];
506
menu->entryHighlight[menu->rowCount] = *highlight;
507
AddTextMenuEntry(menu, modList[*id].version.c_str());
508
}
509
510
void GetModActive(uint *id, int *unused)
511
{
512
scriptEng.checkResult = false;
513
if (*id >= modList.size())
514
return;
515
516
scriptEng.checkResult = modList[*id].active;
517
}
518
519
void SetModActive(uint *id, int *active)
520
{
521
if (*id >= modList.size())
522
return;
523
524
modList[*id].active = *active;
525
}
526
527
void MoveMod(uint *id, int *up)
528
{
529
if (!id || !up)
530
return;
531
532
int preOption = *id;
533
int option = preOption + (*up ? -1 : 1);
534
if (option < 0 || preOption < 0)
535
return;
536
537
if (option >= (int)modList.size() || preOption >= (int)modList.size())
538
return;
539
540
ModInfo swap = modList[preOption];
541
modList[preOption] = modList[option];
542
modList[option] = swap;
543
}
544
545
#endif
546
547
#if RETRO_USE_MOD_LOADER || !RETRO_USE_ORIGINAL_CODE
548
int GetSceneID(byte listID, const char *sceneName)
549
{
550
if (listID >= 3)
551
return -1;
552
553
char scnName[0x40];
554
int scnPos = 0;
555
int pos = 0;
556
while (sceneName[scnPos]) {
557
if (sceneName[scnPos] != ' ')
558
scnName[pos++] = sceneName[scnPos];
559
++scnPos;
560
}
561
scnName[pos] = 0;
562
563
for (int s = 0; s < stageListCount[listID]; ++s) {
564
char nameBuffer[0x40];
565
566
scnPos = 0;
567
pos = 0;
568
while (stageList[listID][s].name[scnPos]) {
569
if (stageList[listID][s].name[scnPos] != ' ')
570
nameBuffer[pos++] = stageList[listID][s].name[scnPos];
571
++scnPos;
572
}
573
nameBuffer[pos] = 0;
574
575
if (StrComp(scnName, nameBuffer)) {
576
return s;
577
}
578
}
579
return -1;
580
}
581
#endif
582
583