diff --git a/openrct2.vcxproj b/openrct2.vcxproj
index 87f7d53f9c..aa51bb272c 100644
--- a/openrct2.vcxproj
+++ b/openrct2.vcxproj
@@ -117,7 +117,7 @@
-
+
@@ -418,7 +418,7 @@
-
+
diff --git a/src/object/ObjectCache.cpp b/src/object/ObjectCache.cpp
deleted file mode 100644
index 158c4cd5d7..0000000000
--- a/src/object/ObjectCache.cpp
+++ /dev/null
@@ -1,149 +0,0 @@
-#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
-/*****************************************************************************
- * OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
- *
- * OpenRCT2 is the work of many authors, a full list can be found in contributors.md
- * For more information, visit https://github.com/OpenRCT2/OpenRCT2
- *
- * OpenRCT2 is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * A full copy of the GNU General Public License can be found in licence.txt
- *****************************************************************************/
-#pragma endregion
-
-#include "../common.h"
-#include "../core/Guard.hpp"
-#include "../core/IStream.hpp"
-#include "../core/Memory.hpp"
-#include "../core/String.hpp"
-#include "ObjectCache.h"
-
-extern "C"
-{
- #include "../object.h"
-}
-
-struct ObjectCacheEntry
-{
- rct_object_entry_extended ObjectEntry;
- utf8 * Path;
- uint32 NumImages;
- utf8 * Name;
- size_t ChunkSize;
- uint16 NumRequiredObjects;
- rct_object_entry_extended * RequiredObjects;
- union
- {
- struct
- {
- uint16 NumThemeObjects;
- rct_object_entry_extended * ThemeObjects;
- };
- struct
- {
- uint8 RideFlags;
- uint8 RideCategory[2];
- uint8 RideType[3];
- };
- };
-
- void Dispose()
- {
- Memory::Free(RequiredObjects);
- Memory::Free(ThemeObjects);
- RequiredObjects = nullptr;
- ThemeObjects = nullptr;
- }
-
- void Read(IStream * stream)
- {
- Guard::Assert(RequiredObjects == nullptr);
- Guard::Assert(ThemeObjects == nullptr);
-
- ObjectEntry = stream->ReadValue();
- Path = stream->ReadString();
- NumImages = stream->ReadValue();
- Name = stream->ReadString();
- ChunkSize = stream->ReadValue();
- NumRequiredObjects = stream->ReadValue();
-
- RequiredObjects = Memory::AllocateArray(NumRequiredObjects);
- for (uint16 i = 0; i < NumRequiredObjects; i++)
- {
- RequiredObjects[i] = stream->ReadValue();
- }
-
- switch (ObjectEntry.flags & 0x0F) {
- case OBJECT_TYPE_RIDE:
- RideFlags = stream->ReadValue();
- for (int i = 0; i < 2; i++)
- {
- RideCategory[i] = stream->ReadValue();
- }
- for (int i = 0; i < 3; i++)
- {
- RideType[i] = stream->ReadValue();
- }
- break;
- case OBJECT_TYPE_SCENERY_SETS:
- NumThemeObjects = stream->ReadValue();
- for (uint16 i = 0; i < NumThemeObjects; i++)
- {
- ThemeObjects[i] = stream->ReadValue();
- }
- break;
- }
- }
-
- void Write(IStream * stream)
- {
- stream->WriteValue(ObjectEntry);
- stream->WriteString(Path);
- stream->WriteValue(NumImages);
- stream->WriteString(Name);
- stream->WriteValue(ChunkSize);
-
- stream->WriteValue(NumRequiredObjects);
- for (uint16 i = 0; i < NumRequiredObjects; i++)
- {
- stream->WriteValue(RequiredObjects[i]);
- }
-
- switch (ObjectEntry.flags & 0x0F) {
- case OBJECT_TYPE_RIDE:
- stream->WriteValue(RideFlags);
- for (int i = 0; i < 2; i++)
- {
- stream->WriteValue(RideCategory[i]);
- }
- for (int i = 0; i < 3; i++)
- {
- stream->WriteValue(RideType[i]);
- }
- break;
- case OBJECT_TYPE_SCENERY_SETS:
- stream->WriteValue(NumThemeObjects);
- for (uint16 i = 0; i < NumThemeObjects; i++)
- {
- stream->WriteValue(ThemeObjects[i]);
- }
- break;
- }
- }
-};
-
-class ObjectCache : IObjectCache
-{
- void Load() override
- {
- // TODO
- }
-
- void Save() override
- {
- // TODO
- }
-};
diff --git a/src/object/ObjectCache.h b/src/object/ObjectCache.h
deleted file mode 100644
index cb7b4ba5b3..0000000000
--- a/src/object/ObjectCache.h
+++ /dev/null
@@ -1,27 +0,0 @@
-#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
-/*****************************************************************************
- * OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
- *
- * OpenRCT2 is the work of many authors, a full list can be found in contributors.md
- * For more information, visit https://github.com/OpenRCT2/OpenRCT2
- *
- * OpenRCT2 is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * A full copy of the GNU General Public License can be found in licence.txt
- *****************************************************************************/
-#pragma endregion
-
-#pragma once
-
-#include "../common.h"
-
-interface IObjectCache
-{
- virtual ~IObjectCache();
-
- virtual void Load() abstract;
- virtual void Save() abstract;
-};
diff --git a/src/object/ObjectRepository.cpp b/src/object/ObjectRepository.cpp
new file mode 100644
index 0000000000..70c1382534
--- /dev/null
+++ b/src/object/ObjectRepository.cpp
@@ -0,0 +1,275 @@
+#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
+/*****************************************************************************
+ * OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
+ *
+ * OpenRCT2 is the work of many authors, a full list can be found in contributors.md
+ * For more information, visit https://github.com/OpenRCT2/OpenRCT2
+ *
+ * OpenRCT2 is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * A full copy of the GNU General Public License can be found in licence.txt
+ *****************************************************************************/
+#pragma endregion
+
+#include
+
+#include "../common.h"
+#include "../core/FileStream.hpp"
+#include "../core/Guard.hpp"
+#include "../core/IStream.hpp"
+#include "../core/Memory.hpp"
+#include "../core/String.hpp"
+#include "ObjectRepository.h"
+
+extern "C"
+{
+ #include "../object.h"
+ #include "../platform/platform.h"
+}
+
+constexpr uint16 OBJECT_REPOSITORY_VERSION = 6;
+
+struct ObjectRepositoryHeader
+{
+ uint16 Version;
+ uint32 TotalFiles;
+ uint64 TotalFileSize;
+ uint32 FileDateModifiedChecksum;
+ uint32 NumItems;
+};
+
+struct QueryDirectoryResult
+{
+ uint32 TotalFiles;
+ uint64 TotalFileSize;
+ uint32 FileDateModifiedChecksum;
+};
+
+class ObjectRepository : public IObjectRepository
+{
+ std::vector _items;
+ QueryDirectoryResult _queryDirectoryResult;
+
+public:
+ void LoadOrConstruct()
+ {
+ _queryDirectoryResult = QueryDirectory();
+ if (!Load())
+ {
+ Construct();
+ Save();
+ }
+ }
+
+ const ObjectRepositoryItem * FindObject(const rct_object_entry * objectEntry) override
+ {
+ for (uint32 i = 0; i < _items.size(); i++)
+ {
+ ObjectRepositoryItem * item = &_items[i];
+ rct_object_entry * itemEntry = (rct_object_entry*)&item->ObjectEntry;
+ if (object_entry_compare(itemEntry, objectEntry))
+ {
+ return item;
+ }
+ }
+ return nullptr;
+ }
+
+private:
+ void Construct()
+ {
+ // TODO
+ }
+
+ bool Load()
+ {
+ utf8 path[MAX_PATH];
+ GetRepositoryPath(path, sizeof(path));
+
+ try
+ {
+ auto fs = FileStream(path, FILE_MODE_OPEN);
+ auto header = fs.ReadValue();
+
+ if (header.Version == OBJECT_REPOSITORY_VERSION &&
+ header.TotalFiles == _queryDirectoryResult.TotalFiles &&
+ header.TotalFileSize == _queryDirectoryResult.TotalFileSize &&
+ header.FileDateModifiedChecksum == _queryDirectoryResult.FileDateModifiedChecksum)
+ {
+ // Header matches, so the index is not out of date
+ for (uint32 i = 0; i < header.NumItems; i++)
+ {
+ ObjectRepositoryItem item = ReadItem(&fs);
+ _items.push_back(item);
+ }
+ return true;
+ }
+ log_info("Object repository is out of date.");
+ return false;
+ }
+ catch (IOException ex)
+ {
+ return false;
+ }
+ }
+
+ void Save() const
+ {
+ utf8 path[MAX_PATH];
+ GetRepositoryPath(path, sizeof(path));
+
+ try
+ {
+ auto fs = FileStream(path, FILE_MODE_WRITE);
+
+ // Write header
+ ObjectRepositoryHeader header;
+ header.Version = OBJECT_REPOSITORY_VERSION;
+ header.TotalFiles = _queryDirectoryResult.TotalFiles;
+ header.TotalFileSize = _queryDirectoryResult.TotalFileSize;
+ header.FileDateModifiedChecksum = _queryDirectoryResult.FileDateModifiedChecksum;
+ header.NumItems = _items.size();
+ fs.WriteValue(header);
+
+ // Write items
+ for (uint32 i = 0; i < header.NumItems; i++)
+ {
+ fs.WriteValue(_items[i]);
+ }
+ }
+ catch (IOException ex)
+ {
+ log_error("Unable to write object repository index.");
+ }
+ }
+
+ static ObjectRepositoryItem ReadItem(IStream * stream)
+ {
+ ObjectRepositoryItem item = { 0 };
+
+ item.ObjectEntry = stream->ReadValue();
+ item.Path = stream->ReadString();
+ item.NumImages = stream->ReadValue();
+ item.Name = stream->ReadString();
+ item.ChunkSize = stream->ReadValue();
+ item.NumRequiredObjects = stream->ReadValue();
+
+ item.RequiredObjects = Memory::AllocateArray(item.NumRequiredObjects);
+ for (uint16 i = 0; i < item.NumRequiredObjects; i++)
+ {
+ item.RequiredObjects[i] = stream->ReadValue();
+ }
+
+ switch (item.ObjectEntry.flags & 0x0F) {
+ case OBJECT_TYPE_RIDE:
+ item.RideFlags = stream->ReadValue();
+ for (int i = 0; i < 2; i++)
+ {
+ item.RideCategory[i] = stream->ReadValue();
+ }
+ for (int i = 0; i < 3; i++)
+ {
+ item.RideType[i] = stream->ReadValue();
+ }
+ break;
+ case OBJECT_TYPE_SCENERY_SETS:
+ item.NumThemeObjects = stream->ReadValue();
+ item.ThemeObjects = Memory::AllocateArray(item.NumThemeObjects);
+ for (uint16 i = 0; i < item.NumThemeObjects; i++)
+ {
+ item.ThemeObjects[i] = stream->ReadValue();
+ }
+ break;
+ }
+ return item;
+ }
+
+ static void WriteItem(IStream * stream, const ObjectRepositoryItem &item)
+ {
+ stream->WriteValue(item.ObjectEntry);
+ stream->WriteString(item.Path);
+ stream->WriteValue(item.NumImages);
+ stream->WriteString(item.Name);
+ stream->WriteValue(item.ChunkSize);
+
+ stream->WriteValue(item.NumRequiredObjects);
+ for (uint16 i = 0; i < item.NumRequiredObjects; i++)
+ {
+ stream->WriteValue(item.RequiredObjects[i]);
+ }
+
+ switch (item.ObjectEntry.flags & 0x0F) {
+ case OBJECT_TYPE_RIDE:
+ stream->WriteValue(item.RideFlags);
+ for (int i = 0; i < 2; i++)
+ {
+ stream->WriteValue(item.RideCategory[i]);
+ }
+ for (int i = 0; i < 3; i++)
+ {
+ stream->WriteValue(item.RideType[i]);
+ }
+ break;
+ case OBJECT_TYPE_SCENERY_SETS:
+ stream->WriteValue(item.NumThemeObjects);
+ for (uint16 i = 0; i < item.NumThemeObjects; i++)
+ {
+ stream->WriteValue(item.ThemeObjects[i]);
+ }
+ break;
+ }
+ }
+
+ static void FreeItem(ObjectRepositoryItem * item)
+ {
+ Memory::Free(item->RequiredObjects);
+ Memory::Free(item->ThemeObjects);
+ item->RequiredObjects = nullptr;
+ item->ThemeObjects = nullptr;
+ }
+
+ static void GetRepositoryPath(utf8 * buffer, size_t bufferSize)
+ {
+ platform_get_user_directory(buffer, nullptr);
+ strcat(buffer, "objects.idx");
+ }
+
+ static QueryDirectoryResult QueryDirectory()
+ {
+ QueryDirectoryResult result = { 0 };
+
+ // Enumerate through each object in the directory
+ int enumFileHandle = platform_enumerate_files_begin(gRCT2AddressObjectDataPath);
+ if (enumFileHandle != INVALID_HANDLE)
+ {
+ file_info enumFileInfo;
+ while (platform_enumerate_files_next(enumFileHandle, &enumFileInfo))
+ {
+ result.TotalFiles++;
+ result.TotalFileSize += enumFileInfo.size;
+ result.FileDateModifiedChecksum ^=
+ (uint32)(enumFileInfo.last_modified >> 32) ^
+ (uint32)(enumFileInfo.last_modified & 0xFFFFFFFF);
+ result.FileDateModifiedChecksum = ror32(result.FileDateModifiedChecksum, 5);
+ }
+ platform_enumerate_files_end(enumFileHandle);
+ }
+
+ return result;
+ }
+};
+
+static ObjectRepository * _objectRepository = nullptr;
+
+IObjectRepository * GetObjectRepository()
+{
+ if (_objectRepository == nullptr)
+ {
+ _objectRepository = new ObjectRepository();
+ _objectRepository->LoadOrConstruct();
+ }
+ return _objectRepository;
+}
diff --git a/src/object/ObjectRepository.h b/src/object/ObjectRepository.h
new file mode 100644
index 0000000000..c686a49de7
--- /dev/null
+++ b/src/object/ObjectRepository.h
@@ -0,0 +1,58 @@
+#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
+/*****************************************************************************
+ * OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
+ *
+ * OpenRCT2 is the work of many authors, a full list can be found in contributors.md
+ * For more information, visit https://github.com/OpenRCT2/OpenRCT2
+ *
+ * OpenRCT2 is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * A full copy of the GNU General Public License can be found in licence.txt
+ *****************************************************************************/
+#pragma endregion
+
+#pragma once
+
+#include "../common.h"
+
+extern "C"
+{
+ #include "../object.h"
+}
+
+struct ObjectRepositoryItem
+{
+ rct_object_entry_extended ObjectEntry;
+ utf8 * Path;
+ uint32 NumImages;
+ utf8 * Name;
+ size_t ChunkSize;
+ uint16 NumRequiredObjects;
+ rct_object_entry_extended * RequiredObjects;
+ union
+ {
+ struct
+ {
+ uint16 NumThemeObjects;
+ rct_object_entry_extended * ThemeObjects;
+ };
+ struct
+ {
+ uint8 RideFlags;
+ uint8 RideCategory[2];
+ uint8 RideType[3];
+ };
+ };
+};
+
+interface IObjectRepository
+{
+ virtual ~IObjectRepository() { }
+
+ virtual const ObjectRepositoryItem * FindObject(const rct_object_entry * objectEntry) abstract;
+};
+
+IObjectRepository * GetObjectRepository();