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