1 /*****************************************************************************
2  * Copyright (c) 2014-2020 OpenRCT2 developers
3  *
4  * For a complete list of all authors, please refer to contributors.md
5  * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6  *
7  * OpenRCT2 is licensed under the GNU General Public License version 3.
8  *****************************************************************************/
9 
10 #include "Game.h"
11 
12 #include "Cheats.h"
13 #include "Context.h"
14 #include "Editor.h"
15 #include "FileClassifier.h"
16 #include "GameStateSnapshots.h"
17 #include "Input.h"
18 #include "OpenRCT2.h"
19 #include "ParkImporter.h"
20 #include "PlatformEnvironment.h"
21 #include "ReplayManager.h"
22 #include "actions/LoadOrQuitAction.h"
23 #include "audio/audio.h"
24 #include "config/Config.h"
25 #include "core/Console.hpp"
26 #include "core/FileScanner.h"
27 #include "core/Path.hpp"
28 #include "interface/Colour.h"
29 #include "interface/Screenshot.h"
30 #include "interface/Viewport.h"
31 #include "interface/Window.h"
32 #include "localisation/Localisation.h"
33 #include "management/Finance.h"
34 #include "management/Marketing.h"
35 #include "management/Research.h"
36 #include "network/network.h"
37 #include "object/Object.h"
38 #include "object/ObjectList.h"
39 #include "peep/Peep.h"
40 #include "peep/Staff.h"
41 #include "platform/Platform2.h"
42 #include "rct1/RCT1.h"
43 #include "ride/Ride.h"
44 #include "ride/RideRatings.h"
45 #include "ride/Station.h"
46 #include "ride/Track.h"
47 #include "ride/TrackDesign.h"
48 #include "ride/Vehicle.h"
49 #include "scenario/Scenario.h"
50 #include "scripting/ScriptEngine.h"
51 #include "title/TitleScreen.h"
52 #include "ui/UiContext.h"
53 #include "ui/WindowManager.h"
54 #include "util/SawyerCoding.h"
55 #include "util/Util.h"
56 #include "windows/Intent.h"
57 #include "world/Banner.h"
58 #include "world/Climate.h"
59 #include "world/Entrance.h"
60 #include "world/Footpath.h"
61 #include "world/Map.h"
62 #include "world/MapAnimation.h"
63 #include "world/Park.h"
64 #include "world/Scenery.h"
65 #include "world/Sprite.h"
66 #include "world/Surface.h"
67 #include "world/Water.h"
68 
69 #include <algorithm>
70 #include <cstdio>
71 #include <iterator>
72 #include <memory>
73 
74 uint16_t gCurrentDeltaTime;
75 uint8_t gGamePaused = 0;
76 int32_t gGameSpeed = 1;
77 bool gDoSingleUpdate = false;
78 float gDayNightCycle = 0;
79 bool gInUpdateCode = false;
80 bool gInMapInitCode = false;
81 std::string gCurrentLoadedPath;
82 
83 bool gLoadKeepWindowsOpen = false;
84 
85 uint32_t gCurrentTicks;
86 uint32_t gCurrentRealTimeTicks;
87 
88 rct_string_id gGameCommandErrorTitle;
89 rct_string_id gGameCommandErrorText;
90 
91 using namespace OpenRCT2;
92 
game_reset_speed()93 void game_reset_speed()
94 {
95     gGameSpeed = 1;
96     window_invalidate_by_class(WC_TOP_TOOLBAR);
97 }
98 
game_increase_game_speed()99 void game_increase_game_speed()
100 {
101     gGameSpeed = std::min(gConfigGeneral.debugging_tools ? 5 : 4, gGameSpeed + 1);
102     if (gGameSpeed == 5)
103         gGameSpeed = 8;
104     window_invalidate_by_class(WC_TOP_TOOLBAR);
105 }
106 
game_reduce_game_speed()107 void game_reduce_game_speed()
108 {
109     gGameSpeed = std::max(1, gGameSpeed - 1);
110     if (gGameSpeed == 7)
111         gGameSpeed = 4;
112     window_invalidate_by_class(WC_TOP_TOOLBAR);
113 }
114 
115 /**
116  *
117  *  rct2: 0x0066B5C0 (part of 0x0066B3E8)
118  */
game_create_windows()119 void game_create_windows()
120 {
121     context_open_window(WC_MAIN_WINDOW);
122     context_open_window(WC_TOP_TOOLBAR);
123     context_open_window(WC_BOTTOM_TOOLBAR);
124     window_resize_gui(context_get_width(), context_get_height());
125 }
126 
127 enum
128 {
129     SPR_GAME_PALETTE_DEFAULT = 1532,
130     SPR_GAME_PALETTE_WATER = 1533,
131     SPR_GAME_PALETTE_WATER_DARKER_1 = 1534,
132     SPR_GAME_PALETTE_WATER_DARKER_2 = 1535,
133     SPR_GAME_PALETTE_3 = 1536,
134     SPR_GAME_PALETTE_3_DARKER_1 = 1537,
135     SPR_GAME_PALETTE_3_DARKER_2 = 1538,
136     SPR_GAME_PALETTE_4 = 1539,
137     SPR_GAME_PALETTE_4_DARKER_1 = 1540,
138     SPR_GAME_PALETTE_4_DARKER_2 = 1541,
139 };
140 
141 /**
142  *
143  *  rct2: 0x006838BD
144  */
update_palette_effects()145 void update_palette_effects()
146 {
147     auto water_type = static_cast<rct_water_type*>(object_entry_get_chunk(ObjectType::Water, 0));
148 
149     if (gClimateLightningFlash == 1)
150     {
151         // Change palette to lighter colour during lightning
152         int32_t palette = SPR_GAME_PALETTE_DEFAULT;
153 
154         if (water_type != nullptr)
155         {
156             palette = water_type->image_id;
157         }
158         const rct_g1_element* g1 = gfx_get_g1_element(palette);
159         if (g1 != nullptr)
160         {
161             int32_t xoffset = g1->x_offset;
162             xoffset = xoffset * 4;
163             uint8_t* paletteOffset = gGamePalette + xoffset;
164             for (int32_t i = 0; i < g1->width; i++)
165             {
166                 paletteOffset[(i * 4) + 0] = -((0xFF - g1->offset[(i * 3) + 0]) / 2) - 1;
167                 paletteOffset[(i * 4) + 1] = -((0xFF - g1->offset[(i * 3) + 1]) / 2) - 1;
168                 paletteOffset[(i * 4) + 2] = -((0xFF - g1->offset[(i * 3) + 2]) / 2) - 1;
169             }
170             platform_update_palette(gGamePalette, PALETTE_OFFSET_DYNAMIC, PALETTE_LENGTH_DYNAMIC);
171         }
172         gClimateLightningFlash++;
173     }
174     else
175     {
176         if (gClimateLightningFlash == 2)
177         {
178             // Change palette back to normal after lightning
179             int32_t palette = SPR_GAME_PALETTE_DEFAULT;
180 
181             if (water_type != nullptr)
182             {
183                 palette = water_type->image_id;
184             }
185 
186             const rct_g1_element* g1 = gfx_get_g1_element(palette);
187             if (g1 != nullptr)
188             {
189                 int32_t xoffset = g1->x_offset;
190                 xoffset = xoffset * 4;
191                 uint8_t* paletteOffset = gGamePalette + xoffset;
192                 for (int32_t i = 0; i < g1->width; i++)
193                 {
194                     paletteOffset[(i * 4) + 0] = g1->offset[(i * 3) + 0];
195                     paletteOffset[(i * 4) + 1] = g1->offset[(i * 3) + 1];
196                     paletteOffset[(i * 4) + 2] = g1->offset[(i * 3) + 2];
197                 }
198             }
199         }
200 
201         // Animate the water/lava/chain movement palette
202         uint32_t shade = 0;
203         if (gConfigGeneral.render_weather_gloom)
204         {
205             auto paletteId = climate_get_weather_gloom_palette_id(gClimateCurrent);
206             if (paletteId != FilterPaletteID::PaletteNull)
207             {
208                 shade = 1;
209                 if (paletteId != FilterPaletteID::PaletteDarken1)
210                 {
211                     shade = 2;
212                 }
213             }
214         }
215         uint32_t j = gPaletteEffectFrame;
216         j = ((static_cast<uint16_t>((~j / 2) * 128) * 15) >> 16);
217         uint32_t waterId = SPR_GAME_PALETTE_WATER;
218         if (water_type != nullptr)
219         {
220             waterId = water_type->palette_index_1;
221         }
222         const rct_g1_element* g1 = gfx_get_g1_element(shade + waterId);
223         if (g1 != nullptr)
224         {
225             uint8_t* vs = &g1->offset[j * 3];
226             uint8_t* vd = &gGamePalette[PALETTE_OFFSET_WATER_WAVES * 4];
227             int32_t n = PALETTE_LENGTH_WATER_WAVES;
228             for (int32_t i = 0; i < n; i++)
229             {
230                 vd[0] = vs[0];
231                 vd[1] = vs[1];
232                 vd[2] = vs[2];
233                 vs += 9;
234                 if (vs >= &g1->offset[9 * n])
235                 {
236                     vs -= 9 * n;
237                 }
238                 vd += 4;
239             }
240         }
241 
242         waterId = SPR_GAME_PALETTE_3;
243         if (water_type != nullptr)
244         {
245             waterId = water_type->palette_index_2;
246         }
247         g1 = gfx_get_g1_element(shade + waterId);
248         if (g1 != nullptr)
249         {
250             uint8_t* vs = &g1->offset[j * 3];
251             uint8_t* vd = &gGamePalette[PALETTE_OFFSET_WATER_SPARKLES * 4];
252             int32_t n = PALETTE_LENGTH_WATER_SPARKLES;
253             for (int32_t i = 0; i < n; i++)
254             {
255                 vd[0] = vs[0];
256                 vd[1] = vs[1];
257                 vd[2] = vs[2];
258                 vs += 9;
259                 if (vs >= &g1->offset[9 * n])
260                 {
261                     vs -= 9 * n;
262                 }
263                 vd += 4;
264             }
265         }
266 
267         j = (static_cast<uint16_t>(gPaletteEffectFrame * -960) * 3) >> 16;
268         waterId = SPR_GAME_PALETTE_4;
269         g1 = gfx_get_g1_element(shade + waterId);
270         if (g1 != nullptr)
271         {
272             uint8_t* vs = &g1->offset[j * 3];
273             uint8_t* vd = &gGamePalette[PALETTE_INDEX_243 * 4];
274             int32_t n = 3;
275             for (int32_t i = 0; i < n; i++)
276             {
277                 vd[0] = vs[0];
278                 vd[1] = vs[1];
279                 vd[2] = vs[2];
280                 vs += 3;
281                 if (vs >= &g1->offset[3 * n])
282                 {
283                     vs -= 3 * n;
284                 }
285                 vd += 4;
286             }
287         }
288 
289         platform_update_palette(gGamePalette, PALETTE_OFFSET_ANIMATED, PALETTE_LENGTH_ANIMATED);
290         if (gClimateLightningFlash == 2)
291         {
292             platform_update_palette(gGamePalette, PALETTE_OFFSET_DYNAMIC, PALETTE_LENGTH_DYNAMIC);
293             gClimateLightningFlash = 0;
294         }
295     }
296 }
297 
pause_toggle()298 void pause_toggle()
299 {
300     gGamePaused ^= GAME_PAUSED_NORMAL;
301     window_invalidate_by_class(WC_TOP_TOOLBAR);
302     if (gGamePaused & GAME_PAUSED_NORMAL)
303     {
304         OpenRCT2::Audio::StopAll();
305     }
306 }
307 
game_is_paused()308 bool game_is_paused()
309 {
310     return gGamePaused != 0;
311 }
312 
game_is_not_paused()313 bool game_is_not_paused()
314 {
315     return gGamePaused == 0;
316 }
317 
318 /**
319  *
320  *  rct2: 0x0066DC0F
321  */
load_landscape()322 static void load_landscape()
323 {
324     auto intent = Intent(WC_LOADSAVE);
325     intent.putExtra(INTENT_EXTRA_LOADSAVE_TYPE, LOADSAVETYPE_LOAD | LOADSAVETYPE_LANDSCAPE);
326     context_open_intent(&intent);
327 }
328 
utf8_to_rct2_self(char * buffer,size_t length)329 void utf8_to_rct2_self(char* buffer, size_t length)
330 {
331     auto temp = utf8_to_rct2(buffer);
332 
333     size_t i = 0;
334     const char* src = temp.data();
335     char* dst = buffer;
336     while (*src != 0 && i < length - 1)
337     {
338         if (*src == static_cast<char>(static_cast<uint8_t>(0xFF)))
339         {
340             if (i < length - 3)
341             {
342                 *dst++ = *src++;
343                 *dst++ = *src++;
344                 *dst++ = *src++;
345             }
346             else
347             {
348                 break;
349             }
350             i += 3;
351         }
352         else
353         {
354             *dst++ = *src++;
355             i++;
356         }
357     }
358     do
359     {
360         *dst++ = '\0';
361         i++;
362     } while (i < length);
363 }
364 
rct2_to_utf8_self(char * buffer,size_t length)365 void rct2_to_utf8_self(char* buffer, size_t length)
366 {
367     if (length > 0)
368     {
369         auto temp = rct2_to_utf8(buffer, RCT2LanguageId::EnglishUK);
370         safe_strcpy(buffer, temp.data(), length);
371     }
372 }
373 
374 /**
375  * Converts all the user strings and news item strings to UTF-8.
376  */
game_convert_strings_to_utf8()377 void game_convert_strings_to_utf8()
378 {
379     // Scenario details
380     gScenarioCompletedBy = rct2_to_utf8(gScenarioCompletedBy, RCT2LanguageId::EnglishUK);
381     gScenarioName = rct2_to_utf8(gScenarioName, RCT2LanguageId::EnglishUK);
382     gScenarioDetails = rct2_to_utf8(gScenarioDetails, RCT2LanguageId::EnglishUK);
383 }
384 
385 /**
386  * Converts all the user strings and news item strings to RCT2 encoding.
387  */
game_convert_strings_to_rct2(rct_s6_data * s6)388 void game_convert_strings_to_rct2(rct_s6_data* s6)
389 {
390     // Scenario details
391     utf8_to_rct2_self(s6->scenario_completed_name, sizeof(s6->scenario_completed_name));
392     utf8_to_rct2_self(s6->scenario_name, sizeof(s6->scenario_name));
393     utf8_to_rct2_self(s6->scenario_description, sizeof(s6->scenario_description));
394 
395     // User strings
396     for (auto* userString : s6->custom_strings)
397     {
398         if (!str_is_null_or_empty(userString))
399         {
400             utf8_to_rct2_self(userString, RCT12_USER_STRING_MAX_LENGTH);
401         }
402     }
403 }
404 
405 // OpenRCT2 workaround to recalculate some values which are saved redundantly in the save to fix corrupted files.
406 // For example recalculate guest count by looking at all the guests instead of trusting the value in the file.
game_fix_save_vars()407 void game_fix_save_vars()
408 {
409     // Recalculates peep count after loading a save to fix corrupted files
410     uint32_t guestCount = 0;
411     {
412         for (auto guest : EntityList<Guest>())
413         {
414             if (!guest->OutsideOfPark)
415             {
416                 guestCount++;
417             }
418         }
419     }
420 
421     gNumGuestsInPark = guestCount;
422 
423     // Peeps to remove have to be cached here, as removing them from within the loop breaks iteration
424     std::vector<Peep*> peepsToRemove;
425 
426     // Fix possibly invalid field values
427     for (auto peep : EntityList<Guest>())
428     {
429         if (peep->CurrentRideStation >= MAX_STATIONS)
430         {
431             const auto srcStation = peep->CurrentRideStation;
432             const auto rideIdx = peep->CurrentRide;
433             if (rideIdx == RIDE_ID_NULL)
434             {
435                 continue;
436             }
437             Ride* ride = get_ride(rideIdx);
438             if (ride == nullptr)
439             {
440                 log_warning("Couldn't find ride %u, resetting ride on peep %u", rideIdx, peep->sprite_index);
441                 peep->CurrentRide = RIDE_ID_NULL;
442                 continue;
443             }
444             auto curName = peep->GetName();
445             log_warning(
446                 "Peep %u (%s) has invalid ride station = %u for ride %u.", peep->sprite_index, curName.c_str(), srcStation,
447                 rideIdx);
448             auto station = ride_get_first_valid_station_exit(ride);
449             if (station == STATION_INDEX_NULL)
450             {
451                 log_warning("Couldn't find station, removing peep %u", peep->sprite_index);
452                 peepsToRemove.push_back(peep);
453             }
454             else
455             {
456                 log_warning("Amending ride station to %u.", station);
457                 peep->CurrentRideStation = station;
458             }
459         }
460     }
461 
462     if (!peepsToRemove.empty())
463     {
464         // Some broken saves have broken spatial indexes
465         reset_sprite_spatial_index();
466     }
467 
468     for (auto ptr : peepsToRemove)
469     {
470         ptr->Remove();
471     }
472 
473     // Fixes broken saves where a surface element could be null
474     // and broken saves with incorrect invisible map border tiles
475     for (int32_t y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; y++)
476     {
477         for (int32_t x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; x++)
478         {
479             auto* surfaceElement = map_get_surface_element_at(TileCoordsXY{ x, y }.ToCoordsXY());
480 
481             if (surfaceElement == nullptr)
482             {
483                 log_error("Null map element at x = %d and y = %d. Fixing...", x, y);
484                 surfaceElement = TileElementInsert<SurfaceElement>(TileCoordsXYZ{ x, y, 14 }.ToCoordsXYZ(), 0b0000);
485                 if (surfaceElement == nullptr)
486                 {
487                     log_error("Unable to fix: Map element limit reached.");
488                     return;
489                 }
490             }
491 
492             // Fix the invisible border tiles.
493             // At this point, we can be sure that surfaceElement is not NULL.
494             if (x == 0 || x == gMapSize - 1 || y == 0 || y == gMapSize - 1)
495             {
496                 surfaceElement->SetBaseZ(MINIMUM_LAND_HEIGHT_BIG);
497                 surfaceElement->SetClearanceZ(MINIMUM_LAND_HEIGHT_BIG);
498                 surfaceElement->SetSlope(0);
499                 surfaceElement->SetWaterHeight(0);
500             }
501         }
502     }
503 
504     research_fix();
505 
506     // Fix banner list pointing to NULL map elements
507     banner_reset_broken_index();
508 
509     // Fix banners which share their index
510     fix_duplicated_banners();
511 
512     // Fix invalid vehicle sprite sizes, thus preventing visual corruption of sprites
513     fix_invalid_vehicle_sprite_sizes();
514 
515     // Fix gParkEntrance locations for which the tile_element no longer exists
516     fix_park_entrance_locations();
517 }
518 
game_load_init()519 void game_load_init()
520 {
521     IGameStateSnapshots* snapshots = GetContext()->GetGameStateSnapshots();
522     snapshots->Reset();
523 
524     gScreenFlags = SCREEN_FLAGS_PLAYING;
525     OpenRCT2::Audio::StopAll();
526     if (!gLoadKeepWindowsOpen)
527     {
528         viewport_init_all();
529         game_create_windows();
530     }
531     else
532     {
533         auto* mainWindow = window_get_main();
534         window_unfollow_sprite(mainWindow);
535     }
536 
537     auto windowManager = GetContext()->GetUiContext()->GetWindowManager();
538     windowManager->SetMainView(gSavedView, gSavedViewZoom, gSavedViewRotation);
539 
540     if (network_get_mode() != NETWORK_MODE_CLIENT)
541     {
542         GameActions::ClearQueue();
543     }
544     reset_sprite_spatial_index();
545     reset_all_sprite_quadrant_placements();
546     scenery_set_default_placement_configuration();
547 
548     auto intent = Intent(INTENT_ACTION_REFRESH_NEW_RIDES);
549     context_broadcast_intent(&intent);
550 
551     gWindowUpdateTicks = 0;
552 
553     load_palette();
554 
555     if (!gOpenRCT2Headless)
556     {
557         intent = Intent(INTENT_ACTION_CLEAR_TILE_INSPECTOR_CLIPBOARD);
558         context_broadcast_intent(&intent);
559         window_update_all();
560     }
561 
562     OpenRCT2::Audio::StopTitleMusic();
563     gGameSpeed = 1;
564 }
565 
game_load_scripts()566 void game_load_scripts()
567 {
568 #ifdef ENABLE_SCRIPTING
569     GetContext()->GetScriptEngine().LoadPlugins();
570 #endif
571 }
572 
game_unload_scripts()573 void game_unload_scripts()
574 {
575 #ifdef ENABLE_SCRIPTING
576     GetContext()->GetScriptEngine().UnloadPlugins();
577 #endif
578 }
579 
580 /**
581  *
582  *  rct2: 0x0069E9A7
583  * Call after a rotation or loading of a save to reset sprite quadrants
584  */
reset_all_sprite_quadrant_placements()585 void reset_all_sprite_quadrant_placements()
586 {
587     for (size_t i = 0; i < MAX_ENTITIES; i++)
588     {
589         auto* spr = GetEntity(i);
590         if (spr != nullptr && spr->Type != EntityType::Null)
591         {
592             spr->MoveTo(spr->GetLocation());
593         }
594     }
595 }
596 
save_game()597 void save_game()
598 {
599     if (!gFirstTimeSaving)
600     {
601         save_game_with_name(gScenarioSavePath.c_str());
602     }
603     else
604     {
605         save_game_as();
606     }
607 }
608 
save_game_cmd(const utf8 * name)609 void save_game_cmd(const utf8* name /* = nullptr */)
610 {
611     if (name == nullptr)
612     {
613         save_game_with_name(gScenarioSavePath.c_str());
614     }
615     else
616     {
617         char savePath[MAX_PATH];
618         platform_get_user_directory(savePath, "save", sizeof(savePath));
619         safe_strcat_path(savePath, name, sizeof(savePath));
620         path_append_extension(savePath, ".sv6", sizeof(savePath));
621         save_game_with_name(savePath);
622     }
623 }
624 
save_game_with_name(const utf8 * name)625 void save_game_with_name(const utf8* name)
626 {
627     log_verbose("Saving to %s", name);
628     if (scenario_save(name, 0x80000000 | (gConfigGeneral.save_plugin_data ? 1 : 0)))
629     {
630         log_verbose("Saved to %s", name);
631         gCurrentLoadedPath = name;
632         gScreenAge = 0;
633     }
634 }
635 
create_save_game_as_intent()636 void* create_save_game_as_intent()
637 {
638     char name[MAX_PATH];
639     safe_strcpy(name, path_get_filename(gScenarioSavePath.c_str()), MAX_PATH);
640     path_remove_extension(name);
641 
642     Intent* intent = new Intent(WC_LOADSAVE);
643     intent->putExtra(INTENT_EXTRA_LOADSAVE_TYPE, LOADSAVETYPE_SAVE | LOADSAVETYPE_GAME);
644     intent->putExtra(INTENT_EXTRA_PATH, std::string{ name });
645 
646     return intent;
647 }
648 
save_game_as()649 void save_game_as()
650 {
651     auto* intent = static_cast<Intent*>(create_save_game_as_intent());
652     context_open_intent(intent);
653     delete intent;
654 }
655 
limit_autosave_count(const size_t numberOfFilesToKeep,bool processLandscapeFolder)656 static void limit_autosave_count(const size_t numberOfFilesToKeep, bool processLandscapeFolder)
657 {
658     size_t autosavesCount = 0;
659     size_t numAutosavesToDelete = 0;
660 
661     auto environment = GetContext()->GetPlatformEnvironment();
662     auto folderDirectory = environment->GetDirectoryPath(DIRBASE::USER, DIRID::SAVE);
663     char const* fileFilter = "autosave_*.sv6";
664     if (processLandscapeFolder)
665     {
666         folderDirectory = environment->GetDirectoryPath(DIRBASE::USER, DIRID::LANDSCAPE);
667         fileFilter = "autosave_*.sc6";
668     }
669 
670     utf8 filter[MAX_PATH];
671     safe_strcpy(filter, folderDirectory.c_str(), sizeof(filter));
672     safe_strcat_path(filter, "autosave", sizeof(filter));
673     safe_strcat_path(filter, fileFilter, sizeof(filter));
674 
675     // At first, count how many autosaves there are
676     {
677         auto scanner = Path::ScanDirectory(filter, false);
678         while (scanner->Next())
679         {
680             autosavesCount++;
681         }
682     }
683 
684     // If there are fewer autosaves than the number of files to keep we don't need to delete anything
685     if (autosavesCount <= numberOfFilesToKeep)
686     {
687         return;
688     }
689 
690     auto autosaveFiles = std::vector<std::string>(autosavesCount);
691     {
692         auto scanner = Path::ScanDirectory(filter, false);
693         for (size_t i = 0; i < autosavesCount; i++)
694         {
695             autosaveFiles[i].resize(MAX_PATH, 0);
696             if (scanner->Next())
697             {
698                 safe_strcpy(autosaveFiles[i].data(), folderDirectory.c_str(), sizeof(utf8) * MAX_PATH);
699                 safe_strcat_path(autosaveFiles[i].data(), "autosave", sizeof(utf8) * MAX_PATH);
700                 safe_strcat_path(autosaveFiles[i].data(), scanner->GetPathRelative(), sizeof(utf8) * MAX_PATH);
701             }
702         }
703     }
704 
705     std::sort(autosaveFiles.begin(), autosaveFiles.end(), [](const auto& saveFile0, const auto& saveFile1) {
706         return saveFile0.compare(saveFile1) < 0;
707     });
708 
709     // Calculate how many saves we need to delete.
710     numAutosavesToDelete = autosavesCount - numberOfFilesToKeep;
711 
712     for (size_t i = 0; numAutosavesToDelete > 0; i++, numAutosavesToDelete--)
713     {
714         if (!platform_file_delete(autosaveFiles[i].data()))
715         {
716             log_warning("Failed to delete autosave file: %s", autosaveFiles[i].data());
717         }
718     }
719 }
720 
game_autosave()721 void game_autosave()
722 {
723     const char* subDirectory = "save";
724     const char* fileExtension = ".sv6";
725     uint32_t saveFlags = 0x80000000;
726     if (gScreenFlags & SCREEN_FLAGS_EDITOR)
727     {
728         subDirectory = "landscape";
729         fileExtension = ".sc6";
730         saveFlags |= 2;
731     }
732 
733     // Retrieve current time
734     auto currentDate = Platform::GetDateLocal();
735     auto currentTime = Platform::GetTimeLocal();
736 
737     utf8 timeName[44];
738     snprintf(
739         timeName, sizeof(timeName), "autosave_%04u-%02u-%02u_%02u-%02u-%02u%s", currentDate.year, currentDate.month,
740         currentDate.day, currentTime.hour, currentTime.minute, currentTime.second, fileExtension);
741 
742     int32_t autosavesToKeep = gConfigGeneral.autosave_amount;
743     limit_autosave_count(autosavesToKeep - 1, (gScreenFlags & SCREEN_FLAGS_EDITOR));
744 
745     utf8 path[MAX_PATH];
746     utf8 backupPath[MAX_PATH];
747     platform_get_user_directory(path, subDirectory, sizeof(path));
748     safe_strcat_path(path, "autosave", sizeof(path));
749     platform_ensure_directory_exists(path);
750     safe_strcpy(backupPath, path, sizeof(backupPath));
751     safe_strcat_path(path, timeName, sizeof(path));
752     safe_strcat_path(backupPath, "autosave", sizeof(backupPath));
753     safe_strcat(backupPath, fileExtension, sizeof(backupPath));
754     safe_strcat(backupPath, ".bak", sizeof(backupPath));
755 
756     if (Platform::FileExists(path))
757     {
758         platform_file_copy(path, backupPath, true);
759     }
760 
761     if (!scenario_save(path, saveFlags))
762         Console::Error::WriteLine("Could not autosave the scenario. Is the save folder writeable?");
763 }
764 
game_load_or_quit_no_save_prompt_callback(int32_t result,const utf8 * path)765 static void game_load_or_quit_no_save_prompt_callback(int32_t result, const utf8* path)
766 {
767     if (result == MODAL_RESULT_OK)
768     {
769         game_unload_scripts();
770         window_close_by_class(WC_EDITOR_OBJECT_SELECTION);
771         context_load_park_from_file(path);
772         game_load_scripts();
773     }
774 }
775 
776 /**
777  *
778  *  rct2: 0x0066DB79
779  */
game_load_or_quit_no_save_prompt()780 void game_load_or_quit_no_save_prompt()
781 {
782     switch (gSavePromptMode)
783     {
784         case PromptMode::SaveBeforeLoad:
785         {
786             auto loadOrQuitAction = LoadOrQuitAction(LoadOrQuitModes::CloseSavePrompt);
787             GameActions::Execute(&loadOrQuitAction);
788             tool_cancel();
789             if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR)
790             {
791                 load_landscape();
792             }
793             else
794             {
795                 auto intent = Intent(WC_LOADSAVE);
796                 intent.putExtra(INTENT_EXTRA_LOADSAVE_TYPE, LOADSAVETYPE_LOAD | LOADSAVETYPE_GAME);
797                 intent.putExtra(INTENT_EXTRA_CALLBACK, reinterpret_cast<void*>(game_load_or_quit_no_save_prompt_callback));
798                 context_open_intent(&intent);
799             }
800             break;
801         }
802         case PromptMode::SaveBeforeQuit:
803         {
804             auto loadOrQuitAction = LoadOrQuitAction(LoadOrQuitModes::CloseSavePrompt);
805             GameActions::Execute(&loadOrQuitAction);
806             tool_cancel();
807             if (input_test_flag(INPUT_FLAG_5))
808             {
809                 input_set_flag(INPUT_FLAG_5, false);
810             }
811             gGameSpeed = 1;
812             gFirstTimeSaving = true;
813             game_unload_scripts();
814             title_load();
815             break;
816         }
817         default:
818             game_unload_scripts();
819             openrct2_finish();
820             break;
821     }
822 }
823 
start_silent_record()824 void start_silent_record()
825 {
826     std::string name = Path::Combine(
827         OpenRCT2::GetContext()->GetPlatformEnvironment()->GetDirectoryPath(OpenRCT2::DIRBASE::USER), "debug_replay.sv6r");
828     auto* replayManager = OpenRCT2::GetContext()->GetReplayManager();
829     if (replayManager->StartRecording(name, OpenRCT2::k_MaxReplayTicks, OpenRCT2::IReplayManager::RecordType::SILENT))
830     {
831         OpenRCT2::ReplayRecordInfo info;
832         replayManager->GetCurrentReplayInfo(info);
833         safe_strcpy(gSilentRecordingName, info.FilePath.c_str(), MAX_PATH);
834 
835         const char* logFmt = "Silent replay recording started: (%s) %s\n";
836         Console::WriteLine(logFmt, info.Name.c_str(), info.FilePath.c_str());
837     }
838 }
839 
stop_silent_record()840 bool stop_silent_record()
841 {
842     auto* replayManager = OpenRCT2::GetContext()->GetReplayManager();
843     if (!replayManager->IsRecording() && !replayManager->IsNormalising())
844     {
845         return false;
846     }
847 
848     OpenRCT2::ReplayRecordInfo info;
849     replayManager->GetCurrentReplayInfo(info);
850 
851     if (replayManager->StopRecording())
852     {
853         const char* logFmt = "Replay recording stopped: (%s) %s\n"
854                              "  Ticks: %u\n"
855                              "  Commands: %u\n"
856                              "  Checksums: %u";
857 
858         Console::WriteLine(logFmt, info.Name.c_str(), info.FilePath.c_str(), info.Ticks, info.NumCommands, info.NumChecksums);
859 
860         return true;
861     }
862 
863     return false;
864 }
865