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 "S6Exporter.h"
11 
12 #include "../Context.h"
13 #include "../Editor.h"
14 #include "../Game.h"
15 #include "../GameState.h"
16 #include "../OpenRCT2.h"
17 #include "../common.h"
18 #include "../config/Config.h"
19 #include "../core/FileStream.h"
20 #include "../core/IStream.hpp"
21 #include "../core/Numerics.hpp"
22 #include "../core/String.hpp"
23 #include "../interface/Viewport.h"
24 #include "../interface/Window.h"
25 #include "../localisation/Date.h"
26 #include "../localisation/Localisation.h"
27 #include "../management/Award.h"
28 #include "../management/Finance.h"
29 #include "../management/Marketing.h"
30 #include "../management/NewsItem.h"
31 #include "../management/Research.h"
32 #include "../object/Object.h"
33 #include "../object/ObjectLimits.h"
34 #include "../object/ObjectManager.h"
35 #include "../object/ObjectRepository.h"
36 #include "../peep/Guest.h"
37 #include "../peep/RideUseSystem.h"
38 #include "../peep/Staff.h"
39 #include "../rct12/SawyerChunkWriter.h"
40 #include "../ride/Ride.h"
41 #include "../ride/RideData.h"
42 #include "../ride/RideRatings.h"
43 #include "../ride/ShopItem.h"
44 #include "../ride/Station.h"
45 #include "../ride/TrackData.h"
46 #include "../ride/Vehicle.h"
47 #include "../scenario/Scenario.h"
48 #include "../util/SawyerCoding.h"
49 #include "../util/Util.h"
50 #include "../world/Balloon.h"
51 #include "../world/Climate.h"
52 #include "../world/Duck.h"
53 #include "../world/EntityList.h"
54 #include "../world/Fountain.h"
55 #include "../world/Litter.h"
56 #include "../world/MapAnimation.h"
57 #include "../world/MoneyEffect.h"
58 #include "../world/Park.h"
59 #include "../world/Particle.h"
60 #include "../world/Sprite.h"
61 
62 #include <algorithm>
63 #include <cstring>
64 #include <functional>
65 #include <iterator>
66 #include <optional>
67 
68 #define ENCRYPT_MONEY(money) (static_cast<money32>(Numerics::ror32((money), 13) ^ 0xF4EC9621))
69 
S6Exporter()70 S6Exporter::S6Exporter()
71 {
72     RemoveTracklessRides = false;
73     std::memset(&_s6, 0x00, sizeof(_s6));
74 }
75 
SaveGame(const utf8 * path)76 void S6Exporter::SaveGame(const utf8* path)
77 {
78     auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_WRITE);
79     SaveGame(&fs);
80 }
81 
SaveGame(OpenRCT2::IStream * stream)82 void S6Exporter::SaveGame(OpenRCT2::IStream* stream)
83 {
84     Save(stream, false);
85 }
86 
SaveScenario(const utf8 * path)87 void S6Exporter::SaveScenario(const utf8* path)
88 {
89     auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_WRITE);
90     SaveScenario(&fs);
91 }
92 
SaveScenario(OpenRCT2::IStream * stream)93 void S6Exporter::SaveScenario(OpenRCT2::IStream* stream)
94 {
95     Save(stream, true);
96 }
97 
Save(OpenRCT2::IStream * stream,bool isScenario)98 void S6Exporter::Save(OpenRCT2::IStream* stream, bool isScenario)
99 {
100     _s6.header.type = isScenario ? S6_TYPE_SCENARIO : S6_TYPE_SAVEDGAME;
101     _s6.header.classic_flag = 0;
102     _s6.header.num_packed_objects = uint16_t(ExportObjectsList.size());
103     _s6.header.version = S6_RCT2_VERSION;
104     _s6.header.magic_number = S6_MAGIC_NUMBER;
105     _s6.game_version_number = 201028;
106 
107     auto chunkWriter = SawyerChunkWriter(stream);
108 
109     // 0: Write header chunk
110     chunkWriter.WriteChunk(&_s6.header, SAWYER_ENCODING::ROTATE);
111 
112     // 1: Write scenario info chunk
113     if (_s6.header.type == S6_TYPE_SCENARIO)
114     {
115         chunkWriter.WriteChunk(&_s6.info, SAWYER_ENCODING::ROTATE);
116     }
117 
118     // 2: Write packed objects
119     if (_s6.header.num_packed_objects > 0)
120     {
121         auto& objRepo = OpenRCT2::GetContext()->GetObjectRepository();
122         objRepo.WritePackedObjects(stream, ExportObjectsList);
123     }
124 
125     // 3: Write available objects chunk
126     chunkWriter.WriteChunk(_s6.Objects, sizeof(_s6.Objects), SAWYER_ENCODING::ROTATE);
127 
128     // 4: Misc fields (data, rand...) chunk
129     chunkWriter.WriteChunk(&_s6.elapsed_months, 16, SAWYER_ENCODING::RLECOMPRESSED);
130 
131     // 5: Map elements + sprites and other fields chunk
132     chunkWriter.WriteChunk(&_s6.tile_elements, 0x180000, SAWYER_ENCODING::RLECOMPRESSED);
133 
134     if (_s6.header.type == S6_TYPE_SCENARIO)
135     {
136         // 6 to 13:
137         chunkWriter.WriteChunk(&_s6.next_free_tile_element_pointer_index, 0x27104C, SAWYER_ENCODING::RLECOMPRESSED);
138         chunkWriter.WriteChunk(&_s6.guests_in_park, 4, SAWYER_ENCODING::RLECOMPRESSED);
139         chunkWriter.WriteChunk(&_s6.last_guests_in_park, 8, SAWYER_ENCODING::RLECOMPRESSED);
140         chunkWriter.WriteChunk(&_s6.park_rating, 2, SAWYER_ENCODING::RLECOMPRESSED);
141         chunkWriter.WriteChunk(&_s6.active_research_types, 1082, SAWYER_ENCODING::RLECOMPRESSED);
142         chunkWriter.WriteChunk(&_s6.current_expenditure, 16, SAWYER_ENCODING::RLECOMPRESSED);
143         chunkWriter.WriteChunk(&_s6.park_value, 4, SAWYER_ENCODING::RLECOMPRESSED);
144         chunkWriter.WriteChunk(&_s6.completed_company_value, 0x761E8, SAWYER_ENCODING::RLECOMPRESSED);
145     }
146     else
147     {
148         // 6: Everything else...
149         chunkWriter.WriteChunk(&_s6.next_free_tile_element_pointer_index, 0x2E8570, SAWYER_ENCODING::RLECOMPRESSED);
150     }
151 
152     // Determine number of bytes written
153     size_t fileSize = stream->GetLength();
154 
155     // Read all written bytes back into a single buffer
156     stream->SetPosition(0);
157     auto data = stream->ReadArray<uint8_t>(fileSize);
158     uint32_t checksum = sawyercoding_calculate_checksum(data.get(), fileSize);
159 
160     // Write the checksum on the end
161     stream->SetPosition(fileSize);
162     stream->WriteValue(checksum);
163 }
164 
ride_all_has_any_track_elements(std::array<bool,RCT12_MAX_RIDES_IN_PARK> & rideIndexArray)165 static void ride_all_has_any_track_elements(std::array<bool, RCT12_MAX_RIDES_IN_PARK>& rideIndexArray)
166 {
167     tile_element_iterator it;
168     tile_element_iterator_begin(&it);
169     while (tile_element_iterator_next(&it))
170     {
171         if (it.element->GetType() != TILE_ELEMENT_TYPE_TRACK)
172             continue;
173         if (it.element->IsGhost())
174             continue;
175 
176         rideIndexArray[EnumValue(it.element->AsTrack()->GetRideIndex())] = true;
177     }
178 }
179 
scenario_remove_trackless_rides(rct_s6_data * s6)180 static void scenario_remove_trackless_rides(rct_s6_data* s6)
181 {
182     std::array<bool, RCT12_MAX_RIDES_IN_PARK> rideHasTrack{};
183     ride_all_has_any_track_elements(rideHasTrack);
184     for (int32_t i = 0; i < RCT12_MAX_RIDES_IN_PARK; i++)
185     {
186         auto ride = &s6->rides[i];
187         if (rideHasTrack[i] || ride->type == RIDE_TYPE_NULL)
188         {
189             continue;
190         }
191 
192         ride->type = RIDE_TYPE_NULL;
193         if (is_user_string_id(ride->name))
194         {
195             s6->custom_strings[(ride->name % RCT12_MAX_USER_STRINGS)][0] = 0;
196         }
197     }
198 }
199 
200 /**
201  * Modifies the given S6 data so that ghost elements, rides with no track elements or unused banners / user strings are saved.
202  */
scenario_fix_ghosts(rct_s6_data * s6)203 static void scenario_fix_ghosts(rct_s6_data* s6)
204 {
205     // Build tile pointer cache (needed to get the first element at a certain location)
206     RCT12TileElement* tilePointers[MAX_TILE_TILE_ELEMENT_POINTERS];
207     for (size_t i = 0; i < MAX_TILE_TILE_ELEMENT_POINTERS; i++)
208     {
209         tilePointers[i] = TILE_UNDEFINED_TILE_ELEMENT;
210     }
211 
212     RCT12TileElement* tileElement = s6->tile_elements;
213     RCT12TileElement** tile = tilePointers;
214     for (size_t y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; y++)
215     {
216         for (size_t x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; x++)
217         {
218             *tile++ = tileElement;
219             while (!(tileElement++)->IsLastForTile())
220                 ;
221         }
222     }
223 
224     // Remove all ghost elements
225     RCT12TileElement* destinationElement = s6->tile_elements;
226 
227     for (int32_t y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; y++)
228     {
229         for (int32_t x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; x++)
230         {
231             // This is the equivalent of map_get_first_element_at(x, y), but on S6 data.
232             RCT12TileElement* originalElement = tilePointers[x + y * MAXIMUM_MAP_SIZE_TECHNICAL];
233             do
234             {
235                 if (originalElement->IsGhost())
236                 {
237                     uint8_t bannerIndex = originalElement->GetBannerIndex();
238                     if (bannerIndex != RCT12_BANNER_INDEX_NULL)
239                     {
240                         auto banner = &s6->banners[bannerIndex];
241                         if (banner->type != RCT12_OBJECT_ENTRY_INDEX_NULL)
242                         {
243                             banner->type = RCT12_OBJECT_ENTRY_INDEX_NULL;
244                             if (is_user_string_id(banner->string_idx))
245                                 s6->custom_strings[(banner->string_idx % RCT12_MAX_USER_STRINGS)][0] = 0;
246                         }
247                     }
248                 }
249                 else
250                 {
251                     *destinationElement++ = *originalElement;
252                 }
253             } while (!(originalElement++)->IsLastForTile());
254 
255             // Set last element flag in case the original last element was never added
256             (destinationElement - 1)->flags |= TILE_ELEMENT_FLAG_LAST_TILE;
257         }
258     }
259 }
260 
261 template<ObjectType TObjectType, size_t TMaxEntries, typename T>
ExportObjectList(IObjectManager & objMgr,T & objects)262 static void ExportObjectList(IObjectManager& objMgr, T& objects)
263 {
264     for (size_t i = 0; i < TMaxEntries; i++)
265     {
266         auto& dst = objects[i];
267 
268         const auto* object = objMgr.GetLoadedObject(TObjectType, i);
269         if (object == nullptr)
270         {
271             // The sv6 format expects null/invalid entries to be filled with 0xFF.
272             std::memset(&dst, 0xFF, sizeof(dst));
273         }
274         else
275         {
276             dst = object->GetObjectEntry();
277         }
278     }
279 }
280 
Export()281 void S6Exporter::Export()
282 {
283     _s6.info = {};
284     _s6.info.editor_step = gEditorStep;
285     _s6.info.category = gScenarioCategory;
286     _s6.info.objective_type = gScenarioObjective.Type;
287     _s6.info.objective_arg_1 = gScenarioObjective.Year;
288     _s6.info.objective_arg_2 = gScenarioObjective.Currency;
289     _s6.info.objective_arg_3 = gScenarioObjective.NumGuests;
290     _s6.info.entry.flags = 0xFFFFFFFF;
291 
292     {
293         auto temp = utf8_to_rct2(gScenarioName.c_str());
294         safe_strcpy(_s6.info.name, temp.data(), sizeof(_s6.info.name));
295     }
296     {
297         auto temp = utf8_to_rct2(gScenarioDetails.c_str());
298         safe_strcpy(_s6.info.details, temp.data(), sizeof(_s6.info.details));
299     }
300 
301     uint32_t researchedTrackPiecesA[128] = {};
302     uint32_t researchedTrackPiecesB[128] = {};
303 
304     auto& objectMgr = OpenRCT2::GetContext()->GetObjectManager();
305     ExportObjectList<ObjectType::Ride, RCT12_MAX_RIDE_OBJECTS>(objectMgr, _s6.RideObjects);
306     ExportObjectList<ObjectType::SmallScenery, RCT2_MAX_SMALL_SCENERY_OBJECTS>(objectMgr, _s6.SceneryObjects);
307     ExportObjectList<ObjectType::LargeScenery, RCT2_MAX_LARGE_SCENERY_OBJECTS>(objectMgr, _s6.LargeSceneryObjects);
308     ExportObjectList<ObjectType::Walls, RCT2_MAX_WALL_SCENERY_OBJECTS>(objectMgr, _s6.WallSceneryObjects);
309     ExportObjectList<ObjectType::Banners, RCT2_MAX_BANNER_OBJECTS>(objectMgr, _s6.BannerObjects);
310     ExportObjectList<ObjectType::Paths, RCT2_MAX_PATH_OBJECTS>(objectMgr, _s6.PathObjects);
311     ExportObjectList<ObjectType::PathBits, RCT2_MAX_PATH_ADDITION_OBJECTS>(objectMgr, _s6.PathAdditionObjects);
312     ExportObjectList<ObjectType::SceneryGroup, RCT2_MAX_SCENERY_GROUP_OBJECTS>(objectMgr, _s6.SceneryGroupObjects);
313     ExportObjectList<ObjectType::ParkEntrance, RCT2_MAX_PARK_ENTRANCE_OBJECTS>(objectMgr, _s6.ParkEntranceObjects);
314     ExportObjectList<ObjectType::Water, RCT2_MAX_WATER_OBJECTS>(objectMgr, _s6.WaterObjects);
315     ExportObjectList<ObjectType::ScenarioText, RCT2_MAX_SCENARIO_TEXT_OBJECTS>(objectMgr, _s6.ScenarioTextObjects);
316 
317     _s6.elapsed_months = static_cast<uint16_t>(gDateMonthsElapsed);
318     _s6.current_day = gDateMonthTicks;
319     _s6.scenario_ticks = gCurrentTicks;
320 
321     auto state = scenario_rand_state();
322     _s6.scenario_srand_0 = state.s0;
323     _s6.scenario_srand_1 = state.s1;
324 
325     // Map elements must be reorganised prior to saving otherwise save may be invalid
326     ReorganiseTileElements();
327     ExportTileElements();
328     ExportEntities();
329     ExportParkName();
330 
331     _s6.initial_cash = ToMoney32(gInitialCash);
332     _s6.current_loan = ToMoney32(gBankLoan);
333     _s6.park_flags = gParkFlags;
334     _s6.park_entrance_fee = gParkEntranceFee;
335     // rct1_park_entrance_x
336     // rct1_park_entrance_y
337     // pad_013573EE
338     // rct1_park_entrance_z
339     ExportPeepSpawns();
340     _s6.guest_count_change_modifier = gGuestChangeModifier;
341     _s6.current_research_level = gResearchFundingLevel;
342     // pad_01357400
343     ExportResearchedRideTypes();
344     ExportResearchedRideEntries();
345     // Not used by OpenRCT2 any more, but left in to keep RCT2 export working.
346     for (uint8_t i = 0; i < std::size(RideTypeDescriptors); i++)
347     {
348         researchedTrackPiecesA[i] = (GetRideTypeDescriptor(i).EnabledTrackPieces) & 0xFFFFFFFFULL;
349         researchedTrackPiecesB[i] = (GetRideTypeDescriptor(i).EnabledTrackPieces >> 32ULL) & 0xFFFFFFFFULL;
350     }
351     std::memcpy(_s6.researched_track_types_a, researchedTrackPiecesA, sizeof(_s6.researched_track_types_a));
352     std::memcpy(_s6.researched_track_types_b, researchedTrackPiecesB, sizeof(_s6.researched_track_types_b));
353 
354     _s6.guests_in_park = gNumGuestsInPark;
355     _s6.guests_heading_for_park = gNumGuestsHeadingForPark;
356 
357     for (size_t i = 0; i < RCT12_EXPENDITURE_TABLE_MONTH_COUNT; i++)
358     {
359         for (size_t j = 0; j < RCT12_EXPENDITURE_TYPE_COUNT; j++)
360         {
361             _s6.expenditure_table[i][j] = ToMoney32(gExpenditureTable[i][j]);
362         }
363     }
364 
365     _s6.last_guests_in_park = gNumGuestsInParkLastWeek;
366     // pad_01357BCA
367     _s6.handyman_colour = gStaffHandymanColour;
368     _s6.mechanic_colour = gStaffMechanicColour;
369     _s6.security_colour = gStaffSecurityColour;
370 
371     ExportResearchedSceneryItems();
372 
373     _s6.park_rating = gParkRating;
374 
375     std::copy(std::begin(gParkRatingHistory), std::end(gParkRatingHistory), _s6.park_rating_history);
376     std::fill(std::begin(_s6.guests_in_park_history), std::end(_s6.guests_in_park_history), RCT12ParkHistoryUndefined);
377     for (size_t i = 0; i < std::size(gGuestsInParkHistory); i++)
378     {
379         if (gGuestsInParkHistory[i] != GuestsInParkHistoryUndefined)
380         {
381             _s6.guests_in_park_history[i] = std::min<uint16_t>(gGuestsInParkHistory[i], 5000) / RCT12GuestsInParkHistoryFactor;
382         }
383     }
384 
385     std::memcpy(_s6.guests_in_park_history, gGuestsInParkHistory, sizeof(_s6.guests_in_park_history));
386 
387     _s6.active_research_types = gResearchPriorities;
388     _s6.research_progress_stage = gResearchProgressStage;
389     if (gResearchLastItem.has_value())
390         _s6.last_researched_item_subject = gResearchLastItem->ToRCT12ResearchItem().rawValue;
391     else
392         _s6.last_researched_item_subject = RCT12_RESEARCHED_ITEMS_SEPARATOR;
393     // pad_01357CF8
394     _s6.research_progress = gResearchProgress;
395 
396     if (gResearchNextItem.has_value())
397     {
398         auto RCT2ResearchItem = gResearchNextItem->ToRCT12ResearchItem();
399         _s6.next_research_item = RCT2ResearchItem.rawValue;
400         _s6.next_research_category = RCT2ResearchItem.category;
401     }
402     else
403     {
404         _s6.next_research_item = RCT12_RESEARCHED_ITEMS_SEPARATOR;
405         _s6.next_research_category = 0;
406     }
407 
408     _s6.next_research_expected_day = gResearchExpectedDay;
409     _s6.next_research_expected_month = gResearchExpectedMonth;
410     _s6.guest_initial_happiness = gGuestInitialHappiness;
411     _s6.park_size = gParkSize;
412     _s6.guest_generation_probability = _guestGenerationProbability;
413     _s6.total_ride_value_for_money = gTotalRideValueForMoney;
414     _s6.maximum_loan = ToMoney32(gMaxBankLoan);
415     _s6.guest_initial_cash = gGuestInitialCash;
416     _s6.guest_initial_hunger = gGuestInitialHunger;
417     _s6.guest_initial_thirst = gGuestInitialThirst;
418     _s6.objective_type = gScenarioObjective.Type;
419     _s6.objective_year = gScenarioObjective.Year;
420     // pad_013580FA
421     _s6.objective_currency = gScenarioObjective.Currency;
422     // In RCT2, the ride string IDs start at index STR_0002 and are directly mappable.
423     // This is not always the case in OpenRCT2, so we use the actual ride ID.
424     if (gScenarioObjective.Type == OBJECTIVE_BUILD_THE_BEST)
425         _s6.objective_guests = gScenarioObjective.RideId + RCT2_RIDE_STRING_START;
426     else
427         _s6.objective_guests = gScenarioObjective.NumGuests;
428 
429     ExportMarketingCampaigns();
430 
431     _s6.current_expenditure = ToMoney32(gCurrentExpenditure);
432     _s6.current_profit = ToMoney32(gCurrentProfit);
433     _s6.weekly_profit_average_dividend = ToMoney32(gWeeklyProfitAverageDividend);
434     _s6.weekly_profit_average_divisor = gWeeklyProfitAverageDivisor;
435     // pad_0135833A
436 
437     _s6.park_value = ToMoney32(gParkValue);
438 
439     for (size_t i = 0; i < RCT12_FINANCE_GRAPH_SIZE; i++)
440     {
441         _s6.balance_history[i] = ToMoney32(gCashHistory[i]);
442         _s6.weekly_profit_history[i] = ToMoney32(gWeeklyProfitHistory[i]);
443         _s6.park_value_history[i] = ToMoney32(gParkValueHistory[i]);
444     }
445 
446     _s6.completed_company_value = OpenRCT2CompletedCompanyValueToRCT12(gScenarioCompletedCompanyValue);
447     _s6.total_admissions = gTotalAdmissions;
448     _s6.income_from_admissions = ToMoney32(gTotalIncomeFromAdmissions);
449     _s6.company_value = ToMoney32(gCompanyValue);
450     std::memcpy(_s6.peep_warning_throttle, gPeepWarningThrottle, sizeof(_s6.peep_warning_throttle));
451 
452     // Awards
453     for (int32_t i = 0; i < RCT12_MAX_AWARDS; i++)
454     {
455         Award* src = &gCurrentAwards[i];
456         rct12_award* dst = &_s6.awards[i];
457         dst->time = src->Time;
458         dst->type = src->Type;
459     }
460 
461     _s6.land_price = gLandPrice;
462     _s6.construction_rights_price = gConstructionRightsPrice;
463     // unk_01358774
464     // pad_01358776
465     // _s6.cd_key
466     // _s6.game_version_number
467     _s6.completed_company_value_record = gScenarioCompanyValueRecord;
468     _s6.loan_hash = GetLoanHash(gInitialCash, gBankLoan, gMaxBankLoan);
469     _s6.ride_count = ride_get_count();
470     // pad_013587CA
471     _s6.historical_profit = ToMoney32(gHistoricalProfit);
472     // pad_013587D4
473     String::Set(_s6.scenario_completed_name, sizeof(_s6.scenario_completed_name), gScenarioCompletedBy.c_str());
474     _s6.cash = ENCRYPT_MONEY(ToMoney32(gCash));
475     // pad_013587FC
476     _s6.park_rating_casualty_penalty = gParkRatingCasualtyPenalty;
477     _s6.map_size_units = GetMapSizeUnits();
478     _s6.map_size_minus_2 = GetMapSizeMinus2();
479     _s6.map_size = gMapSize;
480     _s6.map_max_xy = GetMapSizeMaxXY();
481     _s6.same_price_throughout = gSamePriceThroughoutPark & 0xFFFFFFFF;
482     _s6.suggested_max_guests = _suggestedGuestMaximum;
483     _s6.park_rating_warning_days = gScenarioParkRatingWarningDays;
484     _s6.last_entrance_style = gLastEntranceStyle;
485     // rct1_water_colour
486     // pad_01358842
487     ExportResearchList();
488     _s6.map_base_z = gMapBaseZ;
489     String::Set(_s6.scenario_name, sizeof(_s6.scenario_name), gScenarioName.c_str());
490     String::Set(_s6.scenario_description, sizeof(_s6.scenario_description), gScenarioDetails.c_str());
491     _s6.current_interest_rate = gBankLoanInterestRate;
492     // pad_0135934B
493     _s6.same_price_throughout_extended = gSamePriceThroughoutPark >> 32;
494     // Preserve compatibility with vanilla RCT2's save format.
495     for (uint8_t i = 0; i < RCT12_MAX_PARK_ENTRANCES; i++)
496     {
497         CoordsXYZD entrance = { LOCATION_NULL, LOCATION_NULL, 0, 0 };
498         if (gParkEntrances.size() > i)
499         {
500             entrance = gParkEntrances[i];
501         }
502         _s6.park_entrance_x[i] = entrance.x;
503         _s6.park_entrance_y[i] = entrance.y;
504         _s6.park_entrance_z[i] = entrance.z;
505         _s6.park_entrance_direction[i] = entrance.direction;
506     }
507     safe_strcpy(_s6.scenario_filename, gScenarioFileName, sizeof(_s6.scenario_filename));
508     std::memcpy(_s6.saved_expansion_pack_names, gScenarioExpansionPacks, sizeof(_s6.saved_expansion_pack_names));
509     ExportBanners();
510     _s6.game_ticks_1 = gCurrentTicks;
511 
512     this->ExportRides();
513 
514     _s6.saved_age = gSavedAge;
515     _s6.saved_view_x = gSavedView.x;
516     _s6.saved_view_y = gSavedView.y;
517     _s6.saved_view_zoom = static_cast<int8_t>(std::clamp<ZoomLevel>(gSavedViewZoom, 0, 3));
518     _s6.saved_view_rotation = gSavedViewRotation;
519 
520     ExportMapAnimations();
521 
522     ExportRideRatingsCalcData();
523     ExportRideMeasurements();
524     _s6.next_guest_index = gNextGuestNumber;
525     _s6.grass_and_scenery_tilepos = gGrassSceneryTileLoopPosition;
526     ExportStaffPatrolAreas();
527     // unk_13CA73E
528     // pad_13CA73F
529     // unk_13CA740
530     _s6.climate = static_cast<uint8_t>(gClimate);
531     // pad_13CA741;
532     // byte_13CA742
533     // pad_013CA747
534     _s6.climate_update_timer = gClimateUpdateTimer;
535     _s6.current_weather = EnumValue(gClimateCurrent.Weather);
536     _s6.next_weather = EnumValue(gClimateNext.Weather);
537     _s6.temperature = gClimateCurrent.Temperature;
538     _s6.next_temperature = gClimateNext.Temperature;
539     _s6.current_weather_effect = static_cast<uint8_t>(gClimateCurrent.WeatherEffect);
540     _s6.next_weather_effect = static_cast<uint8_t>(gClimateNext.WeatherEffect);
541     _s6.current_weather_gloom = gClimateCurrent.WeatherGloom;
542     _s6.next_weather_gloom = gClimateNext.WeatherGloom;
543     _s6.current_weather_level = static_cast<uint8_t>(gClimateCurrent.Level);
544     _s6.next_weather_level = static_cast<uint8_t>(gClimateNext.Level);
545 
546     // News items
547     for (size_t i = 0; i < RCT12_MAX_NEWS_ITEMS; i++)
548     {
549         const News::Item* src = &gNewsItems[i];
550         rct12_news_item* dst = &_s6.news_items[i];
551 
552         dst->Type = static_cast<uint8_t>(src->Type);
553         dst->Flags = src->Flags;
554         dst->Assoc = src->Assoc;
555         dst->Ticks = src->Ticks;
556         dst->MonthYear = src->MonthYear;
557         dst->Day = src->Day;
558 
559         auto rct2text = ConvertFormattedStringToRCT2(src->Text, sizeof(dst->Text));
560         std::memcpy(dst->Text, rct2text.c_str(), std::min(sizeof(dst->Text), rct2text.size()));
561     }
562 
563     // pad_13CE730
564     // rct1_scenario_flags
565     _s6.wide_path_tile_loop_x = gWidePathTileLoopPosition.x;
566     _s6.wide_path_tile_loop_y = gWidePathTileLoopPosition.y;
567     // pad_13CE778
568 
569     String::Set(_s6.scenario_filename, sizeof(_s6.scenario_filename), gScenarioFileName);
570 
571     if (RemoveTracklessRides)
572     {
573         scenario_remove_trackless_rides(&_s6);
574     }
575 
576     scenario_fix_ghosts(&_s6);
577     game_convert_strings_to_rct2(&_s6);
578 
579     ExportUserStrings();
580 }
581 
ExportPeepSpawns()582 void S6Exporter::ExportPeepSpawns()
583 {
584     for (size_t i = 0; i < RCT12_MAX_PEEP_SPAWNS; i++)
585     {
586         if (gPeepSpawns.size() > i)
587         {
588             _s6.peep_spawns[i] = { static_cast<uint16_t>(gPeepSpawns[i].x), static_cast<uint16_t>(gPeepSpawns[i].y),
589                                    static_cast<uint8_t>(gPeepSpawns[i].z / 16), gPeepSpawns[i].direction };
590         }
591         else
592         {
593             _s6.peep_spawns[i] = { RCT12_PEEP_SPAWN_UNDEFINED, RCT12_PEEP_SPAWN_UNDEFINED, 0, 0 };
594         }
595     }
596 }
597 
GetLoanHash(money32 initialCash,money32 bankLoan,uint32_t maxBankLoan)598 uint32_t S6Exporter::GetLoanHash(money32 initialCash, money32 bankLoan, uint32_t maxBankLoan)
599 {
600     int32_t value = 0x70093A;
601     value -= initialCash;
602     value = Numerics::ror32(value, 5);
603     value -= bankLoan;
604     value = Numerics::ror32(value, 7);
605     value += maxBankLoan;
606     value = Numerics::ror32(value, 3);
607     return value;
608 }
609 
ExportParkName()610 void S6Exporter::ExportParkName()
611 {
612     auto& park = OpenRCT2::GetContext()->GetGameState()->GetPark();
613     auto stringId = AllocateUserString(park.Name);
614     if (stringId.has_value())
615     {
616         _s6.park_name = stringId.value();
617         _s6.park_name_args = 0;
618     }
619     else
620     {
621         log_warning("Unable to allocate user string for park name during S6 export.");
622         _s6.park_name = STR_UNNAMED_PARK;
623         _s6.park_name_args = 0;
624     }
625 }
626 
ExportRides()627 void S6Exporter::ExportRides()
628 {
629     const Ride nullRide{};
630     for (int32_t index = 0; index < RCT12_MAX_RIDES_IN_PARK; index++)
631     {
632         const auto* src = get_ride(static_cast<ride_id_t>(index));
633         if (src == nullptr)
634         {
635             src = &nullRide;
636         }
637         auto dst = &_s6.rides[index];
638         *dst = {};
639         if (src->type == RIDE_TYPE_NULL)
640         {
641             dst->type = RIDE_TYPE_NULL;
642         }
643         else
644         {
645             ExportRide(dst, src);
646         }
647     }
648 }
649 
ExportRide(rct2_ride * dst,const Ride * src)650 void S6Exporter::ExportRide(rct2_ride* dst, const Ride* src)
651 {
652     std::memset(dst, 0, sizeof(rct2_ride));
653 
654     dst->type = OpenRCT2RideTypeToRCT2RideType(src->type);
655     dst->subtype = OpenRCT2EntryIndexToRCTEntryIndex(src->subtype);
656     // pad_002;
657     dst->mode = static_cast<uint8_t>(src->mode);
658     dst->colour_scheme_type = src->colour_scheme_type;
659 
660     for (uint8_t i = 0; i < RCT2_MAX_CARS_PER_TRAIN; i++)
661     {
662         dst->vehicle_colours[i].body_colour = src->vehicle_colours[i].Body;
663         dst->vehicle_colours[i].trim_colour = src->vehicle_colours[i].Trim;
664     }
665 
666     // pad_046;
667     dst->status = EnumValue(src->status);
668 
669     bool useDefaultName = true;
670     if (!src->custom_name.empty())
671     {
672         // Custom name, allocate user string for ride
673         auto stringId = AllocateUserString(src->custom_name);
674         if (stringId.has_value())
675         {
676             dst->name = stringId.value();
677             dst->name_arguments = 0;
678             useDefaultName = false;
679         }
680         else
681         {
682             log_warning(
683                 "Unable to allocate user string for ride #%d (%s).", static_cast<int>(src->id), src->custom_name.c_str());
684         }
685     }
686     if (useDefaultName)
687     {
688         // Default name with number
689         dst->name = GetRideTypeDescriptor(src->type).Naming.Name;
690         dst->name_arguments_number = src->default_name_number;
691     }
692 
693     if (src->overall_view.IsNull())
694     {
695         dst->overall_view.SetNull();
696     }
697     else
698     {
699         auto tileLoc = TileCoordsXY(src->overall_view);
700         dst->overall_view = { static_cast<uint8_t>(tileLoc.x), static_cast<uint8_t>(tileLoc.y) };
701     }
702 
703     for (int32_t i = 0; i < RCT12_MAX_STATIONS_PER_RIDE; i++)
704     {
705         if (src->stations[i].Start.IsNull())
706         {
707             dst->station_starts[i].SetNull();
708         }
709         else
710         {
711             auto tileStartLoc = TileCoordsXY(src->stations[i].Start);
712             dst->station_starts[i] = { static_cast<uint8_t>(tileStartLoc.x), static_cast<uint8_t>(tileStartLoc.y) };
713         }
714         dst->station_heights[i] = src->stations[i].Height;
715         dst->station_length[i] = src->stations[i].Length;
716         dst->station_depart[i] = src->stations[i].Depart;
717         dst->train_at_station[i] = src->stations[i].TrainAtStation;
718 
719         TileCoordsXYZD entrance = ride_get_entrance_location(src, i);
720         if (entrance.IsNull())
721             dst->entrances[i].SetNull();
722         else
723             dst->entrances[i] = { static_cast<uint8_t>(entrance.x), static_cast<uint8_t>(entrance.y) };
724 
725         TileCoordsXYZD exit = ride_get_exit_location(src, i);
726         if (exit.IsNull())
727             dst->exits[i].SetNull();
728         else
729             dst->exits[i] = { static_cast<uint8_t>(exit.x), static_cast<uint8_t>(exit.y) };
730 
731         dst->last_peep_in_queue[i] = src->stations[i].LastPeepInQueue;
732 
733         dst->length[i] = src->stations[i].SegmentLength;
734         dst->time[i] = src->stations[i].SegmentTime;
735 
736         dst->queue_time[i] = src->stations[i].QueueTime;
737 
738         dst->queue_length[i] = src->stations[i].QueueLength;
739     }
740 
741     for (uint8_t i = 0; i <= RCT2_MAX_VEHICLES_PER_RIDE; i++)
742     {
743         dst->vehicles[i] = src->vehicles[i];
744     }
745 
746     dst->depart_flags = src->depart_flags;
747 
748     dst->num_stations = src->num_stations;
749     dst->num_vehicles = src->num_vehicles;
750     dst->num_cars_per_train = src->num_cars_per_train;
751     dst->proposed_num_vehicles = src->proposed_num_vehicles;
752     dst->proposed_num_cars_per_train = src->proposed_num_cars_per_train;
753     dst->max_trains = src->max_trains;
754     dst->SetMinCarsPerTrain(src->MinCarsPerTrain);
755     dst->SetMaxCarsPerTrain(src->MaxCarsPerTrain);
756     dst->min_waiting_time = src->min_waiting_time;
757     dst->max_waiting_time = src->max_waiting_time;
758 
759     // Includes time_limit, num_laps, launch_speed, speed, rotations
760     dst->operation_option = src->operation_option;
761 
762     dst->boat_hire_return_direction = src->boat_hire_return_direction;
763     dst->boat_hire_return_position = { static_cast<uint8_t>(src->boat_hire_return_position.x),
764                                        static_cast<uint8_t>(src->boat_hire_return_position.y) };
765 
766     dst->special_track_elements = src->special_track_elements;
767     // pad_0D6[2];
768 
769     dst->max_speed = src->max_speed;
770     dst->average_speed = src->average_speed;
771     dst->current_test_segment = src->current_test_segment;
772     dst->average_speed_test_timeout = src->average_speed_test_timeout;
773     // pad_0E2[0x2];
774 
775     dst->max_positive_vertical_g = src->max_positive_vertical_g;
776     dst->max_negative_vertical_g = src->max_negative_vertical_g;
777     dst->max_lateral_g = src->max_lateral_g;
778     dst->previous_vertical_g = src->previous_vertical_g;
779     dst->previous_lateral_g = src->previous_lateral_g;
780     // pad_106[0x2];
781     dst->testing_flags = src->testing_flags;
782 
783     if (src->CurTestTrackLocation.IsNull())
784     {
785         dst->cur_test_track_location.SetNull();
786     }
787     else
788     {
789         dst->cur_test_track_location = { static_cast<uint8_t>(src->CurTestTrackLocation.x),
790                                          static_cast<uint8_t>(src->CurTestTrackLocation.y) };
791         dst->cur_test_track_z = static_cast<uint8_t>(src->CurTestTrackLocation.z);
792     }
793 
794     dst->turn_count_default = src->turn_count_default;
795     dst->turn_count_banked = src->turn_count_banked;
796     dst->turn_count_sloped = src->turn_count_sloped;
797     if (dst->type == RIDE_TYPE_MINI_GOLF)
798         dst->inversions = static_cast<uint8_t>(std::min(src->holes, RCT12_MAX_GOLF_HOLES));
799     else
800         dst->inversions = static_cast<uint8_t>(std::min(src->inversions, RCT12_MAX_INVERSIONS));
801     dst->inversions |= (src->sheltered_eighths << 5);
802     dst->drops = src->drops;
803     dst->start_drop_height = src->start_drop_height;
804     dst->highest_drop_height = src->highest_drop_height;
805     dst->sheltered_length = src->sheltered_length;
806     dst->var_11C = src->var_11C;
807     dst->num_sheltered_sections = src->num_sheltered_sections;
808 
809     dst->cur_num_customers = src->cur_num_customers;
810     dst->num_customers_timeout = src->num_customers_timeout;
811 
812     for (uint8_t i = 0; i < RCT2_CUSTOMER_HISTORY_SIZE; i++)
813     {
814         dst->num_customers[i] = src->num_customers[i];
815     }
816 
817     dst->price = src->price[0];
818 
819     for (uint8_t i = 0; i < 2; i++)
820     {
821         dst->chairlift_bullwheel_location[i] = { static_cast<uint8_t>(src->ChairliftBullwheelLocation[i].x),
822                                                  static_cast<uint8_t>(src->ChairliftBullwheelLocation[i].y) };
823         dst->chairlift_bullwheel_z[i] = static_cast<uint8_t>(src->ChairliftBullwheelLocation[i].z);
824     }
825 
826     dst->ratings = src->ratings;
827     dst->value = src->value;
828 
829     dst->chairlift_bullwheel_rotation = src->chairlift_bullwheel_rotation;
830 
831     dst->satisfaction = src->satisfaction;
832     dst->satisfaction_time_out = src->satisfaction_time_out;
833     dst->satisfaction_next = src->satisfaction_next;
834 
835     dst->window_invalidate_flags = src->window_invalidate_flags;
836     // pad_14E[0x02];
837 
838     dst->total_customers = src->total_customers;
839     dst->total_profit = ToMoney32(src->total_profit);
840     dst->popularity = src->popularity;
841     dst->popularity_time_out = src->popularity_time_out;
842     dst->popularity_next = src->popularity_next;
843     dst->num_riders = src->num_riders;
844     dst->music_tune_id = src->music_tune_id;
845     dst->slide_in_use = src->slide_in_use;
846     // Includes maze_tiles
847     dst->slide_peep = src->slide_peep;
848     // pad_160[0xE];
849     dst->slide_peep_t_shirt_colour = src->slide_peep_t_shirt_colour;
850     // pad_16F[0x7];
851     dst->spiral_slide_progress = src->spiral_slide_progress;
852     // pad_177[0x9];
853     dst->build_date = static_cast<int16_t>(src->build_date);
854     dst->upkeep_cost = src->upkeep_cost;
855     dst->race_winner = src->race_winner;
856     // pad_186[0x02];
857     dst->music_position = src->music_position;
858 
859     dst->breakdown_reason_pending = src->breakdown_reason_pending;
860     dst->mechanic_status = src->mechanic_status;
861     dst->mechanic = src->mechanic;
862     dst->inspection_station = src->inspection_station;
863     dst->broken_vehicle = src->broken_vehicle;
864     dst->broken_car = src->broken_car;
865     dst->breakdown_reason = src->breakdown_reason;
866 
867     dst->price_secondary = src->price[1];
868 
869     dst->reliability = src->reliability;
870     dst->unreliability_factor = src->unreliability_factor;
871     dst->downtime = src->downtime;
872     dst->inspection_interval = src->inspection_interval;
873     dst->last_inspection = src->last_inspection;
874 
875     for (uint8_t i = 0; i < RCT2_DOWNTIME_HISTORY_SIZE; i++)
876     {
877         dst->downtime_history[i] = src->downtime_history[i];
878     }
879 
880     dst->no_primary_items_sold = src->no_primary_items_sold;
881     dst->no_secondary_items_sold = src->no_secondary_items_sold;
882 
883     dst->breakdown_sound_modifier = src->breakdown_sound_modifier;
884     dst->not_fixed_timeout = src->not_fixed_timeout;
885     dst->last_crash_type = src->last_crash_type;
886     dst->connected_message_throttle = src->connected_message_throttle;
887 
888     dst->income_per_hour = src->income_per_hour;
889     dst->profit = ToMoney32(src->profit);
890 
891     for (uint8_t i = 0; i < RCT12_NUM_COLOUR_SCHEMES; i++)
892     {
893         dst->track_colour_main[i] = src->track_colour[i].main;
894         dst->track_colour_additional[i] = src->track_colour[i].additional;
895         dst->track_colour_supports[i] = src->track_colour[i].supports;
896     }
897 
898     dst->music = src->music;
899     dst->entrance_style = src->entrance_style;
900     dst->vehicle_change_timeout = src->vehicle_change_timeout;
901     dst->num_block_brakes = src->num_block_brakes;
902     dst->lift_hill_speed = src->lift_hill_speed;
903     dst->guests_favourite = src->guests_favourite;
904     dst->lifecycle_flags = src->lifecycle_flags;
905 
906     for (uint8_t i = 0; i < RCT2_MAX_CARS_PER_TRAIN; i++)
907     {
908         dst->vehicle_colours_extended[i] = src->vehicle_colours[i].Ternary;
909     }
910 
911     dst->total_air_time = src->total_air_time;
912     dst->current_test_station = src->current_test_station;
913     dst->num_circuits = src->num_circuits;
914     dst->cable_lift_x = static_cast<int16_t>(src->CableLiftLoc.x);
915     dst->cable_lift_y = static_cast<int16_t>(src->CableLiftLoc.y);
916     dst->cable_lift_z = static_cast<int16_t>(src->CableLiftLoc.z / COORDS_Z_STEP);
917     // pad_1FD;
918     dst->cable_lift = src->cable_lift;
919 
920     // pad_208[0x58];
921 }
922 
ExportRideRatingsCalcData()923 void S6Exporter::ExportRideRatingsCalcData()
924 {
925     const auto& src = gRideRatingUpdateState;
926     auto& dst = _s6.ride_ratings_calc_data;
927     dst.proximity_x = src.Proximity.x;
928     dst.proximity_y = src.Proximity.y;
929     dst.proximity_z = src.Proximity.z;
930     dst.proximity_start_x = src.ProximityStart.x;
931     dst.proximity_start_y = src.ProximityStart.y;
932     dst.proximity_start_z = src.ProximityStart.z;
933     dst.current_ride = OpenRCT2RideIdToRCT12RideId(src.CurrentRide);
934     dst.state = src.State;
935     if (src.ProximityTrackType == TrackElemType::None)
936         dst.proximity_track_type = 0xFF;
937     else
938         dst.proximity_track_type = OpenRCT2TrackTypeToRCT2(src.ProximityTrackType);
939     dst.proximity_base_height = src.ProximityBaseHeight;
940     dst.proximity_total = src.ProximityTotal;
941     for (size_t i = 0; i < std::size(dst.proximity_scores); i++)
942     {
943         dst.proximity_scores[i] = src.ProximityScores[i];
944     }
945     dst.num_brakes = src.AmountOfBrakes;
946     dst.num_reversers = src.AmountOfReversers;
947     dst.station_flags = src.StationFlags;
948 }
949 
ExportRideMeasurements()950 void S6Exporter::ExportRideMeasurements()
951 {
952     // Get all the ride measurements
953     std::vector<Ride*> ridesWithMeasurements;
954     for (size_t i = 0; i < RCT12_MAX_RIDES_IN_PARK; i++)
955     {
956         auto ride = get_ride(static_cast<ride_id_t>(i));
957         if (ride != nullptr && ride->measurement != nullptr)
958         {
959             ridesWithMeasurements.push_back(ride);
960         }
961     }
962 
963     // If there are more than S6 can hold, trim it by LRU
964     if (ridesWithMeasurements.size() > RCT12_RIDE_MEASUREMENT_MAX_ITEMS)
965     {
966         // Sort in order of last recently used
967         std::sort(ridesWithMeasurements.begin(), ridesWithMeasurements.end(), [](const Ride* a, const Ride* b) {
968             return a->measurement->last_use_tick > b->measurement->last_use_tick;
969         });
970         ridesWithMeasurements.resize(RCT12_RIDE_MEASUREMENT_MAX_ITEMS);
971     }
972 
973     // Convert ride measurements to S6 format
974     uint8_t i{};
975     for (auto src : ridesWithMeasurements)
976     {
977         auto& dst = _s6.ride_measurements[i];
978         ExportRideMeasurement(_s6.ride_measurements[i], *src->measurement.get());
979 
980         auto rideId = src->id;
981         dst.ride_index = static_cast<uint8_t>(rideId);
982         _s6.rides[dst.ride_index].measurement_index = i;
983         i++;
984     }
985 }
986 
ExportRideMeasurement(RCT12RideMeasurement & dst,const RideMeasurement & src)987 void S6Exporter::ExportRideMeasurement(RCT12RideMeasurement& dst, const RideMeasurement& src)
988 {
989     dst.flags = src.flags;
990     dst.last_use_tick = src.last_use_tick;
991     dst.num_items = src.num_items;
992     dst.current_item = src.current_item;
993     dst.vehicle_index = src.vehicle_index;
994     dst.current_station = src.current_station;
995     for (size_t i = 0; i < std::size(src.velocity); i++)
996     {
997         dst.velocity[i] = src.velocity[i];
998         dst.altitude[i] = src.altitude[i];
999         dst.vertical[i] = src.vertical[i];
1000         dst.lateral[i] = src.lateral[i];
1001     }
1002 }
1003 
ExportResearchedRideTypes()1004 void S6Exporter::ExportResearchedRideTypes()
1005 {
1006     std::fill(std::begin(_s6.researched_ride_types), std::end(_s6.researched_ride_types), false);
1007 
1008     for (int32_t rideType = 0; rideType < RCT2_RIDE_TYPE_COUNT; rideType++)
1009     {
1010         if (ride_type_is_invented(rideType))
1011         {
1012             int32_t quadIndex = rideType >> 5;
1013             int32_t bitIndex = rideType & 0x1F;
1014             _s6.researched_ride_types[quadIndex] |= 1UL << bitIndex;
1015         }
1016     }
1017 }
1018 
ExportResearchedRideEntries()1019 void S6Exporter::ExportResearchedRideEntries()
1020 {
1021     std::fill(std::begin(_s6.researched_ride_entries), std::end(_s6.researched_ride_entries), false);
1022 
1023     for (int32_t rideEntryIndex = 0; rideEntryIndex < MAX_RIDE_OBJECTS; rideEntryIndex++)
1024     {
1025         if (ride_entry_is_invented(rideEntryIndex))
1026         {
1027             int32_t quadIndex = rideEntryIndex >> 5;
1028             int32_t bitIndex = rideEntryIndex & 0x1F;
1029             _s6.researched_ride_entries[quadIndex] |= 1UL << bitIndex;
1030         }
1031     }
1032 }
1033 
ExportResearchedSceneryItems()1034 void S6Exporter::ExportResearchedSceneryItems()
1035 {
1036     std::fill(std::begin(_s6.researched_scenery_items), std::end(_s6.researched_scenery_items), false);
1037 
1038     for (uint16_t sceneryEntryIndex = 0; sceneryEntryIndex < RCT2_MAX_RESEARCHED_SCENERY_ITEMS; sceneryEntryIndex++)
1039     {
1040         ScenerySelection scenerySelection = { static_cast<uint8_t>((sceneryEntryIndex >> 8) & 0xFF),
1041                                               static_cast<uint16_t>(sceneryEntryIndex & 0xFF) };
1042 
1043         // SV6 allows for more scenery types than there are. Avoid triggering an assertion in scenery_is_invented().
1044         if (scenerySelection.SceneryType >= SCENERY_TYPE_COUNT)
1045             break;
1046 
1047         if (scenery_is_invented(scenerySelection))
1048         {
1049             int32_t quadIndex = sceneryEntryIndex >> 5;
1050             int32_t bitIndex = sceneryEntryIndex & 0x1F;
1051             _s6.researched_scenery_items[quadIndex] |= 1UL << bitIndex;
1052         }
1053     }
1054 }
1055 
ExportResearchList()1056 void S6Exporter::ExportResearchList()
1057 {
1058     size_t i = 0;
1059     for (const auto& researchItem : gResearchItemsInvented)
1060     {
1061         _s6.research_items[i++] = researchItem.ToRCT12ResearchItem();
1062     }
1063     _s6.research_items[i++] = { RCT12_RESEARCHED_ITEMS_SEPARATOR, 0 };
1064     for (const auto& researchItem : gResearchItemsUninvented)
1065     {
1066         _s6.research_items[i++] = researchItem.ToRCT12ResearchItem();
1067     }
1068     _s6.research_items[i++] = { RCT12_RESEARCHED_ITEMS_END, 0 };
1069     _s6.research_items[i] = { RCT12_RESEARCHED_ITEMS_END_2, 0 };
1070 }
1071 
ExportMarketingCampaigns()1072 void S6Exporter::ExportMarketingCampaigns()
1073 {
1074     std::memset(_s6.campaign_weeks_left, 0, sizeof(_s6.campaign_weeks_left));
1075     std::memset(_s6.campaign_ride_index, 0, sizeof(_s6.campaign_ride_index));
1076     for (const auto& campaign : gMarketingCampaigns)
1077     {
1078         _s6.campaign_weeks_left[campaign.Type] = campaign.WeeksLeft | CAMPAIGN_ACTIVE_FLAG;
1079         if ((campaign.Flags & MarketingCampaignFlags::FIRST_WEEK))
1080             _s6.campaign_weeks_left[campaign.Type] |= CAMPAIGN_FIRST_WEEK_FLAG;
1081         if (campaign.Type == ADVERTISING_CAMPAIGN_RIDE_FREE || campaign.Type == ADVERTISING_CAMPAIGN_RIDE)
1082         {
1083             _s6.campaign_ride_index[campaign.Type] = OpenRCT2RideIdToRCT12RideId(campaign.RideId);
1084         }
1085         else if (campaign.Type == ADVERTISING_CAMPAIGN_FOOD_OR_DRINK_FREE)
1086         {
1087             _s6.campaign_ride_index[campaign.Type] = EnumValue(campaign.ShopItemType);
1088         }
1089     }
1090 }
1091 
RebuildEntitySpatialLocation(const TileCoordsXY & loc)1092 void S6Exporter::RebuildEntitySpatialLocation(const TileCoordsXY& loc)
1093 {
1094     uint16_t previous = SPRITE_INDEX_NULL;
1095     for (auto* entity : EntityTileList(loc.ToCoordsXY()))
1096     {
1097         if (previous != SPRITE_INDEX_NULL)
1098         {
1099             _s6.sprites[previous].unknown.next_in_quadrant = entity->sprite_index;
1100         }
1101         previous = entity->sprite_index;
1102     }
1103     if (previous != SPRITE_INDEX_NULL)
1104     {
1105         _s6.sprites[previous].unknown.next_in_quadrant = SPRITE_INDEX_NULL;
1106     }
1107 }
1108 
ExportStaffPatrolAreas()1109 void S6Exporter::ExportStaffPatrolAreas()
1110 {
1111     std::fill(std::begin(_s6.staff_modes), std::end(_s6.staff_modes), RCT2StaffMode::None);
1112     std::fill(std::begin(_s6.patrol_areas), std::end(_s6.patrol_areas), 0);
1113 
1114     auto staffId = 0;
1115     for (auto* staff : EntityList<Staff>())
1116     {
1117         if (staff->HasPatrolArea())
1118         {
1119             const size_t staffPatrolOffset = staffId * STAFF_PATROL_AREA_SIZE;
1120             std::copy(
1121                 std::begin(staff->PatrolInfo->Data), std::end(staff->PatrolInfo->Data), &_s6.patrol_areas[staffPatrolOffset]);
1122             _s6.staff_modes[staffId] = RCT2StaffMode::Patrol;
1123         }
1124         else
1125         {
1126             _s6.staff_modes[staffId] = RCT2StaffMode::Walk;
1127         }
1128 
1129         _s6.sprites[staff->sprite_index].peep.staff_id = staffId;
1130 
1131         staffId++;
1132     }
1133 
1134     constexpr auto hasData = [](const auto& datapoint) { return datapoint != 0; };
1135     for (const auto type : { StaffType::Handyman, StaffType::Mechanic, StaffType::Security, StaffType::Entertainer })
1136     {
1137         const size_t staffPatrolOffset = (EnumValue(type) + STAFF_MAX_COUNT) * STAFF_PATROL_AREA_SIZE;
1138         const auto& area = GetMergedPatrolArea(type);
1139         std::copy(std::begin(area.Data), std::end(area.Data), &_s6.patrol_areas[staffPatrolOffset]);
1140         if (std::any_of(std::begin(area.Data), std::end(area.Data), hasData))
1141         {
1142             _s6.staff_modes[EnumValue(type) + STAFF_MAX_COUNT] = RCT2StaffMode::Patrol;
1143         }
1144         else
1145         {
1146             _s6.staff_modes[EnumValue(type) + STAFF_MAX_COUNT] = RCT2StaffMode::Walk;
1147         }
1148     }
1149 }
1150 
RebuildEntityLinks()1151 void S6Exporter::RebuildEntityLinks()
1152 {
1153     // Rebuild next/previous linked list entity indexs
1154     for (auto list :
1155          { RCT12EntityLinkListOffset::Free, RCT12EntityLinkListOffset::Litter, RCT12EntityLinkListOffset::Misc,
1156            RCT12EntityLinkListOffset::Peep, RCT12EntityLinkListOffset::TrainHead, RCT12EntityLinkListOffset::Vehicle })
1157     {
1158         uint16_t previous = SPRITE_INDEX_NULL;
1159         // Intialise Head to NULL for situations where there are no entities of this type.
1160         _s6.sprite_lists_head[EnumValue(list) >> 1] = SPRITE_INDEX_NULL;
1161 
1162         for (auto& entity : _s6.sprites)
1163         {
1164             if (entity.unknown.linked_list_type_offset == list)
1165             {
1166                 _s6.sprite_lists_count[EnumValue(list) >> 1]++;
1167                 _s6.sprites[entity.unknown.sprite_index].unknown.previous = previous;
1168                 if (previous != SPRITE_INDEX_NULL)
1169                 {
1170                     _s6.sprites[previous].unknown.next = entity.unknown.sprite_index;
1171                 }
1172                 else
1173                 {
1174                     _s6.sprite_lists_head[EnumValue(list) >> 1] = entity.unknown.sprite_index;
1175                 }
1176                 _s6.sprites[entity.unknown.sprite_index].unknown.next = SPRITE_INDEX_NULL;
1177                 previous = entity.unknown.sprite_index;
1178             }
1179         }
1180     }
1181 
1182     // Rebuild next_in_quadrant linked list entity indexs
1183     // This in theory is not required but to be on the safe side we are rebuilding it.
1184     for (auto x = 0; x < 255; ++x)
1185     {
1186         for (auto y = 0; y < 255; ++y)
1187         {
1188             RebuildEntitySpatialLocation(TileCoordsXY{ x, y });
1189         }
1190     }
1191     // Rebuild next_in_quadrant linked list for LOCATION_NULL
1192     TileCoordsXY invalid;
1193     invalid.SetNull();
1194     RebuildEntitySpatialLocation(invalid);
1195 }
1196 
GetRCT2LinkListOffset(const EntityBase * src)1197 constexpr RCT12EntityLinkListOffset GetRCT2LinkListOffset(const EntityBase* src)
1198 {
1199     RCT12EntityLinkListOffset output = RCT12EntityLinkListOffset::Free;
1200     switch (src->Type)
1201     {
1202         case EntityType::Vehicle:
1203         {
1204             auto veh = src->As<Vehicle>();
1205             if (veh != nullptr && veh->IsHead())
1206             {
1207                 output = RCT12EntityLinkListOffset::TrainHead;
1208             }
1209             else
1210             {
1211                 output = RCT12EntityLinkListOffset::Vehicle;
1212             }
1213         }
1214         break;
1215         case EntityType::Guest:
1216         case EntityType::Staff:
1217             output = RCT12EntityLinkListOffset::Peep;
1218             break;
1219         case EntityType::SteamParticle:
1220         case EntityType::MoneyEffect:
1221         case EntityType::CrashedVehicleParticle:
1222         case EntityType::ExplosionCloud:
1223         case EntityType::CrashSplash:
1224         case EntityType::ExplosionFlare:
1225         case EntityType::JumpingFountain:
1226         case EntityType::Balloon:
1227         case EntityType::Duck:
1228             output = RCT12EntityLinkListOffset::Misc;
1229             break;
1230         case EntityType::Litter:
1231             output = RCT12EntityLinkListOffset::Litter;
1232             break;
1233         default:
1234             break;
1235     }
1236     return output;
1237 }
1238 
GetRCT2SpriteIdentifier(const EntityBase * src)1239 constexpr RCT12SpriteIdentifier GetRCT2SpriteIdentifier(const EntityBase* src)
1240 {
1241     RCT12SpriteIdentifier output = RCT12SpriteIdentifier::Null;
1242     switch (src->Type)
1243     {
1244         case EntityType::Vehicle:
1245             output = RCT12SpriteIdentifier::Vehicle;
1246             break;
1247         case EntityType::Guest:
1248         case EntityType::Staff:
1249             output = RCT12SpriteIdentifier::Peep;
1250             break;
1251         case EntityType::SteamParticle:
1252         case EntityType::MoneyEffect:
1253         case EntityType::CrashedVehicleParticle:
1254         case EntityType::ExplosionCloud:
1255         case EntityType::CrashSplash:
1256         case EntityType::ExplosionFlare:
1257         case EntityType::JumpingFountain:
1258         case EntityType::Balloon:
1259         case EntityType::Duck:
1260             output = RCT12SpriteIdentifier::Misc;
1261             break;
1262         case EntityType::Litter:
1263             output = RCT12SpriteIdentifier::Litter;
1264             break;
1265         default:
1266             break;
1267     }
1268     return output;
1269 }
1270 
ExportEntityCommonProperties(RCT12SpriteBase * dst,const EntityBase * src)1271 void S6Exporter::ExportEntityCommonProperties(RCT12SpriteBase* dst, const EntityBase* src)
1272 {
1273     dst->sprite_identifier = GetRCT2SpriteIdentifier(src);
1274     dst->linked_list_type_offset = GetRCT2LinkListOffset(src);
1275     dst->next_in_quadrant = SPRITE_INDEX_NULL;
1276     dst->sprite_height_negative = src->sprite_height_negative;
1277     dst->sprite_index = src->sprite_index;
1278     dst->flags = 0;
1279     dst->x = static_cast<int16_t>(src->x);
1280     dst->y = static_cast<int16_t>(src->y);
1281     dst->z = static_cast<int16_t>(src->z);
1282     dst->sprite_width = src->sprite_width;
1283     dst->sprite_height_positive = src->sprite_height_positive;
1284     dst->sprite_left = src->SpriteRect.GetLeft();
1285     dst->sprite_top = src->SpriteRect.GetTop();
1286     dst->sprite_right = src->SpriteRect.GetRight();
1287     dst->sprite_bottom = src->SpriteRect.GetBottom();
1288     dst->sprite_direction = src->sprite_direction;
1289 }
1290 
ExportEntity(RCT2SpriteVehicle * dst,const Vehicle * src)1291 template<> void S6Exporter::ExportEntity(RCT2SpriteVehicle* dst, const Vehicle* src)
1292 {
1293     const auto* ride = src->GetRide();
1294 
1295     ExportEntityCommonProperties(dst, src);
1296     dst->type = EnumValue(src->SubType);
1297     dst->Pitch = src->Pitch;
1298     dst->bank_rotation = src->bank_rotation;
1299     dst->remaining_distance = src->remaining_distance;
1300     dst->velocity = src->velocity;
1301     dst->acceleration = src->acceleration;
1302     dst->ride = static_cast<uint8_t>(src->ride);
1303     dst->vehicle_type = src->vehicle_type;
1304     dst->colours = src->colours;
1305     dst->track_progress = src->track_progress;
1306     if (ride != nullptr && ride->mode == RideMode::BoatHire && src->status == Vehicle::Status::TravellingBoat)
1307     {
1308         if (src->BoatLocation.IsNull())
1309         {
1310             dst->boat_location.SetNull();
1311         }
1312         else
1313         {
1314             dst->boat_location = { static_cast<uint8_t>(src->BoatLocation.x / COORDS_XY_STEP),
1315                                    static_cast<uint8_t>(src->BoatLocation.y / COORDS_XY_STEP) };
1316         }
1317     }
1318     else
1319     {
1320         auto trackType = OpenRCT2TrackTypeToRCT2(src->GetTrackType());
1321         // Track direction and type are in the same field
1322         dst->SetTrackType(trackType);
1323         dst->SetTrackDirection(src->GetTrackDirection());
1324     }
1325     dst->track_x = src->TrackLocation.x;
1326     dst->track_y = src->TrackLocation.y;
1327     dst->track_z = src->TrackLocation.z;
1328     dst->next_vehicle_on_train = src->next_vehicle_on_train;
1329     dst->prev_vehicle_on_ride = src->prev_vehicle_on_ride;
1330     dst->next_vehicle_on_ride = src->next_vehicle_on_ride;
1331     dst->var_44 = src->var_44;
1332     dst->mass = src->mass;
1333     dst->update_flags = src->update_flags;
1334     dst->SwingSprite = src->SwingSprite;
1335     dst->current_station = src->current_station;
1336     dst->current_time = src->current_time;
1337     dst->crash_z = src->crash_z;
1338     dst->status = static_cast<uint8_t>(src->status);
1339     dst->sub_state = src->sub_state;
1340     for (size_t i = 0; i < std::size(src->peep); i++)
1341     {
1342         dst->peep[i] = src->peep[i];
1343         dst->peep_tshirt_colours[i] = src->peep_tshirt_colours[i];
1344     }
1345     dst->num_seats = src->num_seats;
1346     dst->num_peeps = src->num_peeps;
1347     dst->next_free_seat = src->next_free_seat;
1348     dst->restraints_position = src->restraints_position;
1349     dst->crash_x = src->crash_x;
1350     dst->sound2_flags = src->sound2_flags;
1351     dst->spin_sprite = src->spin_sprite;
1352     dst->sound1_id = static_cast<uint8_t>(src->sound1_id);
1353     dst->sound1_volume = src->sound1_volume;
1354     dst->sound2_id = static_cast<uint8_t>(src->sound2_id);
1355     dst->sound2_volume = src->sound2_volume;
1356     dst->sound_vector_factor = src->sound_vector_factor;
1357     dst->time_waiting = src->time_waiting;
1358     dst->speed = src->speed;
1359     dst->powered_acceleration = src->powered_acceleration;
1360     dst->dodgems_collision_direction = src->dodgems_collision_direction;
1361     dst->animation_frame = src->animation_frame;
1362     dst->animationState = src->animationState;
1363     dst->scream_sound_id = static_cast<uint8_t>(src->scream_sound_id);
1364     dst->TrackSubposition = static_cast<uint8_t>(src->TrackSubposition);
1365     dst->var_CE = src->var_CE;
1366     dst->var_CF = src->var_CF;
1367     dst->lost_time_out = src->lost_time_out;
1368     dst->vertical_drop_countdown = src->vertical_drop_countdown;
1369     dst->var_D3 = src->var_D3;
1370     dst->mini_golf_current_animation = EnumValue(src->mini_golf_current_animation);
1371     dst->mini_golf_flags = src->mini_golf_flags;
1372     dst->ride_subtype = OpenRCT2EntryIndexToRCTEntryIndex(src->ride_subtype);
1373     dst->colours_extended = src->colours_extended;
1374     dst->seat_rotation = src->seat_rotation;
1375     dst->target_seat_rotation = src->target_seat_rotation;
1376     dst->flags = src->IsCrashedVehicle ? RCT12_SPRITE_FLAGS_IS_CRASHED_VEHICLE_SPRITE : 0;
1377 }
1378 
ExportEntity(RCT2SpritePeep * dst,const Guest * src)1379 template<> void S6Exporter::ExportEntity(RCT2SpritePeep* dst, const Guest* src)
1380 {
1381     ExportEntityPeep(dst, src);
1382     dst->outside_of_park = static_cast<uint8_t>(src->OutsideOfPark);
1383     dst->no_of_rides = src->GuestNumRides;
1384     dst->happiness = src->Happiness;
1385     dst->happiness_target = src->HappinessTarget;
1386     dst->nausea = src->Nausea;
1387     dst->nausea_target = src->NauseaTarget;
1388     dst->hunger = src->Hunger;
1389     dst->thirst = src->Thirst;
1390     dst->toilet = src->Toilet;
1391     dst->time_to_consume = src->TimeToConsume;
1392     dst->intensity = static_cast<uint8_t>(src->Intensity);
1393     dst->nausea_tolerance = EnumValue(src->NauseaTolerance);
1394     dst->paid_on_drink = src->PaidOnDrink;
1395     auto* typeHistory = OpenRCT2::RideUse::GetTypeHistory().GetAll(src->sprite_index);
1396     if (typeHistory != nullptr)
1397     {
1398         for (auto typeId : *typeHistory)
1399         {
1400             dst->ride_types_been_on[typeId / 8] |= (1 << (typeId % 8));
1401         }
1402     }
1403     auto* rideHistory = OpenRCT2::RideUse::GetHistory().GetAll(src->sprite_index);
1404     if (rideHistory != nullptr)
1405     {
1406         for (auto rideId : *rideHistory)
1407         {
1408             const auto rideIndex = EnumValue(rideId);
1409             dst->rides_been_on[rideIndex / 8] |= (1 << (rideIndex % 8));
1410         }
1411     }
1412     dst->item_extra_flags = static_cast<uint32_t>(src->GetItemFlags() >> 32);
1413     dst->photo1_ride_ref = OpenRCT2RideIdToRCT12RideId(src->Photo1RideRef);
1414     dst->photo2_ride_ref = OpenRCT2RideIdToRCT12RideId(src->Photo2RideRef);
1415     dst->photo3_ride_ref = OpenRCT2RideIdToRCT12RideId(src->Photo3RideRef);
1416     dst->photo4_ride_ref = OpenRCT2RideIdToRCT12RideId(src->Photo4RideRef);
1417     dst->next_in_queue = src->GuestNextInQueue;
1418     dst->time_in_queue = src->TimeInQueue;
1419     dst->cash_in_pocket = src->CashInPocket;
1420     dst->cash_spent = src->CashSpent;
1421     dst->park_entry_time = src->ParkEntryTime;
1422     dst->rejoin_queue_timeout = src->RejoinQueueTimeout;
1423     dst->previous_ride = OpenRCT2RideIdToRCT12RideId(src->PreviousRide);
1424     dst->previous_ride_time_out = src->PreviousRideTimeOut;
1425     for (size_t i = 0; i < std::size(src->Thoughts); i++)
1426     {
1427         auto srcThought = &src->Thoughts[i];
1428         auto dstThought = &dst->thoughts[i];
1429         dstThought->type = static_cast<uint8_t>(srcThought->type);
1430         dstThought->item = static_cast<uint8_t>(srcThought->item);
1431         dstThought->freshness = srcThought->freshness;
1432         dstThought->fresh_timeout = srcThought->fresh_timeout;
1433     }
1434     dst->guest_heading_to_ride_id = OpenRCT2RideIdToRCT12RideId(src->GuestHeadingToRideId);
1435     dst->peep_is_lost_countdown = src->GuestIsLostCountdown;
1436     dst->litter_count = src->LitterCount;
1437     dst->time_on_ride = src->GuestTimeOnRide;
1438     dst->disgusting_count = src->DisgustingCount;
1439     dst->paid_to_enter = src->PaidToEnter;
1440     dst->paid_on_rides = src->PaidOnRides;
1441     dst->paid_on_food = src->PaidOnFood;
1442     dst->paid_on_souvenirs = src->PaidOnSouvenirs;
1443     dst->no_of_food = src->AmountOfFood;
1444     dst->no_of_drinks = src->AmountOfDrinks;
1445     dst->no_of_souvenirs = src->AmountOfSouvenirs;
1446     dst->vandalism_seen = src->VandalismSeen;
1447     dst->voucher_type = src->VoucherType;
1448     dst->voucher_arguments = OpenRCT2RideIdToRCT12RideId(src->VoucherRideId);
1449     dst->surroundings_thought_timeout = src->SurroundingsThoughtTimeout;
1450     dst->angriness = src->Angriness;
1451     dst->time_lost = src->TimeLost;
1452     dst->days_in_queue = src->DaysInQueue;
1453     dst->balloon_colour = src->BalloonColour;
1454     dst->umbrella_colour = src->UmbrellaColour;
1455     dst->hat_colour = src->HatColour;
1456     dst->favourite_ride = OpenRCT2RideIdToRCT12RideId(src->FavouriteRide);
1457     dst->favourite_ride_rating = src->FavouriteRideRating;
1458     dst->item_standard_flags = static_cast<uint32_t>(src->GetItemFlags());
1459 }
ExportEntity(RCT2SpritePeep * dst,const Staff * src)1460 template<> void S6Exporter::ExportEntity(RCT2SpritePeep* dst, const Staff* src)
1461 {
1462     ExportEntityPeep(dst, src);
1463     dst->staff_type = static_cast<uint8_t>(src->AssignedStaffType);
1464     dst->mechanic_time_since_call = src->MechanicTimeSinceCall;
1465     dst->park_entry_time = src->HireDate;
1466     dst->staff_orders = src->StaffOrders;
1467     dst->staff_mowing_timeout = src->StaffMowingTimeout;
1468     dst->paid_to_enter = src->StaffLawnsMown;
1469     dst->paid_on_rides = src->StaffGardensWatered;
1470     dst->paid_on_food = src->StaffLitterSwept;
1471     dst->paid_on_souvenirs = src->StaffBinsEmptied;
1472 }
1473 
ExportEntityPeep(RCT2SpritePeep * dst,const Peep * src)1474 void S6Exporter::ExportEntityPeep(RCT2SpritePeep* dst, const Peep* src)
1475 {
1476     ExportEntityCommonProperties(dst, src);
1477 
1478     auto generateName = true;
1479     if (src->Name != nullptr)
1480     {
1481         auto stringId = AllocateUserString(src->Name);
1482         if (stringId.has_value())
1483         {
1484             dst->name_string_idx = stringId.value();
1485             generateName = false;
1486         }
1487         else
1488         {
1489             log_warning(
1490                 "Unable to allocate user string for peep #%d (%s) during S6 export.", static_cast<int>(src->sprite_index),
1491                 src->Name);
1492         }
1493     }
1494     if (generateName)
1495     {
1496         auto* staff = src->As<Staff>();
1497         if (staff != nullptr)
1498         {
1499             static constexpr const rct_string_id staffNames[] = {
1500                 STR_HANDYMAN_X,
1501                 STR_MECHANIC_X,
1502                 STR_SECURITY_GUARD_X,
1503                 STR_ENTERTAINER_X,
1504             };
1505             dst->name_string_idx = staffNames[static_cast<uint8_t>(staff->AssignedStaffType) % sizeof(staffNames)];
1506         }
1507         else if (gParkFlags & PARK_FLAGS_SHOW_REAL_GUEST_NAMES)
1508         {
1509             dst->name_string_idx = get_real_name_string_id_from_id(src->Id);
1510         }
1511         else
1512         {
1513             dst->name_string_idx = STR_GUEST_X;
1514         }
1515     }
1516 
1517     dst->next_x = src->NextLoc.x;
1518     dst->next_y = src->NextLoc.y;
1519     dst->next_z = src->NextLoc.z / COORDS_Z_STEP;
1520     dst->next_flags = src->NextFlags;
1521     dst->state = static_cast<uint8_t>(src->State);
1522     dst->sub_state = src->SubState;
1523     dst->sprite_type = static_cast<uint8_t>(src->SpriteType);
1524     dst->peep_type = static_cast<uint8_t>(src->Type == EntityType::Staff ? RCT12PeepType::Staff : RCT12PeepType::Guest);
1525     dst->tshirt_colour = src->TshirtColour;
1526     dst->trousers_colour = src->TrousersColour;
1527     dst->destination_x = src->DestinationX;
1528     dst->destination_y = src->DestinationY;
1529     dst->destination_tolerance = src->DestinationTolerance;
1530     dst->var_37 = src->Var37;
1531     dst->energy = src->Energy;
1532     dst->energy_target = src->EnergyTarget;
1533     dst->mass = src->Mass;
1534     dst->window_invalidate_flags = src->WindowInvalidateFlags;
1535     dst->current_ride = OpenRCT2RideIdToRCT12RideId(src->CurrentRide);
1536     dst->current_ride_station = src->CurrentRideStation;
1537     dst->current_train = src->CurrentTrain;
1538     dst->time_to_sitdown = src->TimeToSitdown;
1539     dst->special_sprite = src->SpecialSprite;
1540     dst->action_sprite_type = static_cast<uint8_t>(src->ActionSpriteType);
1541     dst->next_action_sprite_type = static_cast<uint8_t>(src->NextActionSpriteType);
1542     dst->action_sprite_image_offset = src->ActionSpriteImageOffset;
1543     dst->action = static_cast<uint8_t>(src->Action);
1544     dst->action_frame = src->ActionFrame;
1545     dst->step_progress = src->StepProgress;
1546     dst->direction = src->PeepDirection;
1547     dst->interaction_ride_index = OpenRCT2RideIdToRCT12RideId(src->InteractionRideIndex);
1548     dst->id = src->Id;
1549     dst->path_check_optimisation = src->PathCheckOptimisation;
1550     dst->peep_flags = src->PeepFlags;
1551     if (src->PathfindGoal.IsNull())
1552     {
1553         dst->pathfind_goal = { 0xFF, 0xFF, 0xFF, INVALID_DIRECTION };
1554     }
1555     else
1556     {
1557         dst->pathfind_goal = { static_cast<uint8_t>(src->PathfindGoal.x), static_cast<uint8_t>(src->PathfindGoal.y),
1558                                static_cast<uint8_t>(src->PathfindGoal.z), src->PathfindGoal.direction };
1559     }
1560     for (size_t i = 0; i < std::size(src->PathfindHistory); i++)
1561     {
1562         if (src->PathfindHistory[i].IsNull())
1563         {
1564             dst->pathfind_history[i] = { 0xFF, 0xFF, 0xFF, INVALID_DIRECTION };
1565         }
1566         else
1567         {
1568             dst->pathfind_history[i] = { static_cast<uint8_t>(src->PathfindHistory[i].x),
1569                                          static_cast<uint8_t>(src->PathfindHistory[i].y),
1570                                          static_cast<uint8_t>(src->PathfindHistory[i].z), src->PathfindHistory[i].direction };
1571         }
1572     }
1573     dst->no_action_frame_num = src->WalkingFrameNum;
1574 }
1575 
ExportEntity(RCT12SpriteSteamParticle * dst,const SteamParticle * src)1576 template<> void S6Exporter::ExportEntity(RCT12SpriteSteamParticle* dst, const SteamParticle* src)
1577 {
1578     ExportEntityCommonProperties(dst, src);
1579     dst->type = EnumValue(RCT12MiscEntityType::SteamParticle);
1580     dst->time_to_move = src->time_to_move;
1581     dst->frame = src->frame;
1582 }
ExportEntity(RCT12SpriteMoneyEffect * dst,const MoneyEffect * src)1583 template<> void S6Exporter::ExportEntity(RCT12SpriteMoneyEffect* dst, const MoneyEffect* src)
1584 {
1585     ExportEntityCommonProperties(dst, src);
1586     dst->type = EnumValue(RCT12MiscEntityType::MoneyEffect);
1587     dst->move_delay = src->MoveDelay;
1588     dst->num_movements = src->NumMovements;
1589     dst->vertical = src->Vertical;
1590     dst->value = src->Value;
1591     dst->offset_x = src->OffsetX;
1592     dst->wiggle = src->Wiggle;
1593 }
ExportEntity(RCT12SpriteCrashedVehicleParticle * dst,const VehicleCrashParticle * src)1594 template<> void S6Exporter::ExportEntity(RCT12SpriteCrashedVehicleParticle* dst, const VehicleCrashParticle* src)
1595 {
1596     ExportEntityCommonProperties(dst, src);
1597     dst->type = EnumValue(RCT12MiscEntityType::CrashedVehicleParticle);
1598     dst->frame = src->frame;
1599     dst->time_to_live = src->time_to_live;
1600     dst->frame = src->frame;
1601     dst->colour[0] = src->colour[0];
1602     dst->colour[1] = src->colour[1];
1603     dst->crashed_sprite_base = src->crashed_sprite_base;
1604     dst->velocity_x = src->velocity_x;
1605     dst->velocity_y = src->velocity_y;
1606     dst->velocity_z = src->velocity_z;
1607     dst->acceleration_x = src->acceleration_x;
1608     dst->acceleration_y = src->acceleration_y;
1609     dst->acceleration_z = src->acceleration_z;
1610 }
ExportEntity(RCT12SpriteJumpingFountain * dst,const JumpingFountain * src)1611 template<> void S6Exporter::ExportEntity(RCT12SpriteJumpingFountain* dst, const JumpingFountain* src)
1612 {
1613     ExportEntityCommonProperties(dst, src);
1614     dst->type = EnumValue(
1615         src->FountainType == JumpingFountainType::Snow ? RCT12MiscEntityType::JumpingFountainSnow
1616                                                        : RCT12MiscEntityType::JumpingFountainWater);
1617     dst->num_ticks_alive = src->NumTicksAlive;
1618     dst->frame = src->frame;
1619     dst->fountain_flags = src->FountainFlags;
1620     dst->target_x = src->TargetX;
1621     dst->target_y = src->TargetY;
1622     dst->target_y = src->TargetY;
1623     dst->iteration = src->Iteration;
1624 }
ExportEntity(RCT12SpriteBalloon * dst,const Balloon * src)1625 template<> void S6Exporter::ExportEntity(RCT12SpriteBalloon* dst, const Balloon* src)
1626 {
1627     ExportEntityCommonProperties(dst, src);
1628     dst->type = EnumValue(RCT12MiscEntityType::Balloon);
1629     dst->popped = src->popped;
1630     dst->time_to_move = src->time_to_move;
1631     dst->frame = src->frame;
1632     dst->colour = src->colour;
1633 }
ExportEntity(RCT12SpriteDuck * dst,const Duck * src)1634 template<> void S6Exporter::ExportEntity(RCT12SpriteDuck* dst, const Duck* src)
1635 {
1636     ExportEntityCommonProperties(dst, src);
1637     dst->type = EnumValue(RCT12MiscEntityType::Duck);
1638     dst->frame = src->frame;
1639     dst->target_x = src->target_x;
1640     dst->target_y = src->target_y;
1641     dst->state = EnumValue(src->state);
1642 }
ExportEntity(RCT12SpriteParticle * dst,const ExplosionCloud * src)1643 template<> void S6Exporter::ExportEntity(RCT12SpriteParticle* dst, const ExplosionCloud* src)
1644 {
1645     ExportEntityCommonProperties(dst, src);
1646     dst->type = EnumValue(RCT12MiscEntityType::ExplosionCloud);
1647     dst->frame = src->frame;
1648 }
ExportEntity(RCT12SpriteParticle * dst,const ExplosionFlare * src)1649 template<> void S6Exporter::ExportEntity(RCT12SpriteParticle* dst, const ExplosionFlare* src)
1650 {
1651     ExportEntityCommonProperties(dst, src);
1652     dst->type = EnumValue(RCT12MiscEntityType::ExplosionFlare);
1653     dst->frame = src->frame;
1654 }
ExportEntity(RCT12SpriteParticle * dst,const CrashSplashParticle * src)1655 template<> void S6Exporter::ExportEntity(RCT12SpriteParticle* dst, const CrashSplashParticle* src)
1656 {
1657     ExportEntityCommonProperties(dst, src);
1658     dst->type = EnumValue(RCT12MiscEntityType::CrashSplash);
1659     dst->frame = src->frame;
1660 }
1661 
ExportEntity(RCT12SpriteLitter * dst,const Litter * src)1662 template<> void S6Exporter::ExportEntity(RCT12SpriteLitter* dst, const Litter* src)
1663 {
1664     ExportEntityCommonProperties(dst, src);
1665     dst->type = EnumValue(src->SubType);
1666     dst->creationTick = src->creationTick;
1667 }
1668 
ExportEntities()1669 void S6Exporter::ExportEntities()
1670 {
1671     // Clear everything to free
1672     for (int32_t i = 0; i < RCT2_MAX_SPRITES; i++)
1673     {
1674         auto& entity = _s6.sprites[i];
1675         std::memset(&entity, 0, sizeof(entity));
1676         entity.unknown.sprite_identifier = RCT12SpriteIdentifier::Null;
1677         entity.unknown.sprite_index = i;
1678         entity.unknown.linked_list_type_offset = RCT12EntityLinkListOffset::Free;
1679     }
1680 
1681     for (auto* entity : EntityList<Guest>())
1682     {
1683         ExportEntity(&_s6.sprites[entity->sprite_index].peep, entity);
1684     }
1685     for (auto* entity : EntityList<Staff>())
1686     {
1687         ExportEntity(&_s6.sprites[entity->sprite_index].peep, entity);
1688     }
1689     for (auto* entity : EntityList<Vehicle>())
1690     {
1691         ExportEntity(&_s6.sprites[entity->sprite_index].vehicle, entity);
1692     }
1693     for (auto* entity : EntityList<Litter>())
1694     {
1695         ExportEntity(&_s6.sprites[entity->sprite_index].litter, entity);
1696     }
1697     for (auto* entity : EntityList<Duck>())
1698     {
1699         ExportEntity(&_s6.sprites[entity->sprite_index].duck, entity);
1700     }
1701     for (auto* entity : EntityList<SteamParticle>())
1702     {
1703         ExportEntity(&_s6.sprites[entity->sprite_index].steam_particle, entity);
1704     }
1705     for (auto* entity : EntityList<MoneyEffect>())
1706     {
1707         ExportEntity(&_s6.sprites[entity->sprite_index].money_effect, entity);
1708     }
1709     for (auto* entity : EntityList<VehicleCrashParticle>())
1710     {
1711         ExportEntity(&_s6.sprites[entity->sprite_index].crashed_vehicle_particle, entity);
1712     }
1713     for (auto* entity : EntityList<JumpingFountain>())
1714     {
1715         ExportEntity(&_s6.sprites[entity->sprite_index].jumping_fountain, entity);
1716     }
1717     for (auto* entity : EntityList<Balloon>())
1718     {
1719         ExportEntity(&_s6.sprites[entity->sprite_index].balloon, entity);
1720     }
1721     for (auto* entity : EntityList<ExplosionCloud>())
1722     {
1723         ExportEntity(&_s6.sprites[entity->sprite_index].misc_particle, entity);
1724     }
1725     for (auto* entity : EntityList<ExplosionFlare>())
1726     {
1727         ExportEntity(&_s6.sprites[entity->sprite_index].misc_particle, entity);
1728     }
1729     for (auto* entity : EntityList<CrashSplashParticle>())
1730     {
1731         ExportEntity(&_s6.sprites[entity->sprite_index].misc_particle, entity);
1732     }
1733 
1734     RebuildEntityLinks();
1735 }
1736 
ExportBanners()1737 void S6Exporter::ExportBanners()
1738 {
1739     for (BannerIndex i = 0; i < RCT2_MAX_BANNERS_IN_PARK; i++)
1740     {
1741         auto src = GetBanner(i);
1742         auto dst = &_s6.banners[i];
1743         if (src != nullptr)
1744         {
1745             ExportBanner(*dst, *src);
1746         }
1747         else
1748         {
1749             ExportBanner(*dst, {});
1750         }
1751     }
1752 }
1753 
ExportBanner(RCT12Banner & dst,const Banner & src)1754 void S6Exporter::ExportBanner(RCT12Banner& dst, const Banner& src)
1755 {
1756     dst = {};
1757     dst.type = OpenRCT2EntryIndexToRCTEntryIndex(src.type);
1758 
1759     if (!src.IsNull())
1760     {
1761         dst.flags = src.flags;
1762 
1763         dst.string_idx = STR_DEFAULT_SIGN;
1764 
1765         std::string bannerText;
1766         if (!(src.flags & BANNER_FLAG_IS_WALL) && !(src.flags & BANNER_FLAG_IS_LARGE_SCENERY))
1767         {
1768             auto formatCode = static_cast<codepoint_t>(RCT2_STRING_FORMAT_COLOUR_START + src.text_colour);
1769             String::AppendCodepoint(bannerText, formatCode);
1770         }
1771         bannerText.append(src.text);
1772 
1773         auto stringId = AllocateUserString(bannerText);
1774         if (stringId.has_value())
1775         {
1776             dst.string_idx = stringId.value();
1777         }
1778 
1779         if (src.flags & BANNER_FLAG_LINKED_TO_RIDE)
1780         {
1781             dst.ride_index = OpenRCT2RideIdToRCT12RideId(src.ride_index);
1782         }
1783         else
1784         {
1785             dst.colour = src.colour;
1786         }
1787         dst.text_colour = src.text_colour;
1788         dst.x = src.position.x;
1789         dst.y = src.position.y;
1790     }
1791 }
1792 
ExportMapAnimations()1793 void S6Exporter::ExportMapAnimations()
1794 {
1795     const auto& mapAnimations = GetMapAnimations();
1796     auto numAnimations = std::min(mapAnimations.size(), std::size(_s6.map_animations));
1797     _s6.num_map_animations = static_cast<uint16_t>(numAnimations);
1798     for (size_t i = 0; i < numAnimations; i++)
1799     {
1800         const auto& src = mapAnimations[i];
1801         auto& dst = _s6.map_animations[i];
1802 
1803         dst.type = src.type;
1804         // In RCT12MapAnimation, the x and y coordinates use big coords, while the z coordinate uses small coords.
1805         dst.x = src.location.x;
1806         dst.y = src.location.y;
1807         dst.baseZ = src.location.z / COORDS_Z_STEP;
1808     }
1809 }
1810 
ExportTileElements()1811 void S6Exporter::ExportTileElements()
1812 {
1813     const auto& tileElements = GetTileElements();
1814     for (uint32_t index = 0; index < RCT2_MAX_TILE_ELEMENTS; index++)
1815     {
1816         auto dst = &_s6.tile_elements[index];
1817         if (index >= tileElements.size())
1818         {
1819             dst = {};
1820             continue;
1821         }
1822         auto src = &tileElements[index];
1823         if (src->base_height == MAX_ELEMENT_HEIGHT)
1824         {
1825             std::memcpy(dst, src, sizeof(*dst));
1826         }
1827         else
1828         {
1829             auto tileElementType = static_cast<RCT12TileElementType>(src->GetType());
1830             if (tileElementType == RCT12TileElementType::Corrupt || tileElementType == RCT12TileElementType::EightCarsCorrupt14
1831                 || tileElementType == RCT12TileElementType::EightCarsCorrupt15)
1832                 std::memcpy(dst, src, sizeof(*dst));
1833             else
1834                 ExportTileElement(dst, src);
1835         }
1836     }
1837     _s6.next_free_tile_element_pointer_index = static_cast<uint32_t>(tileElements.size());
1838 }
1839 
ExportTileElement(RCT12TileElement * dst,const TileElement * src)1840 void S6Exporter::ExportTileElement(RCT12TileElement* dst, const TileElement* src)
1841 {
1842     // Todo: allow for changing definition of OpenRCT2 tile element types - replace with a map
1843     uint8_t tileElementType = src->GetType();
1844     dst->ClearAs(tileElementType);
1845     dst->SetDirection(src->GetDirection());
1846     dst->base_height = src->base_height;
1847     dst->clearance_height = src->clearance_height;
1848 
1849     // All saved in "flags"
1850     dst->SetOccupiedQuadrants(src->GetOccupiedQuadrants());
1851     dst->SetGhost(src->IsGhost());
1852     dst->SetLastForTile(src->IsLastForTile());
1853 
1854     switch (tileElementType)
1855     {
1856         case TILE_ELEMENT_TYPE_SURFACE:
1857         {
1858             auto dst2 = dst->AsSurface();
1859             auto src2 = src->AsSurface();
1860 
1861             dst2->SetSlope(src2->GetSlope());
1862             dst2->SetSurfaceStyle(src2->GetSurfaceStyle());
1863             dst2->SetEdgeStyle(src2->GetEdgeStyle());
1864             dst2->SetGrassLength(src2->GetGrassLength());
1865             dst2->SetOwnership(src2->GetOwnership());
1866             dst2->SetParkFences(src2->GetParkFences());
1867             dst2->SetWaterHeight(src2->GetWaterHeight());
1868             dst2->SetHasTrackThatNeedsWater(src2->HasTrackThatNeedsWater());
1869 
1870             break;
1871         }
1872         case TILE_ELEMENT_TYPE_PATH:
1873         {
1874             auto dst2 = dst->AsPath();
1875             auto src2 = src->AsPath();
1876 
1877             dst2->SetPathEntryIndex(src2->GetLegacyPathEntryIndex());
1878             dst2->SetQueueBannerDirection(src2->GetQueueBannerDirection());
1879             dst2->SetSloped(src2->IsSloped());
1880             dst2->SetSlopeDirection(src2->GetSlopeDirection());
1881             dst2->SetRideIndex(OpenRCT2RideIdToRCT12RideId(src2->GetRideIndex()));
1882             dst2->SetStationIndex(src2->GetStationIndex());
1883             dst2->SetWide(src2->IsWide());
1884             dst2->SetIsQueue(src2->IsQueue());
1885             dst2->SetHasQueueBanner(src2->HasQueueBanner());
1886             dst2->SetEdges(src2->GetEdges());
1887             dst2->SetCorners(src2->GetCorners());
1888             dst2->SetAddition(src2->GetAddition());
1889             dst2->SetAdditionIsGhost(src2->AdditionIsGhost());
1890             dst2->SetAdditionStatus(src2->GetAdditionStatus());
1891             dst2->SetIsBroken(src2->IsBroken());
1892             dst2->SetIsBlockedByVehicle(src2->IsBlockedByVehicle());
1893 
1894             break;
1895         }
1896         case TILE_ELEMENT_TYPE_TRACK:
1897         {
1898             auto dst2 = dst->AsTrack();
1899             auto src2 = src->AsTrack();
1900 
1901             auto trackType = OpenRCT2TrackTypeToRCT2(src2->GetTrackType());
1902             dst2->SetTrackType(static_cast<uint8_t>(trackType));
1903             dst2->SetSequenceIndex(src2->GetSequenceIndex());
1904             dst2->SetRideIndex(OpenRCT2RideIdToRCT12RideId(src2->GetRideIndex()));
1905             dst2->SetColourScheme(src2->GetColourScheme());
1906             dst2->SetStationIndex(src2->GetStationIndex());
1907             dst2->SetHasGreenLight(src2->HasGreenLight());
1908             dst2->SetHasChain(src2->HasChain());
1909             dst2->SetHasCableLift(src2->HasCableLift());
1910             dst2->SetInverted(src2->IsInverted());
1911             dst2->SetBrakeBoosterSpeed(src2->GetBrakeBoosterSpeed());
1912             dst2->SetPhotoTimeout(src2->GetPhotoTimeout());
1913             dst2->SetBlockBrakeClosed(src2->BlockBrakeClosed());
1914             dst2->SetIsIndestructible(src2->IsIndestructible());
1915 
1916             // Skipping IsHighlighted()
1917 
1918             // This has to be done last, since the maze entry shares fields with the colour and sequence fields.
1919             auto ride = get_ride(static_cast<ride_id_t>(dst2->GetRideIndex()));
1920             if (ride != nullptr)
1921             {
1922                 if (ride->type == RIDE_TYPE_MAZE)
1923                 {
1924                     dst2->SetMazeEntry(src2->GetMazeEntry());
1925                 }
1926                 else if (ride->type == RIDE_TYPE_GHOST_TRAIN)
1927                 {
1928                     dst2->SetDoorAState(src2->GetDoorAState());
1929                     dst2->SetDoorBState(src2->GetDoorBState());
1930                 }
1931                 else
1932                 {
1933                     dst2->SetSeatRotation(src2->GetSeatRotation());
1934                 }
1935             }
1936             // _Should_ not happen, but if it does, pick the most likely option.
1937             else
1938             {
1939                 dst2->SetSeatRotation(src2->GetSeatRotation());
1940             }
1941 
1942             break;
1943         }
1944         case TILE_ELEMENT_TYPE_SMALL_SCENERY:
1945         {
1946             auto dst2 = dst->AsSmallScenery();
1947             auto src2 = src->AsSmallScenery();
1948 
1949             dst2->SetEntryIndex(src2->GetEntryIndex());
1950             dst2->SetAge(src2->GetAge());
1951             dst2->SetSceneryQuadrant(src2->GetSceneryQuadrant());
1952             dst2->SetPrimaryColour(src2->GetPrimaryColour());
1953             dst2->SetSecondaryColour(src2->GetSecondaryColour());
1954             if (src2->NeedsSupports())
1955                 dst2->SetNeedsSupports();
1956 
1957             break;
1958         }
1959         case TILE_ELEMENT_TYPE_ENTRANCE:
1960         {
1961             auto dst2 = dst->AsEntrance();
1962             auto src2 = src->AsEntrance();
1963 
1964             dst2->SetEntranceType(src2->GetEntranceType());
1965             dst2->SetRideIndex(OpenRCT2RideIdToRCT12RideId(src2->GetRideIndex()));
1966             dst2->SetStationIndex(src2->GetStationIndex());
1967             dst2->SetSequenceIndex(src2->GetSequenceIndex());
1968             dst2->SetPathType(src2->GetLegacyPathEntryIndex());
1969 
1970             break;
1971         }
1972         case TILE_ELEMENT_TYPE_WALL:
1973         {
1974             auto dst2 = dst->AsWall();
1975             auto src2 = src->AsWall();
1976 
1977             dst2->SetEntryIndex(src2->GetEntryIndex());
1978             dst2->SetSlope(src2->GetSlope());
1979             dst2->SetPrimaryColour(src2->GetPrimaryColour());
1980             dst2->SetSecondaryColour(src2->GetSecondaryColour());
1981             dst2->SetTertiaryColour(src2->GetTertiaryColour());
1982             dst2->SetAnimationFrame(src2->GetAnimationFrame());
1983             dst2->SetAcrossTrack(src2->IsAcrossTrack());
1984             dst2->SetAnimationIsBackwards(src2->AnimationIsBackwards());
1985 
1986             auto entry = src2->GetEntry();
1987             if (entry != nullptr && entry->scrolling_mode != SCROLLING_MODE_NONE)
1988             {
1989                 auto bannerIndex = src2->GetBannerIndex();
1990                 if (bannerIndex != BANNER_INDEX_NULL)
1991                     dst2->SetBannerIndex(bannerIndex);
1992                 else
1993                     dst2->SetBannerIndex(RCT12_BANNER_INDEX_NULL);
1994             }
1995 
1996             break;
1997         }
1998         case TILE_ELEMENT_TYPE_LARGE_SCENERY:
1999         {
2000             auto dst2 = dst->AsLargeScenery();
2001             auto src2 = src->AsLargeScenery();
2002 
2003             dst2->SetEntryIndex(src2->GetEntryIndex());
2004             dst2->SetSequenceIndex(src2->GetSequenceIndex());
2005             dst2->SetPrimaryColour(src2->GetPrimaryColour());
2006             dst2->SetSecondaryColour(src2->GetSecondaryColour());
2007 
2008             auto entry = src2->GetEntry();
2009             if (entry != nullptr && entry->scrolling_mode != SCROLLING_MODE_NONE)
2010             {
2011                 auto bannerIndex = src2->GetBannerIndex();
2012                 if (bannerIndex != BANNER_INDEX_NULL)
2013                     dst2->SetBannerIndex(bannerIndex);
2014                 else
2015                     dst2->SetBannerIndex(RCT12_BANNER_INDEX_NULL);
2016             }
2017 
2018             break;
2019         }
2020         case TILE_ELEMENT_TYPE_BANNER:
2021         {
2022             auto dst2 = dst->AsBanner();
2023             auto src2 = src->AsBanner();
2024 
2025             dst2->SetPosition(src2->GetPosition());
2026             dst2->SetAllowedEdges(src2->GetAllowedEdges());
2027             auto bannerIndex = src2->GetIndex();
2028             if (bannerIndex != BANNER_INDEX_NULL)
2029                 dst2->SetIndex(bannerIndex);
2030             else
2031                 dst2->SetIndex(RCT12_BANNER_INDEX_NULL);
2032             break;
2033         }
2034         default:
2035             assert(false);
2036     }
2037 }
2038 
AllocateUserString(std::string_view value)2039 std::optional<uint16_t> S6Exporter::AllocateUserString(std::string_view value)
2040 {
2041     auto nextId = _userStrings.size();
2042     if (nextId < RCT12_MAX_USER_STRINGS)
2043     {
2044         _userStrings.emplace_back(value);
2045         return static_cast<uint16_t>(USER_STRING_START + nextId);
2046     }
2047     return std::nullopt;
2048 }
2049 
ExportUserStrings()2050 void S6Exporter::ExportUserStrings()
2051 {
2052     auto numUserStrings = std::min<size_t>(_userStrings.size(), RCT12_MAX_USER_STRINGS);
2053     for (size_t i = 0; i < numUserStrings; i++)
2054     {
2055         auto dst = _s6.custom_strings[i];
2056         const auto& src = _userStrings[i];
2057         auto encodedSrc = GetTruncatedRCT2String(src, RCT12_USER_STRING_MAX_LENGTH);
2058         auto stringLen = std::min<size_t>(encodedSrc.size(), RCT12_USER_STRING_MAX_LENGTH - 1);
2059         std::memcpy(dst, encodedSrc.data(), stringLen);
2060     }
2061 }
2062 
2063 enum : uint32_t
2064 {
2065     S6_SAVE_FLAG_EXPORT = 1 << 0,
2066     S6_SAVE_FLAG_SCENARIO = 1 << 1,
2067     S6_SAVE_FLAG_AUTOMATIC = 1u << 31,
2068 };
2069 
2070 /**
2071  *
2072  *  rct2: 0x006754F5
2073  * @param flags bit 0: pack objects, 1: save as scenario
2074  */
scenario_save(const utf8 * path,int32_t flags)2075 int32_t scenario_save(const utf8* path, int32_t flags)
2076 {
2077     if (flags & S6_SAVE_FLAG_SCENARIO)
2078     {
2079         log_verbose("scenario_save(%s, SCENARIO)", path);
2080     }
2081     else
2082     {
2083         log_verbose("scenario_save(%s, SAVED GAME)", path);
2084     }
2085 
2086     if (!(flags & S6_SAVE_FLAG_AUTOMATIC))
2087     {
2088         window_close_construction_windows();
2089     }
2090 
2091     viewport_set_saved_view();
2092 
2093     bool result = false;
2094     auto s6exporter = new S6Exporter();
2095     try
2096     {
2097         if (flags & S6_SAVE_FLAG_EXPORT)
2098         {
2099             auto& objManager = OpenRCT2::GetContext()->GetObjectManager();
2100             s6exporter->ExportObjectsList = objManager.GetPackableObjects();
2101         }
2102         s6exporter->RemoveTracklessRides = true;
2103         s6exporter->Export();
2104         if (flags & S6_SAVE_FLAG_SCENARIO)
2105         {
2106             s6exporter->SaveScenario(path);
2107         }
2108         else
2109         {
2110             s6exporter->SaveGame(path);
2111         }
2112         result = true;
2113     }
2114     catch (const std::exception& e)
2115     {
2116         log_error("Unable to save park: '%s'", e.what());
2117     }
2118     delete s6exporter;
2119 
2120     gfx_invalidate_screen();
2121 
2122     if (result && !(flags & S6_SAVE_FLAG_AUTOMATIC))
2123     {
2124         gScreenAge = 0;
2125     }
2126     return result;
2127 }
2128