diff --git a/openrct2.vcxproj b/openrct2.vcxproj
index 330d4242fe..5ea5b3b28f 100644
--- a/openrct2.vcxproj
+++ b/openrct2.vcxproj
@@ -51,6 +51,7 @@
+
@@ -380,4 +381,4 @@
-
+
\ No newline at end of file
diff --git a/openrct2.vcxproj.filters b/openrct2.vcxproj.filters
index 5fc8dbe7c9..9e8e13016e 100644
--- a/openrct2.vcxproj.filters
+++ b/openrct2.vcxproj.filters
@@ -582,6 +582,9 @@
Source\Windows
+
+ Source\Interface
+
diff --git a/src/interface/Theme.cpp b/src/interface/Theme.cpp
new file mode 100644
index 0000000000..911a703644
--- /dev/null
+++ b/src/interface/Theme.cpp
@@ -0,0 +1,474 @@
+#include
+
+extern "C"
+{
+ #include "../common.h"
+ #include "window.h"
+}
+
+#include "../core/FileStream.hpp"
+#include "../core/Math.hpp"
+#include "../core/Memory.hpp"
+#include "../core/String.hpp"
+#include "../core/Util.hpp"
+
+struct WindowThemeDesc;
+
+// Don't try to load theme files that exceed 64 MiB
+constexpr uint64 MAX_THEME_SIZE = 64 * 1024 * 1024;
+
+enum {
+ UITHEME_FLAG_PREDEFINED = 1 << 0,
+ UITHEME_FLAG_USE_LIGHTS_RIDE = 1 << 1,
+ UITHEME_FLAG_USE_LIGHTS_PARK = 1 << 2,
+ UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT = 1 << 3,
+};
+
+/**
+ * Represents a window theming style such as the colour scheme.
+ */
+struct WindowTheme
+{
+ colour_t Colours[6];
+};
+
+/**
+ * Represents the style for a particular type of window.
+ */
+struct UIThemeEntry
+{
+ rct_windowclass WindowClass;
+ WindowTheme Theme;
+
+
+ json_t * ToJson() const;
+ static UIThemeEntry * FromJson(const WindowThemeDesc * wtDesc, const json_t * json);
+};
+
+/**
+ * Represents a user interface theme. Contains window colour schemes and appearance features.
+ */
+struct UITheme
+{
+ utf8 * Name;
+ UIThemeEntry * Entries;
+ uint8 Flags;
+
+
+ void SetEntry(const UIThemeEntry * entry);
+ void RemoveEntry(rct_windowclass windowClass);
+ json_t * ToJson() const;
+ bool WriteToFile(const utf8 * path) const;
+ static UITheme * Create(const utf8 * name);
+ static void Free(UITheme * theme);
+ static UITheme * FromJson(const json_t * json);
+ static UITheme * FromFile(const utf8 * path);
+};
+
+/**
+ * Represents the theme descriptor for a specific window type including the default colour scheme.
+ */
+struct WindowThemeDesc
+{
+ rct_windowclass WindowClass;
+ const utf8 * WindowClassSZ;
+ rct_string_id WindowName;
+ uint8 NumColours;
+ WindowTheme DefaultTheme;
+};
+
+#pragma region Window Theme Descriptors
+
+#define COLOURS_1(c0) 1, { { (c0), 0, 0, 0, 0, 0 } }
+#define COLOURS_2(c0, c1) 2, { { (c0), (c1), 0, 0, 0, 0 } }
+#define COLOURS_3(c0, c1, c2) 3, { { (c0), (c1), (c2), 0, 0, 0 } }
+#define COLOURS_4(c0, c1, c2, c3) 4, { { (c0), (c1), (c2), (c3), 0, 0 } }
+#define COLOURS_5(c0, c1, c2, c3, c4) 5, { { (c0), (c1), (c2), (c3), (c4), 0 } }
+#define COLOURS_6(c0, c1, c2, c3, c4, c5) 6, { { (c0), (c1), (c2), (c3), (c4), (c5) } }
+
+#define THEME_DEF_END { 0xFF, { 0, 0, 0, 0, 0, 0 } }
+
+#define TWINDOW(window_class, window_name, window_string_id, theme) { window_class, window_name, window_string_id, theme }
+
+#define THEME_WC(wc) wc, #wc
+
+WindowThemeDesc WindowThemeDescriptors[] =
+{
+ // WindowClass, WindowClassSZ WindowName NumColours, DefaultTheme
+ { THEME_WC(WC_TOP_TOOLBAR), 5245, COLOURS_4(COLOUR_LIGHT_BLUE, COLOUR_DARK_GREEN, COLOUR_DARK_BROWN, COLOUR_GREY ) },
+ { THEME_WC(WC_BOTTOM_TOOLBAR), 5246, COLOURS_4(TRANSLUCENT(COLOUR_DARK_GREEN), TRANSLUCENT(COLOUR_DARK_GREEN), COLOUR_BLACK, COLOUR_BRIGHT_GREEN ) },
+ { THEME_WC(WC_RIDE), 5203, COLOURS_3(COLOUR_GREY, COLOUR_BORDEAUX_RED, COLOUR_GREY ) },
+ { THEME_WC(WC_RIDE_CONSTRUCTION), 5199, COLOURS_3(COLOUR_DARK_BROWN, COLOUR_DARK_BROWN, COLOUR_DARK_BROWN ) },
+ { THEME_WC(WC_RIDE_LIST), 5204, COLOURS_3(COLOUR_GREY, COLOUR_BORDEAUX_RED, COLOUR_BORDEAUX_RED ) },
+ { THEME_WC(WC_SAVE_PROMPT), 5223, COLOURS_1(TRANSLUCENT(COLOUR_BORDEAUX_RED) ) },
+ { THEME_WC(WC_CONSTRUCT_RIDE), 5201, COLOURS_3(COLOUR_DARK_BROWN, COLOUR_BORDEAUX_RED, COLOUR_BORDEAUX_RED ) },
+ { THEME_WC(WC_DEMOLISH_RIDE_PROMPT), 5224, COLOURS_1(TRANSLUCENT(COLOUR_BORDEAUX_RED) ) },
+ { THEME_WC(WC_SCENERY), 5197, COLOURS_3(COLOUR_DARK_BROWN, COLOUR_DARK_GREEN, COLOUR_DARK_GREEN ) },
+ { THEME_WC(WC_OPTIONS), 5219, COLOURS_3(COLOUR_LIGHT_BLUE, COLOUR_LIGHT_BLUE, COLOUR_LIGHT_BLUE ) },
+ { THEME_WC(WC_FOOTPATH), 5198, COLOURS_3(COLOUR_DARK_BROWN, COLOUR_DARK_BROWN, COLOUR_DARK_BROWN ) },
+ { THEME_WC(WC_LAND), 5193, COLOURS_3(COLOUR_DARK_BROWN, COLOUR_DARK_BROWN, COLOUR_DARK_BROWN ) },
+ { THEME_WC(WC_WATER), 5194, COLOURS_3(COLOUR_DARK_BROWN, COLOUR_DARK_BROWN, COLOUR_DARK_BROWN ) },
+ { THEME_WC(WC_PEEP), 5205, COLOURS_3(COLOUR_GREY, COLOUR_OLIVE_GREEN, COLOUR_OLIVE_GREEN ) },
+ { THEME_WC(WC_GUEST_LIST), 5206, COLOURS_3(COLOUR_GREY, COLOUR_OLIVE_GREEN, COLOUR_OLIVE_GREEN ) },
+ { THEME_WC(WC_STAFF_LIST), 5208, COLOURS_3(COLOUR_GREY, COLOUR_LIGHT_PURPLE, COLOUR_LIGHT_PURPLE ) },
+ { THEME_WC(WC_FIRE_PROMPT), 5225, COLOURS_1(TRANSLUCENT(COLOUR_BORDEAUX_RED) ) },
+ { THEME_WC(WC_PARK_INFORMATION), 5253, COLOURS_3(COLOUR_GREY, COLOUR_DARK_YELLOW, COLOUR_DARK_YELLOW ) },
+ { THEME_WC(WC_FINANCES), 5187, COLOURS_3(COLOUR_GREY, COLOUR_DARK_YELLOW, COLOUR_DARK_YELLOW ) },
+ { THEME_WC(WC_TITLE_MENU), 5249, COLOURS_3(TRANSLUCENT(COLOUR_DARK_GREEN), TRANSLUCENT(COLOUR_DARK_GREEN), TRANSLUCENT(COLOUR_DARK_GREEN) ) },
+ { THEME_WC(WC_TITLE_EXIT), 5250, COLOURS_3(TRANSLUCENT(COLOUR_DARK_GREEN), TRANSLUCENT(COLOUR_DARK_GREEN), TRANSLUCENT(COLOUR_DARK_GREEN) ) },
+ { THEME_WC(WC_RECENT_NEWS), 5192, COLOURS_3(COLOUR_GREY, COLOUR_GREY, COLOUR_BLACK ) },
+ { THEME_WC(WC_SCENARIO_SELECT), 5252, COLOURS_3(COLOUR_GREY, COLOUR_BORDEAUX_RED, COLOUR_BORDEAUX_RED ) },
+ { THEME_WC(WC_TRACK_DESIGN_LIST), 5202, COLOURS_3(COLOUR_BORDEAUX_RED, COLOUR_BORDEAUX_RED, COLOUR_BORDEAUX_RED ) },
+ { THEME_WC(WC_TRACK_DESIGN_PLACE), 5200, COLOURS_3(COLOUR_DARK_BROWN, COLOUR_DARK_BROWN, COLOUR_DARK_BROWN ) },
+ { THEME_WC(WC_NEW_CAMPAIGN), 5188, COLOURS_3(COLOUR_DARK_YELLOW, COLOUR_DARK_YELLOW, COLOUR_DARK_YELLOW ) },
+ { THEME_WC(WC_KEYBOARD_SHORTCUT_LIST), 5220, COLOURS_3(COLOUR_LIGHT_BLUE, COLOUR_LIGHT_BLUE, COLOUR_LIGHT_BLUE ) },
+ { THEME_WC(WC_CHANGE_KEYBOARD_SHORTCUT), 5221, COLOURS_3(COLOUR_LIGHT_BLUE, COLOUR_LIGHT_BLUE, COLOUR_LIGHT_BLUE ) },
+ { THEME_WC(WC_MAP), 5190, COLOURS_2(COLOUR_DARK_GREEN, COLOUR_DARK_BROWN ) },
+ { THEME_WC(WC_BANNER), 5209, COLOURS_3(COLOUR_DARK_BROWN, COLOUR_DARK_BROWN, COLOUR_DARK_BROWN ) },
+ { THEME_WC(WC_EDITOR_OBJECT_SELECTION), 5210, COLOURS_3(COLOUR_LIGHT_PURPLE, COLOUR_GREY, COLOUR_GREY ) },
+ { THEME_WC(WC_EDITOR_INVENTION_LIST), 5211, COLOURS_3(COLOUR_LIGHT_PURPLE, COLOUR_GREY, COLOUR_GREY ) },
+ { THEME_WC(WC_EDITOR_SCENARIO_OPTIONS), 5212, COLOURS_3(COLOUR_LIGHT_PURPLE, COLOUR_GREY, COLOUR_GREY ) },
+ { THEME_WC(WC_EDTIOR_OBJECTIVE_OPTIONS), 5213, COLOURS_3(COLOUR_LIGHT_PURPLE, COLOUR_GREY, COLOUR_GREY ) },
+ { THEME_WC(WC_MANAGE_TRACK_DESIGN), 5215, COLOURS_3(COLOUR_GREY, COLOUR_GREY, COLOUR_GREY ) },
+ { THEME_WC(WC_TRACK_DELETE_PROMPT), 5226, COLOURS_3(COLOUR_BORDEAUX_RED, COLOUR_BORDEAUX_RED, COLOUR_BORDEAUX_RED ) },
+ { THEME_WC(WC_INSTALL_TRACK), 5216, COLOURS_3(COLOUR_BORDEAUX_RED, COLOUR_BORDEAUX_RED, COLOUR_BORDEAUX_RED ) },
+ { THEME_WC(WC_CLEAR_SCENERY), 5195, COLOURS_3(COLOUR_DARK_BROWN, COLOUR_DARK_BROWN, COLOUR_DARK_BROWN ) },
+ { THEME_WC(WC_CHEATS), 5217, COLOURS_3(COLOUR_GREY, COLOUR_DARK_YELLOW, COLOUR_DARK_YELLOW ) },
+ { THEME_WC(WC_RESEARCH), 5189, COLOURS_3(COLOUR_GREY, COLOUR_DARK_YELLOW, COLOUR_DARK_YELLOW ) },
+ { THEME_WC(WC_VIEWPORT), 5191, COLOURS_3(COLOUR_DARK_BROWN, COLOUR_DARK_BROWN, COLOUR_DARK_BROWN ) },
+ { THEME_WC(WC_MAPGEN), 5214, COLOURS_3(COLOUR_DARK_GREEN, COLOUR_DARK_BROWN, COLOUR_DARK_BROWN ) },
+ { THEME_WC(WC_LOADSAVE), 5222, COLOURS_3(COLOUR_LIGHT_BLUE, COLOUR_LIGHT_BLUE, COLOUR_LIGHT_BLUE ) },
+ { THEME_WC(WC_LOADSAVE_OVERWRITE_PROMPT), 5227, COLOURS_1(TRANSLUCENT(COLOUR_BORDEAUX_RED) ) },
+ { THEME_WC(WC_TITLE_OPTIONS), 5251, COLOURS_3(TRANSLUCENT(COLOUR_DARK_GREEN), TRANSLUCENT(COLOUR_DARK_GREEN), TRANSLUCENT(COLOUR_DARK_GREEN) ) },
+ { THEME_WC(WC_LAND_RIGHTS), 5196, COLOURS_3(COLOUR_DARK_YELLOW, COLOUR_DARK_YELLOW, COLOUR_DARK_YELLOW ) },
+ { THEME_WC(WC_THEMES), 5218, COLOURS_3(COLOUR_GREY, COLOUR_DARK_GREEN, COLOUR_DARK_GREEN ) },
+ { THEME_WC(WC_STAFF), 5207, COLOURS_3(COLOUR_GREY, COLOUR_LIGHT_PURPLE, COLOUR_LIGHT_PURPLE ) },
+ { THEME_WC(WC_EDITOR_TRACK_BOTTOM_TOOLBAR), 5247, COLOURS_3(TRANSLUCENT(COLOUR_LIGHT_BLUE), TRANSLUCENT(COLOUR_LIGHT_BLUE), TRANSLUCENT(COLOUR_LIGHT_BLUE) ) },
+ { THEME_WC(WC_EDITOR_SCENARIO_BOTTOM_TOOLBAR), 5248, COLOURS_3(TRANSLUCENT(COLOUR_LIGHT_BROWN), TRANSLUCENT(COLOUR_LIGHT_BROWN), TRANSLUCENT(COLOUR_MOSS_GREEN) ) },
+ { THEME_WC(WC_TITLE_EDITOR), 5433, COLOURS_3(COLOUR_GREY, COLOUR_OLIVE_GREEN, COLOUR_OLIVE_GREEN ) },
+};
+
+#pragma endregion
+
+#pragma region Pre-defined Themes
+
+#define COLOURS_RCT1(c0, c1, c2, c3, c4, c5) { { (c0), (c1), (c2), (c3), (c4), (c5) } }
+
+UIThemeEntry PredefinedThemeRCT1_Entries[] =
+{
+ { WC_TOP_TOOLBAR, COLOURS_RCT1(COLOUR_GREY, COLOUR_GREY, COLOUR_GREY, COLOUR_GREY, COLOUR_BLACK, COLOUR_BLACK) },
+ { WC_BOTTOM_TOOLBAR, COLOURS_RCT1(TRANSLUCENT(COLOUR_GREY), TRANSLUCENT(COLOUR_GREY), COLOUR_BLACK, COLOUR_YELLOW, COLOUR_BLACK, COLOUR_BLACK) },
+ { WC_RIDE, COLOURS_RCT1(COLOUR_BORDEAUX_RED, COLOUR_GREY, COLOUR_SATURATED_GREEN, COLOUR_BLACK, COLOUR_BLACK, COLOUR_BLACK) },
+ { WC_RIDE_LIST, COLOURS_RCT1(COLOUR_BORDEAUX_RED, COLOUR_GREY, COLOUR_GREY, COLOUR_BLACK, COLOUR_BLACK, COLOUR_BLACK) },
+ { WC_CONSTRUCT_RIDE, COLOURS_RCT1(COLOUR_BORDEAUX_RED, COLOUR_GREY, COLOUR_GREY, COLOUR_BLACK, COLOUR_BLACK, COLOUR_BLACK) },
+ { WC_PEEP, COLOURS_RCT1(COLOUR_LIGHT_BROWN, COLOUR_BORDEAUX_RED, COLOUR_BORDEAUX_RED, COLOUR_BLACK, COLOUR_BLACK, COLOUR_BLACK) },
+ { WC_GUEST_LIST, COLOURS_RCT1(COLOUR_LIGHT_BROWN, COLOUR_BORDEAUX_RED, COLOUR_BORDEAUX_RED, COLOUR_BLACK, COLOUR_BLACK, COLOUR_BLACK) },
+ { WC_STAFF_LIST, COLOURS_RCT1(COLOUR_DARK_GREEN, COLOUR_LIGHT_PURPLE, COLOUR_LIGHT_PURPLE, COLOUR_BLACK, COLOUR_BLACK, COLOUR_BLACK) },
+ { WC_FINANCES, COLOURS_RCT1(COLOUR_LIGHT_PURPLE, COLOUR_GREY, COLOUR_GREY, COLOUR_BLACK, COLOUR_BLACK, COLOUR_BLACK) },
+ { WC_TITLE_MENU, COLOURS_RCT1(TRANSLUCENT(COLOUR_GREY), TRANSLUCENT(COLOUR_GREY), TRANSLUCENT(COLOUR_GREY), COLOUR_BLACK, COLOUR_BLACK, COLOUR_BLACK) },
+ { WC_TITLE_EXIT, COLOURS_RCT1(TRANSLUCENT(COLOUR_GREY), TRANSLUCENT(COLOUR_GREY), TRANSLUCENT(COLOUR_GREY), COLOUR_BLACK, COLOUR_BLACK, COLOUR_BLACK) },
+ { WC_NEW_CAMPAIGN, COLOURS_RCT1(COLOUR_LIGHT_PURPLE, COLOUR_LIGHT_PURPLE, COLOUR_GREY, COLOUR_BLACK, COLOUR_BLACK, COLOUR_BLACK) },
+ { WC_TITLE_OPTIONS, COLOURS_RCT1(TRANSLUCENT(COLOUR_GREY), TRANSLUCENT(COLOUR_GREY), TRANSLUCENT(COLOUR_GREY), COLOUR_BLACK, COLOUR_BLACK, COLOUR_BLACK) },
+ { WC_STAFF, COLOURS_RCT1(COLOUR_DARK_GREEN, COLOUR_LIGHT_PURPLE, COLOUR_LIGHT_PURPLE, COLOUR_BLACK, COLOUR_BLACK, COLOUR_BLACK) },
+ THEME_DEF_END
+};
+
+UIThemeEntry PredefinedThemeRCT2_Entries[] =
+{
+ THEME_DEF_END
+};
+
+const UITheme PredefinedThemeRCT1 = { "RCT1", PredefinedThemeRCT1_Entries, UITHEME_FLAG_PREDEFINED };
+const UITheme PredefinedThemeRCT2 = { "RCT2", PredefinedThemeRCT2_Entries, UITHEME_FLAG_PREDEFINED };
+
+const UITheme * PredefinedThemes[] = {
+ &PredefinedThemeRCT1,
+ &PredefinedThemeRCT2,
+ nullptr
+};
+
+#pragma endregion
+
+static const WindowThemeDesc * GetWindowThemeDescriptor(rct_windowclass windowClass)
+{
+ for (size_t i = 0; i < Util::CountOf(WindowThemeDescriptors); i++)
+ {
+ const WindowThemeDesc * desc = &WindowThemeDescriptors[i];
+ if (desc->WindowClass == windowClass)
+ {
+ return desc;
+ }
+ }
+ return nullptr;
+}
+
+static const WindowThemeDesc * GetWindowThemeDescriptor(const utf8 * windowClassSZ)
+{
+ for (size_t i = 0; i < Util::CountOf(WindowThemeDescriptors); i++)
+ {
+ const WindowThemeDesc * desc = &WindowThemeDescriptors[i];
+ if (String::Equals(desc->WindowClassSZ, windowClassSZ))
+ {
+ return desc;
+ }
+ }
+ return nullptr;
+}
+
+static void ThrowThemeLoadException()
+{
+ throw Exception("Invalid JSON UI theme entry.");
+}
+
+#pragma region UIThemeEntry
+
+json_t * UIThemeEntry::ToJson() const
+{
+ const WindowThemeDesc * wtDesc = GetWindowThemeDescriptor(WindowClass);
+
+ json_t * jsonColours = json_array();
+ for (uint8 i = 0; i < wtDesc->NumColours; i++) {
+ colour_t colour = Theme.Colours[i];
+ json_array_append_new(jsonColours, json_integer(colour));
+ }
+
+ json_t * jsonEntry = json_object();
+ json_object_set_new(jsonEntry, "colours", jsonColours);
+
+ return jsonEntry;
+}
+
+UIThemeEntry * UIThemeEntry::FromJson(const WindowThemeDesc * wtDesc, const json_t * json)
+{
+ json_t * jsonColours = json_object_get(json, "colours");
+ if (jsonColours == nullptr)
+ {
+ ThrowThemeLoadException();
+ }
+
+ uint8 numColours = (uint8)json_array_size(jsonColours);
+ numColours = Math::Min(numColours, wtDesc->NumColours);
+
+ UIThemeEntry * result = Memory::Allocate();
+ result->WindowClass = wtDesc->WindowClass;
+ result->Theme = wtDesc->DefaultTheme;
+
+ for (uint8 i = 0; i < numColours; i++)
+ {
+ result->Theme.Colours[i] = (colour_t)json_integer_value(json_array_get(jsonColours, i));
+ }
+
+ return result;
+}
+
+UITheme * UITheme::Create(const utf8 * name)
+{
+ UITheme * theme = Memory::Allocate();
+ theme->Name = String::Duplicate(name);
+ theme->Entries = Memory::AllocateArray(1);
+ theme->Entries[0].WindowClass = 255;
+ theme->Flags = 0;
+ return theme;
+}
+
+#pragma endregion
+
+#pragma region UITheme
+
+void UITheme::Free(UITheme * theme)
+{
+ assert(!(theme->Flags & UITHEME_FLAG_PREDEFINED));
+
+ Memory::Free(theme->Name);
+ Memory::Free(theme->Entries);
+}
+
+void UITheme::SetEntry(const UIThemeEntry * newEntry)
+{
+ // Try to replace existing entry
+ size_t numEntries = 0;
+ for (UIThemeEntry * entry = Entries; entry->WindowClass != 0xFF; entry++)
+ {
+ if (entry->WindowClass == newEntry->WindowClass)
+ {
+ *entry = *newEntry;
+ return;
+ }
+
+ numEntries++;
+ }
+
+ // Increase size of entry list
+ Entries = Memory::ReallocateArray(Entries, numEntries + 2);
+ Entries[numEntries] = *newEntry;
+ Entries[numEntries + 1].WindowClass = 255;
+}
+
+void UITheme::RemoveEntry(rct_windowclass windowClass)
+{
+ // Remove existing entry
+ bool found = false;
+ for (UIThemeEntry * entry = Entries; entry->WindowClass != 0xFF; entry++)
+ {
+ if (entry->WindowClass == windowClass)
+ {
+ for (UIThemeEntry * entry2 = entry; entry2->WindowClass != 0xFF; entry2++)
+ {
+ *entry2 = *(entry2 + 1);
+ }
+ found = true;
+ break;
+ }
+ }
+ if (!found) return;
+
+ // Reduce size of entry list
+ size_t numEntries = 0;
+ for (UIThemeEntry * entry = Entries; entry->WindowClass != 0xFF; entry++)
+ {
+ numEntries++;
+ }
+ Entries = Memory::ReallocateArray(Entries, numEntries);
+}
+
+json_t * UITheme::ToJson() const
+{
+ // Create entries
+ json_t * jsonEntries = json_object();
+ for (UIThemeEntry * entry = Entries; entry->WindowClass != 0xFF; entry++)
+ {
+ const WindowThemeDesc * wtDesc = GetWindowThemeDescriptor(entry->WindowClass);
+ json_object_set_new(jsonEntries, wtDesc->WindowClassSZ, entry->ToJson());
+ }
+
+ // Create theme object
+ json_t * jsonTheme = json_object();
+ json_object_set_new(jsonTheme, "name", json_string(Name));
+ json_object_set_new(jsonTheme, "entries", jsonEntries);
+
+ json_object_set_new(jsonTheme, "useLightsRide", json_boolean(Flags & UITHEME_FLAG_USE_LIGHTS_RIDE));
+ json_object_set_new(jsonTheme, "useLightsPark", json_boolean(Flags & UITHEME_FLAG_USE_LIGHTS_PARK));
+ json_object_set_new(jsonTheme,
+ "useAltScenarioSelectFont",
+ json_boolean(Flags & UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT));
+
+ return jsonTheme;
+}
+
+bool UITheme::WriteToFile(const utf8 * path) const
+{
+ const char * jsonOutput;
+
+ // Serialise theme object as JSON
+ json_t * jsonTheme = ToJson();
+ jsonOutput = json_dumps(jsonTheme, JSON_INDENT(4));
+ json_decref(jsonTheme);
+
+ try
+ {
+ auto fs = FileStream(path, FILE_MODE_WRITE);
+ size_t jsonOutputSize = String::SizeOf(jsonOutput);
+ fs.Write(jsonOutput, jsonOutputSize);
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ log_error("Unable to save %s: %s", path, ex.GetMessage());
+ return false;
+ }
+}
+
+UITheme * UITheme::FromJson(const json_t * json)
+{
+ const char * themeName = json_string_value(json_object_get(json, "name"));
+ if (themeName == nullptr)
+ {
+ ThrowThemeLoadException();
+ }
+
+ json_t * jsonEntries = json_object_get(json, "entries");
+ size_t numEntries = json_object_size(jsonEntries);
+ if (numEntries == 0)
+ {
+ ThrowThemeLoadException();
+ }
+
+ UITheme * result = nullptr;
+ try
+ {
+ result = UITheme::Create(themeName);
+
+ if (json_is_true(json_object_get(json, "useLightsRide")))
+ {
+ result->Flags |= UITHEME_FLAG_USE_LIGHTS_RIDE;
+ }
+ if (json_is_true(json_object_get(json, "useLightsPark")))
+ {
+ result->Flags |= UITHEME_FLAG_USE_LIGHTS_PARK;
+ }
+ if (json_is_true(json_object_get(json, "useAltScenarioSelectFont")))
+ {
+ result->Flags |= UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT;
+ }
+
+ const char * jkey;
+ json_t * jvalue;
+ size_t i = 0;
+ json_object_foreach(jsonEntries, jkey, jvalue)
+ {
+ const WindowThemeDesc * wtDesc = GetWindowThemeDescriptor(jkey);
+ if (wtDesc == nullptr) continue;
+
+ UIThemeEntry * entry = UIThemeEntry::FromJson(wtDesc, jvalue);
+ result->SetEntry(entry);
+ Memory::Free(entry);
+ }
+
+ return result;
+ }
+ catch (Exception ex)
+ {
+ UITheme::Free(result);
+ throw ex;
+ }
+}
+
+UITheme * UITheme::FromFile(const utf8 * path)
+{
+ json_t * json = nullptr;
+ UITheme * result = nullptr;
+ try
+ {
+ auto fs = FileStream(path, FILE_MODE_OPEN);
+
+ size_t fileLength = (size_t)fs.GetLength();
+ if (fileLength > MAX_THEME_SIZE)
+ {
+ throw IOException("Language file too large.");
+ }
+
+ utf8 * fileData = Memory::Allocate(fileLength + 1);
+ fs.Read(fileData, fileLength);
+ fileData[fileLength] = '\0';
+
+ json_error_t jsonLoadError;
+ json = json_loads(fileData, 0, &jsonLoadError);
+ Memory::Free(fileData);
+
+ if (json != nullptr)
+ {
+ result = UITheme::FromJson(json);
+ }
+ }
+ catch (Exception ex)
+ {
+ result = nullptr;
+ }
+
+ json_decref(json);
+ return result;
+}
+
+#pragma endregion
+
diff --git a/src/rct2.h b/src/rct2.h
index e3a3af6300..7c84aaeab0 100644
--- a/src/rct2.h
+++ b/src/rct2.h
@@ -59,6 +59,7 @@ typedef wchar_t utf16;
typedef utf16* utf16string;
typedef uint32 codepoint_t;
+typedef uint8 colour_t;
#define rol8(x, shift) (((uint8)(x) << (shift)) | ((uint8)(x) >> (8 - (shift))))
#define ror8(x, shift) (((uint8)(x) >> (shift)) | ((uint8)(x) << (8 - (shift))))