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 "ObjectRepository.h"
11
12 #include "../Context.h"
13 #include "../PlatformEnvironment.h"
14 #include "../common.h"
15 #include "../config/Config.h"
16 #include "../core/Console.hpp"
17 #include "../core/DataSerialiser.h"
18 #include "../core/FileIndex.hpp"
19 #include "../core/FileStream.h"
20 #include "../core/Guard.hpp"
21 #include "../core/IStream.hpp"
22 #include "../core/Memory.hpp"
23 #include "../core/MemoryStream.h"
24 #include "../core/Numerics.hpp"
25 #include "../core/Path.hpp"
26 #include "../core/String.hpp"
27 #include "../localisation/Localisation.h"
28 #include "../localisation/LocalisationService.h"
29 #include "../object/Object.h"
30 #include "../platform/platform.h"
31 #include "../rct12/SawyerChunkReader.h"
32 #include "../rct12/SawyerChunkWriter.h"
33 #include "../scenario/ScenarioRepository.h"
34 #include "../util/SawyerCoding.h"
35 #include "../util/Util.h"
36 #include "Object.h"
37 #include "ObjectFactory.h"
38 #include "ObjectList.h"
39 #include "ObjectManager.h"
40 #include "RideObject.h"
41
42 #include <algorithm>
43 #include <memory>
44 #include <unordered_map>
45 #include <vector>
46
47 // windows.h defines CP_UTF8
48 #undef CP_UTF8
49
50 using namespace OpenRCT2;
51
52 struct ObjectEntryHash
53 {
operator ()ObjectEntryHash54 size_t operator()(const rct_object_entry& entry) const
55 {
56 uint32_t hash = 5381;
57 for (auto i : entry.name)
58 {
59 hash = ((hash << 5) + hash) + i;
60 }
61 return hash;
62 }
63 };
64
65 struct ObjectEntryEqual
66 {
operator ()ObjectEntryEqual67 bool operator()(const rct_object_entry& lhs, const rct_object_entry& rhs) const
68 {
69 return memcmp(&lhs.name, &rhs.name, 8) == 0;
70 }
71 };
72
73 using ObjectIdentifierMap = std::unordered_map<std::string, size_t>;
74 using ObjectEntryMap = std::unordered_map<rct_object_entry, size_t, ObjectEntryHash, ObjectEntryEqual>;
75
76 class ObjectFileIndex final : public FileIndex<ObjectRepositoryItem>
77 {
78 private:
79 static constexpr uint32_t MAGIC_NUMBER = 0x5844494F; // OIDX
80 static constexpr uint16_t VERSION = 28;
81 static constexpr auto PATTERN = "*.dat;*.pob;*.json;*.parkobj";
82
83 IObjectRepository& _objectRepository;
84
85 public:
ObjectFileIndex(IObjectRepository & objectRepository,const IPlatformEnvironment & env)86 explicit ObjectFileIndex(IObjectRepository& objectRepository, const IPlatformEnvironment& env)
87 : FileIndex(
88 "object index", MAGIC_NUMBER, VERSION, env.GetFilePath(PATHID::CACHE_OBJECTS), std::string(PATTERN),
89 std::vector<std::string>{
90 env.GetDirectoryPath(DIRBASE::OPENRCT2, DIRID::OBJECT),
91 env.GetDirectoryPath(DIRBASE::USER, DIRID::OBJECT),
92 })
93 , _objectRepository(objectRepository)
94 {
95 }
96
97 public:
Create(int32_t language,const std::string & path) const98 std::tuple<bool, ObjectRepositoryItem> Create([[maybe_unused]] int32_t language, const std::string& path) const override
99 {
100 std::unique_ptr<Object> object;
101 auto extension = Path::GetExtension(path);
102 if (String::Equals(extension, ".json", true))
103 {
104 object = ObjectFactory::CreateObjectFromJsonFile(_objectRepository, path);
105 }
106 else if (String::Equals(extension, ".parkobj", true))
107 {
108 object = ObjectFactory::CreateObjectFromZipFile(_objectRepository, path);
109 }
110 else
111 {
112 object = ObjectFactory::CreateObjectFromLegacyFile(_objectRepository, path.c_str());
113 }
114 if (object != nullptr)
115 {
116 ObjectRepositoryItem item = {};
117 item.Type = object->GetObjectType();
118 item.Generation = object->GetGeneration();
119 item.Identifier = object->GetIdentifier();
120 item.ObjectEntry = object->GetObjectEntry();
121 item.Path = path;
122 item.Name = object->GetName();
123 item.Authors = object->GetAuthors();
124 item.Sources = object->GetSourceGames();
125 object->SetRepositoryItem(&item);
126 return std::make_tuple(true, item);
127 }
128 return std::make_tuple(false, ObjectRepositoryItem());
129 }
130
131 protected:
Serialise(DataSerialiser & ds,ObjectRepositoryItem & item) const132 void Serialise(DataSerialiser& ds, ObjectRepositoryItem& item) const override
133 {
134 ds << item.Type;
135 ds << item.Generation;
136 ds << item.Identifier;
137 ds << item.ObjectEntry;
138 ds << item.Path;
139 ds << item.Name;
140
141 ds << item.Sources;
142 ds << item.Authors;
143
144 switch (item.Type)
145 {
146 case ObjectType::Ride:
147 ds << item.RideInfo.RideFlags;
148 ds << item.RideInfo.RideCategory;
149 ds << item.RideInfo.RideType;
150 break;
151 case ObjectType::SceneryGroup:
152 {
153 ds << item.SceneryGroupInfo.Entries;
154 break;
155 }
156 case ObjectType::FootpathSurface:
157 ds << item.FootpathSurfaceInfo.Flags;
158 break;
159 default:
160 // Switch processes only ObjectType::Ride and ObjectType::SceneryGroup
161 break;
162 }
163 }
164
165 private:
IsTrackReadOnly(const std::string & path) const166 bool IsTrackReadOnly(const std::string& path) const
167 {
168 return String::StartsWith(path, SearchPaths[0]) || String::StartsWith(path, SearchPaths[1]);
169 }
170 };
171
172 class ObjectRepository final : public IObjectRepository
173 {
174 std::shared_ptr<IPlatformEnvironment> const _env;
175 ObjectFileIndex const _fileIndex;
176 std::vector<ObjectRepositoryItem> _items;
177 ObjectIdentifierMap _newItemMap;
178 ObjectEntryMap _itemMap;
179
180 public:
ObjectRepository(const std::shared_ptr<IPlatformEnvironment> & env)181 explicit ObjectRepository(const std::shared_ptr<IPlatformEnvironment>& env)
182 : _env(env)
183 , _fileIndex(*this, *env)
184 {
185 }
186
~ObjectRepository()187 ~ObjectRepository() final
188 {
189 ClearItems();
190 }
191
LoadOrConstruct(int32_t language)192 void LoadOrConstruct(int32_t language) override
193 {
194 ClearItems();
195 auto items = _fileIndex.LoadOrBuild(language);
196 AddItems(items);
197 SortItems();
198 }
199
Construct(int32_t language)200 void Construct(int32_t language) override
201 {
202 auto items = _fileIndex.Rebuild(language);
203 AddItems(items);
204 SortItems();
205 }
206
GetNumObjects() const207 size_t GetNumObjects() const override
208 {
209 return _items.size();
210 }
211
GetObjects() const212 const ObjectRepositoryItem* GetObjects() const override
213 {
214 return _items.data();
215 }
216
FindObjectLegacy(std::string_view legacyIdentifier) const217 const ObjectRepositoryItem* FindObjectLegacy(std::string_view legacyIdentifier) const override
218 {
219 rct_object_entry entry = {};
220 entry.SetName(legacyIdentifier);
221
222 auto kvp = _itemMap.find(entry);
223 if (kvp != _itemMap.end())
224 {
225 return &_items[kvp->second];
226 }
227 return nullptr;
228 }
229
FindObject(std::string_view identifier) const230 const ObjectRepositoryItem* FindObject(std::string_view identifier) const override final
231 {
232 auto kvp = _newItemMap.find(std::string(identifier));
233 if (kvp != _newItemMap.end())
234 {
235 return &_items[kvp->second];
236 }
237 return nullptr;
238 }
239
FindObject(const rct_object_entry * objectEntry) const240 const ObjectRepositoryItem* FindObject(const rct_object_entry* objectEntry) const override final
241 {
242 auto kvp = _itemMap.find(*objectEntry);
243 if (kvp != _itemMap.end())
244 {
245 return &_items[kvp->second];
246 }
247 return nullptr;
248 }
249
FindObject(const ObjectEntryDescriptor & entry) const250 const ObjectRepositoryItem* FindObject(const ObjectEntryDescriptor& entry) const override final
251 {
252 if (entry.Generation == ObjectGeneration::DAT)
253 return FindObject(&entry.Entry);
254
255 return FindObject(entry.Identifier);
256 }
257
LoadObject(const ObjectRepositoryItem * ori)258 std::unique_ptr<Object> LoadObject(const ObjectRepositoryItem* ori) override
259 {
260 Guard::ArgumentNotNull(ori, GUARD_LINE);
261
262 auto extension = Path::GetExtension(ori->Path);
263 if (String::Equals(extension, ".json", true))
264 {
265 return ObjectFactory::CreateObjectFromJsonFile(*this, ori->Path);
266 }
267 if (String::Equals(extension, ".parkobj", true))
268 {
269 return ObjectFactory::CreateObjectFromZipFile(*this, ori->Path);
270 }
271
272 return ObjectFactory::CreateObjectFromLegacyFile(*this, ori->Path.c_str());
273 }
274
RegisterLoadedObject(const ObjectRepositoryItem * ori,std::unique_ptr<Object> && object)275 void RegisterLoadedObject(const ObjectRepositoryItem* ori, std::unique_ptr<Object>&& object) override
276 {
277 ObjectRepositoryItem* item = &_items[ori->Id];
278
279 Guard::Assert(item->LoadedObject == nullptr, GUARD_LINE);
280 item->LoadedObject = std::move(object);
281 }
282
UnregisterLoadedObject(const ObjectRepositoryItem * ori,Object * object)283 void UnregisterLoadedObject(const ObjectRepositoryItem* ori, Object* object) override
284 {
285 ObjectRepositoryItem* item = &_items[ori->Id];
286 if (item->LoadedObject.get() == object)
287 {
288 item->LoadedObject = nullptr;
289 }
290 }
291
AddObject(const rct_object_entry * objectEntry,const void * data,size_t dataSize)292 void AddObject(const rct_object_entry* objectEntry, const void* data, size_t dataSize) override
293 {
294 utf8 objectName[9];
295 object_entry_get_name_fixed(objectName, sizeof(objectName), objectEntry);
296
297 // Check that the object is loadable before writing it
298 auto object = ObjectFactory::CreateObjectFromLegacyData(*this, objectEntry, data, dataSize);
299 if (object == nullptr)
300 {
301 Console::Error::WriteLine("[%s] Unable to export object.", objectName);
302 }
303 else
304 {
305 log_verbose("Adding object: [%s]", objectName);
306 auto path = GetPathForNewObject(ObjectGeneration::DAT, objectName);
307 try
308 {
309 SaveObject(path, objectEntry, data, dataSize);
310 ScanObject(path);
311 }
312 catch (const std::exception&)
313 {
314 Console::Error::WriteLine("Failed saving object: [%s] to '%s'.", objectName, path.c_str());
315 }
316 }
317 }
318
AddObjectFromFile(ObjectGeneration generation,std::string_view objectName,const void * data,size_t dataSize)319 void AddObjectFromFile(ObjectGeneration generation, std::string_view objectName, const void* data, size_t dataSize) override
320 {
321 log_verbose("Adding object: [%s]", std::string(objectName).c_str());
322 auto path = GetPathForNewObject(generation, objectName);
323 try
324 {
325 File::WriteAllBytes(path, data, dataSize);
326 ScanObject(path);
327 }
328 catch (const std::exception&)
329 {
330 Console::Error::WriteLine("Failed saving object: [%s] to '%s'.", std::string(objectName).c_str(), path.c_str());
331 }
332 }
333
ExportPackedObject(IStream * stream)334 void ExportPackedObject(IStream* stream) override
335 {
336 auto chunkReader = SawyerChunkReader(stream);
337
338 // Check if we already have this object
339 rct_object_entry entry = stream->ReadValue<rct_object_entry>();
340 if (FindObject(&entry) != nullptr)
341 {
342 chunkReader.SkipChunk();
343 }
344 else
345 {
346 // Read object and save to new file
347 std::shared_ptr<SawyerChunk> chunk = chunkReader.ReadChunk();
348 AddObject(&entry, chunk->GetData(), chunk->GetLength());
349 }
350 }
351
WritePackedObjects(IStream * stream,std::vector<const ObjectRepositoryItem * > & objects)352 void WritePackedObjects(IStream* stream, std::vector<const ObjectRepositoryItem*>& objects) override
353 {
354 log_verbose("packing %u objects", objects.size());
355 for (const auto& object : objects)
356 {
357 Guard::ArgumentNotNull(object);
358
359 log_verbose("exporting object %.8s", object->ObjectEntry.name);
360 if (IsObjectCustom(object))
361 {
362 WritePackedObject(stream, &object->ObjectEntry);
363 }
364 else
365 {
366 log_warning("Refusing to pack vanilla/expansion object \"%s\"", object->ObjectEntry.name);
367 }
368 }
369 }
370
371 private:
ClearItems()372 void ClearItems()
373 {
374 _items.clear();
375 _newItemMap.clear();
376 _itemMap.clear();
377 }
378
SortItems()379 void SortItems()
380 {
381 std::sort(_items.begin(), _items.end(), [](const ObjectRepositoryItem& a, const ObjectRepositoryItem& b) -> bool {
382 return String::Compare(a.Name, b.Name) < 0;
383 });
384
385 // Fix the IDs
386 for (size_t i = 0; i < _items.size(); i++)
387 {
388 _items[i].Id = i;
389 }
390
391 // Rebuild item map
392 _itemMap.clear();
393 _newItemMap.clear();
394 for (size_t i = 0; i < _items.size(); i++)
395 {
396 rct_object_entry entry = _items[i].ObjectEntry;
397 _itemMap[entry] = i;
398 if (!_items[i].Identifier.empty())
399 {
400 _newItemMap[_items[i].Identifier] = i;
401 }
402 }
403 }
404
AddItems(const std::vector<ObjectRepositoryItem> & items)405 void AddItems(const std::vector<ObjectRepositoryItem>& items)
406 {
407 size_t numConflicts = 0;
408 for (const auto& item : items)
409 {
410 if (!AddItem(item))
411 {
412 numConflicts++;
413 }
414 }
415 if (numConflicts > 0)
416 {
417 Console::Error::WriteLine("%zu object conflicts found.", numConflicts);
418 }
419 }
420
AddItem(const ObjectRepositoryItem & item)421 bool AddItem(const ObjectRepositoryItem& item)
422 {
423 const ObjectRepositoryItem* conflict{};
424 if (item.ObjectEntry.name[0] != '\0')
425 {
426 conflict = FindObject(&item.ObjectEntry);
427 }
428 if (conflict == nullptr)
429 {
430 conflict = FindObject(item.Identifier);
431 }
432 if (conflict == nullptr)
433 {
434 size_t index = _items.size();
435 auto copy = item;
436 copy.Id = index;
437 _items.push_back(std::move(copy));
438 if (!item.Identifier.empty())
439 {
440 _newItemMap[item.Identifier] = index;
441 }
442 if (!item.ObjectEntry.IsEmpty())
443 {
444 _itemMap[item.ObjectEntry] = index;
445 }
446 return true;
447 }
448
449 Console::Error::WriteLine("Object conflict: '%s'", conflict->Path.c_str());
450 Console::Error::WriteLine(" : '%s'", item.Path.c_str());
451 return false;
452 }
453
ScanObject(const std::string & path)454 void ScanObject(const std::string& path)
455 {
456 auto language = LocalisationService_GetCurrentLanguage();
457 auto result = _fileIndex.Create(language, path);
458 if (std::get<0>(result))
459 {
460 auto ori = std::get<1>(result);
461 AddItem(ori);
462 }
463 }
464
SaveObject(std::string_view path,const rct_object_entry * entry,const void * data,size_t dataSize,bool fixChecksum=true)465 static void SaveObject(
466 std::string_view path, const rct_object_entry* entry, const void* data, size_t dataSize, bool fixChecksum = true)
467 {
468 if (fixChecksum)
469 {
470 uint32_t realChecksum = object_calculate_checksum(entry, data, dataSize);
471 if (realChecksum != entry->checksum)
472 {
473 char objectName[9];
474 object_entry_get_name_fixed(objectName, sizeof(objectName), entry);
475 log_verbose("[%s] Incorrect checksum, adding salt bytes...", objectName);
476
477 // Calculate the value of extra bytes that can be appended to the data so that the
478 // data is then valid for the object's checksum
479 size_t extraBytesCount = 0;
480 void* extraBytes = CalculateExtraBytesToFixChecksum(realChecksum, entry->checksum, &extraBytesCount);
481
482 // Create new data blob with appended bytes
483 size_t newDataSize = dataSize + extraBytesCount;
484 uint8_t* newData = Memory::Allocate<uint8_t>(newDataSize);
485 uint8_t* newDataSaltOffset = newData + dataSize;
486 std::copy_n(static_cast<const uint8_t*>(data), dataSize, newData);
487 std::copy_n(static_cast<const uint8_t*>(extraBytes), extraBytesCount, newDataSaltOffset);
488
489 try
490 {
491 uint32_t newRealChecksum = object_calculate_checksum(entry, newData, newDataSize);
492 if (newRealChecksum != entry->checksum)
493 {
494 Console::Error::WriteLine("CalculateExtraBytesToFixChecksum failed to fix checksum.");
495
496 // Save old data form
497 SaveObject(path, entry, data, dataSize, false);
498 }
499 else
500 {
501 // Save new data form
502 SaveObject(path, entry, newData, newDataSize, false);
503 }
504 Memory::Free(newData);
505 Memory::Free(extraBytes);
506 }
507 catch (const std::exception&)
508 {
509 Memory::Free(newData);
510 Memory::Free(extraBytes);
511 throw;
512 }
513 return;
514 }
515 }
516
517 // Encode data
518 ObjectType objectType = entry->GetType();
519 sawyercoding_chunk_header chunkHeader;
520 chunkHeader.encoding = object_entry_group_encoding[EnumValue(objectType)];
521 chunkHeader.length = static_cast<uint32_t>(dataSize);
522 uint8_t* encodedDataBuffer = Memory::Allocate<uint8_t>(0x600000);
523 size_t encodedDataSize = sawyercoding_write_chunk_buffer(
524 encodedDataBuffer, reinterpret_cast<const uint8_t*>(data), chunkHeader);
525
526 // Save to file
527 try
528 {
529 auto fs = FileStream(std::string(path), FILE_MODE_WRITE);
530 fs.Write(entry, sizeof(rct_object_entry));
531 fs.Write(encodedDataBuffer, encodedDataSize);
532
533 Memory::Free(encodedDataBuffer);
534 }
535 catch (const std::exception&)
536 {
537 Memory::Free(encodedDataBuffer);
538 throw;
539 }
540 }
541
CalculateExtraBytesToFixChecksum(int32_t currentChecksum,int32_t targetChecksum,size_t * outSize)542 static void* CalculateExtraBytesToFixChecksum(int32_t currentChecksum, int32_t targetChecksum, size_t* outSize)
543 {
544 // Allocate 11 extra bytes to manipulate the checksum
545 uint8_t* salt = Memory::Allocate<uint8_t>(11);
546 if (outSize != nullptr)
547 *outSize = 11;
548
549 // Next work out which bits need to be flipped to make the current checksum match the one in the file
550 // The bitwise rotation compensates for the rotation performed during the checksum calculation*/
551 int32_t bitsToFlip = targetChecksum ^ ((currentChecksum << 25) | (currentChecksum >> 7));
552
553 // Each set bit encountered during encoding flips one bit of the resulting checksum (so each bit of the checksum is an
554 // XOR of bits from the file). Here, we take each bit that should be flipped in the checksum and set one of the bits in
555 // the data that maps to it. 11 bytes is the minimum needed to touch every bit of the checksum - with less than that,
556 // you wouldn't always be able to make the checksum come out to the desired target
557 salt[0] = (bitsToFlip & 0x00000001) << 7;
558 salt[1] = ((bitsToFlip & 0x00200000) >> 14);
559 salt[2] = ((bitsToFlip & 0x000007F8) >> 3);
560 salt[3] = ((bitsToFlip & 0xFF000000) >> 24);
561 salt[4] = ((bitsToFlip & 0x00100000) >> 13);
562 salt[5] = (bitsToFlip & 0x00000004) >> 2;
563 salt[6] = 0;
564 salt[7] = ((bitsToFlip & 0x000FF000) >> 12);
565 salt[8] = (bitsToFlip & 0x00000002) >> 1;
566 salt[9] = (bitsToFlip & 0x00C00000) >> 22;
567 salt[10] = (bitsToFlip & 0x00000800) >> 11;
568
569 return salt;
570 }
571
GetPathForNewObject(ObjectGeneration generation,std::string_view name)572 std::string GetPathForNewObject(ObjectGeneration generation, std::string_view name)
573 {
574 // Get object directory and create it if it doesn't exist
575 auto userObjPath = _env->GetDirectoryPath(DIRBASE::USER, DIRID::OBJECT);
576 Path::CreateDirectory(userObjPath);
577
578 // Find a unique file name
579 auto fileName = GetFileNameForNewObject(generation, name);
580 auto extension = (generation == ObjectGeneration::DAT ? ".DAT" : ".parkobj");
581 auto fullPath = Path::Combine(userObjPath, fileName + extension);
582 auto counter = 1U;
583 while (File::Exists(fullPath))
584 {
585 counter++;
586 fullPath = Path::Combine(userObjPath, String::StdFormat("%s-%02X%s", fileName.c_str(), counter, extension));
587 }
588
589 return fullPath;
590 }
591
GetFileNameForNewObject(ObjectGeneration generation,std::string_view name)592 std::string GetFileNameForNewObject(ObjectGeneration generation, std::string_view name)
593 {
594 if (generation == ObjectGeneration::DAT)
595 {
596 // Trim name
597 char normalisedName[9] = { 0 };
598 auto maxLength = std::min<size_t>(name.size(), 8);
599 for (size_t i = 0; i < maxLength; i++)
600 {
601 if (name[i] != ' ')
602 {
603 normalisedName[i] = toupper(name[i]);
604 }
605 else
606 {
607 normalisedName[i] = '\0';
608 break;
609 }
610 }
611
612 // Convert to UTF-8 filename
613 return String::Convert(normalisedName, CODE_PAGE::CP_1252, CODE_PAGE::CP_UTF8);
614 }
615 else
616 {
617 return std::string(name);
618 }
619 }
620
WritePackedObject(OpenRCT2::IStream * stream,const rct_object_entry * entry)621 void WritePackedObject(OpenRCT2::IStream* stream, const rct_object_entry* entry)
622 {
623 const ObjectRepositoryItem* item = FindObject(entry);
624 if (item == nullptr)
625 {
626 throw std::runtime_error(String::StdFormat("Unable to find object '%.8s'", entry->name));
627 }
628
629 // Read object data from file
630 auto fs = OpenRCT2::FileStream(item->Path, OpenRCT2::FILE_MODE_OPEN);
631 auto fileEntry = fs.ReadValue<rct_object_entry>();
632 if (*entry != fileEntry)
633 {
634 throw std::runtime_error("Header found in object file does not match object to pack.");
635 }
636 auto chunkReader = SawyerChunkReader(&fs);
637 auto chunk = chunkReader.ReadChunk();
638
639 // Write object data to stream
640 auto chunkWriter = SawyerChunkWriter(stream);
641 stream->WriteValue(*entry);
642 chunkWriter.WriteChunk(chunk.get());
643 }
644 };
645
CreateObjectRepository(const std::shared_ptr<IPlatformEnvironment> & env)646 std::unique_ptr<IObjectRepository> CreateObjectRepository(const std::shared_ptr<IPlatformEnvironment>& env)
647 {
648 return std::make_unique<ObjectRepository>(env);
649 }
650
IsObjectCustom(const ObjectRepositoryItem * object)651 bool IsObjectCustom(const ObjectRepositoryItem* object)
652 {
653 Guard::ArgumentNotNull(object);
654
655 // Do not count our new object types as custom yet, otherwise the game
656 // will try to pack them into saved games.
657 if (object->Type > ObjectType::ScenarioText)
658 {
659 return false;
660 }
661
662 switch (object->GetFirstSourceGame())
663 {
664 case ObjectSourceGame::RCT1:
665 case ObjectSourceGame::AddedAttractions:
666 case ObjectSourceGame::LoopyLandscapes:
667 case ObjectSourceGame::RCT2:
668 case ObjectSourceGame::WackyWorlds:
669 case ObjectSourceGame::TimeTwister:
670 case ObjectSourceGame::OpenRCT2Official:
671 return false;
672 default:
673 return true;
674 }
675 }
676
object_list_find(rct_object_entry * entry)677 const rct_object_entry* object_list_find(rct_object_entry* entry)
678 {
679 const rct_object_entry* result = nullptr;
680 auto& objRepo = GetContext()->GetObjectRepository();
681 auto item = objRepo.FindObject(entry);
682 if (item != nullptr)
683 {
684 result = &item->ObjectEntry;
685 }
686 return result;
687 }
688
object_repository_load_object(const rct_object_entry * objectEntry)689 std::unique_ptr<Object> object_repository_load_object(const rct_object_entry* objectEntry)
690 {
691 std::unique_ptr<Object> object;
692 auto& objRepository = GetContext()->GetObjectRepository();
693 const ObjectRepositoryItem* ori = objRepository.FindObject(objectEntry);
694 if (ori != nullptr)
695 {
696 object = objRepository.LoadObject(ori);
697 if (object != nullptr)
698 {
699 object->Load();
700 }
701 }
702 return object;
703 }
704
scenario_translate(scenario_index_entry * scenarioEntry)705 void scenario_translate(scenario_index_entry* scenarioEntry)
706 {
707 rct_string_id localisedStringIds[3];
708 if (language_get_localised_scenario_strings(scenarioEntry->name, localisedStringIds))
709 {
710 if (localisedStringIds[0] != STR_NONE)
711 {
712 String::Set(scenarioEntry->name, sizeof(scenarioEntry->name), language_get_string(localisedStringIds[0]));
713 }
714 if (localisedStringIds[2] != STR_NONE)
715 {
716 String::Set(scenarioEntry->details, sizeof(scenarioEntry->details), language_get_string(localisedStringIds[2]));
717 }
718 }
719 }
720
object_repository_get_items_count()721 size_t object_repository_get_items_count()
722 {
723 auto& objectRepository = GetContext()->GetObjectRepository();
724 return objectRepository.GetNumObjects();
725 }
726
object_repository_get_items()727 const ObjectRepositoryItem* object_repository_get_items()
728 {
729 auto& objectRepository = GetContext()->GetObjectRepository();
730 return objectRepository.GetObjects();
731 }
732
object_repository_find_object_by_entry(const rct_object_entry * entry)733 const ObjectRepositoryItem* object_repository_find_object_by_entry(const rct_object_entry* entry)
734 {
735 auto& objectRepository = GetContext()->GetObjectRepository();
736 return objectRepository.FindObject(entry);
737 }
738
object_repository_find_object_by_name(const char * name)739 const ObjectRepositoryItem* object_repository_find_object_by_name(const char* name)
740 {
741 auto& objectRepository = GetContext()->GetObjectRepository();
742 return objectRepository.FindObjectLegacy(name);
743 }
744
object_calculate_checksum(const rct_object_entry * entry,const void * data,size_t dataLength)745 int32_t object_calculate_checksum(const rct_object_entry* entry, const void* data, size_t dataLength)
746 {
747 const uint8_t* entryBytePtr = reinterpret_cast<const uint8_t*>(entry);
748
749 uint32_t checksum = 0xF369A75B;
750 checksum ^= entryBytePtr[0];
751 checksum = Numerics::rol32(checksum, 11);
752 for (int32_t i = 4; i < 12; i++)
753 {
754 checksum ^= entryBytePtr[i];
755 checksum = Numerics::rol32(checksum, 11);
756 }
757
758 const uint8_t* dataBytes = reinterpret_cast<const uint8_t*>(data);
759 const size_t dataLength32 = dataLength - (dataLength & 31);
760 for (size_t i = 0; i < 32; i++)
761 {
762 for (size_t j = i; j < dataLength32; j += 32)
763 {
764 checksum ^= dataBytes[j];
765 }
766 checksum = Numerics::rol32(checksum, 11);
767 }
768 for (size_t i = dataLength32; i < dataLength; i++)
769 {
770 checksum ^= dataBytes[i];
771 checksum = Numerics::rol32(checksum, 11);
772 }
773
774 return static_cast<int32_t>(checksum);
775 }
776