diff --git a/openrct2.vcxproj b/openrct2.vcxproj
index 738db609f1..92ae3037be 100644
--- a/openrct2.vcxproj
+++ b/openrct2.vcxproj
@@ -324,6 +324,7 @@
+
@@ -568,6 +569,7 @@
+
diff --git a/src/config.c b/src/config.c
index cebc91efc3..8ca2707d32 100644
--- a/src/config.c
+++ b/src/config.c
@@ -1211,6 +1211,8 @@ static void * get_zip_data(zip_t * zip, const char * name, size_t * outSize)
return data;
}
+#include "title/TitleSequence.h"
+
static void title_sequence_open(const char *path, const char *customName)
{
utf8 titlePath[MAX_PATH];
@@ -1219,6 +1221,9 @@ static void title_sequence_open(const char *path, const char *customName)
int fileEnumHandle, i, preset;
char parts[3 * 128], *token, *part1, *part2;
+ TitleSequence * seq = LoadTitleSequence(path);
+ FreeTitleSequence(seq);
+
int error;
zip_t * zip = zip_open(path, ZIP_RDONLY, &error);
if (zip == NULL) {
diff --git a/src/core/Collections.hpp b/src/core/Collections.hpp
index 4caceb6dd1..c937bfed26 100644
--- a/src/core/Collections.hpp
+++ b/src/core/Collections.hpp
@@ -18,6 +18,7 @@
#include
#include "../common.h"
+#include "Memory.hpp"
#include "String.hpp"
namespace Collections
@@ -100,5 +101,24 @@ namespace Collections
}
}
+ template
+ static typename TCollection::value_type * ToArray(const TCollection &collection)
+ {
+ size_t count = collection.size();
+ if (count == 0)
+ {
+ return nullptr;
+ }
+
+ auto * items = Memory::AllocateArray(count);
+ size_t i = 0;
+ for (const auto &item : collection)
+ {
+ items[i] = item;
+ i++;
+ }
+ return items;
+ }
+
#pragma endregion
}
diff --git a/src/core/Path.cpp b/src/core/Path.cpp
index f0e6735381..ad729785c1 100644
--- a/src/core/Path.cpp
+++ b/src/core/Path.cpp
@@ -71,6 +71,16 @@ namespace Path
lastPathSeperator + 1;
}
+ utf8 * GetFileNameWithoutExtension(const utf8 * path)
+ {
+ size_t maxSize = String::SizeOf(path) + 1;
+ utf8 * result = Memory::Allocate(maxSize);
+ GetFileNameWithoutExtension(result, maxSize, path);
+ size_t reducedSize = String::SizeOf(path) + 1;
+ result = Memory::Reallocate(result, reducedSize);
+ return result;
+ }
+
utf8 * GetFileNameWithoutExtension(utf8 * buffer, size_t bufferSize, const utf8 * path)
{
path = GetFileName(path);
diff --git a/src/core/Path.hpp b/src/core/Path.hpp
index 14353a56f7..ef0ca98fc6 100644
--- a/src/core/Path.hpp
+++ b/src/core/Path.hpp
@@ -26,6 +26,7 @@ namespace Path
utf8 * Append(utf8 * buffer, size_t bufferSize, const utf8 * src);
utf8 * GetDirectory(utf8 * buffer, size_t bufferSize, const utf8 * path);
const utf8 * GetFileName(const utf8 * path);
+ utf8 * GetFileNameWithoutExtension(const utf8 * path);
utf8 * GetFileNameWithoutExtension(utf8 * buffer, size_t bufferSize, const utf8 * path);
const utf8 * GetExtension(const utf8 * path);
utf8 * GetAbsolute(utf8 * buffer, size_t bufferSize, const utf8 * relativePath);
diff --git a/src/interface/title_sequences.c b/src/interface/title_sequences.c
index f94fd0daf7..0ccd075188 100644
--- a/src/interface/title_sequences.c
+++ b/src/interface/title_sequences.c
@@ -17,6 +17,7 @@
#include "../localisation/string_ids.h"
#include "../rct2.h"
#include "../title.h"
+#include "../title/TitleSequence.h"
#include "../util/util.h"
#include "title_sequences.h"
#include "window.h"
diff --git a/src/title.c b/src/title.c
index 130b5765e3..113f0ddb34 100644
--- a/src/title.c
+++ b/src/title.c
@@ -35,6 +35,7 @@
#include "scenario.h"
#include "ScenarioRepository.h"
#include "ScenarioSources.h"
+#include "title/TitleSequence.h"
#include "util/util.h"
#include "world/climate.h"
#include "world/map.h"
diff --git a/src/title.h b/src/title.h
index 244f1b994b..ad93583be4 100644
--- a/src/title.h
+++ b/src/title.h
@@ -20,21 +20,6 @@
#include
#include "drawing/drawing.h"
-enum {
- TITLE_SCRIPT_WAIT,
- TITLE_SCRIPT_LOADMM,
- TITLE_SCRIPT_LOCATION,
- TITLE_SCRIPT_ROTATE,
- TITLE_SCRIPT_ZOOM,
- TITLE_SCRIPT_RESTART,
- TITLE_SCRIPT_LOAD,
- TITLE_SCRIPT_END,
- TITLE_SCRIPT_SPEED,
- TITLE_SCRIPT_LOOP,
- TITLE_SCRIPT_ENDLOOP,
- TITLE_SCRIPT_LOADRCT1,
-};
-
extern bool gTitleHideVersionInfo;
extern sint32 gTitleScriptCommand;
diff --git a/src/title/TitleSequence.cpp b/src/title/TitleSequence.cpp
new file mode 100644
index 0000000000..fafe93b9e7
--- /dev/null
+++ b/src/title/TitleSequence.cpp
@@ -0,0 +1,267 @@
+#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
+#include
+#include
+#include "../core/Collections.hpp"
+#include "../core/Math.hpp"
+#include "../core/Memory.hpp"
+#include "../core/Path.hpp"
+#include "../core/String.hpp"
+#include "TitleSequence.h"
+
+static std::vector GetSaves(zip_t * zip);
+static std::vector LegacyScriptRead(utf8 * script, size_t scriptLength, std::vector saves);
+static void LegacyScriptGetLine(SDL_RWops * file, char * parts);
+static void * GetZipFileData(zip_t * zip, const char * name, size_t * outSize);
+
+extern "C"
+{
+ TitleSequence * LoadTitleSequence(const utf8 * path)
+ {
+ int error;
+ zip_t * zip = zip_open(path, ZIP_RDONLY, &error);
+ if (zip == nullptr)
+ {
+ // Unable to open zip
+ return nullptr;
+ }
+
+ size_t scriptLength;
+ char * script = (char *)GetZipFileData(zip, "script.txt", &scriptLength);
+ if (script == NULL) {
+ // Unable to open script
+ zip_close(zip);
+ return nullptr;
+ }
+
+ std::vector saves = GetSaves(zip);
+ std::vector commands = LegacyScriptRead(script, scriptLength, saves);
+
+ TitleSequence * seq = Memory::Allocate();
+ seq->Name = Path::GetFileNameWithoutExtension(path);
+ seq->Path = String::Duplicate(path);
+ seq->NumSaves = saves.size();
+ seq->Saves = Collections::ToArray(saves);
+ seq->NumCommands = commands.size();
+ seq->Commands = Collections::ToArray(commands);
+ return seq;
+ }
+
+ void FreeTitleSequence(TitleSequence * seq)
+ {
+ Memory::Free(seq->Name);
+ Memory::Free(seq->Path);
+ Memory::Free(seq->Commands);
+ for (size_t i = 0; i < seq->NumSaves; i++)
+ {
+ Memory::Free(seq->Saves[i]);
+ }
+ Memory::Free(seq->Saves);
+ }
+}
+
+static std::vector GetSaves(zip_t * zip)
+{
+ std::vector saves;
+ int numFiles = zip_get_num_files(zip);
+ for (int i = 0; i < numFiles; i++)
+ {
+ const utf8 * name = zip_get_name(zip, i, ZIP_FL_ENC_GUESS);
+ const utf8 * ext = Path::GetExtension(name);
+ if (String::Equals(ext, ".sv6", true) ||
+ String::Equals(ext, ".sc6", true))
+ {
+ saves.push_back(String::Duplicate(name));
+ }
+ }
+ return saves;
+}
+
+static std::vector LegacyScriptRead(utf8 * script, size_t scriptLength, std::vector saves)
+{
+ std::vector commands;
+ SDL_RWops * file = SDL_RWFromMem(script, (int)scriptLength);
+ do {
+ char parts[3 * 128], *token, *part1, *part2;
+ LegacyScriptGetLine(file, parts);
+
+ token = &parts[0 * 128];
+ part1 = &parts[1 * 128];
+ part2 = &parts[2 * 128];
+ TitleCommand command = { 0 };
+ command.Type = TITLE_SCRIPT_UNDEFINED;
+
+ if (token[0] != 0)
+ {
+ if (_stricmp(token, "LOAD") == 0)
+ {
+ command.Type = TITLE_SCRIPT_LOAD;
+ command.SaveIndex = UINT8_MAX;
+ for (size_t i = 0; i < saves.size(); i++)
+ {
+ if (String::Equals(part1, saves[i], true))
+ {
+ command.SaveIndex = (uint8)i;
+ break;
+ }
+ }
+ }
+ else if (_stricmp(token, "LOCATION") == 0)
+ {
+ command.Type = TITLE_SCRIPT_LOCATION;
+ command.X = atoi(part1) & 0xFF;
+ command.Y = atoi(part2) & 0xFF;
+ }
+ else if (_stricmp(token, "ROTATE") == 0)
+ {
+ command.Type = TITLE_SCRIPT_ROTATE;
+ command.Rotations = atoi(part1) & 0xFF;
+ }
+ else if (_stricmp(token, "ZOOM") == 0)
+ {
+ command.Type = TITLE_SCRIPT_ZOOM;
+ command.Zoom = atoi(part1) & 0xFF;
+ }
+ else if (_stricmp(token, "SPEED") == 0)
+ {
+ command.Type = TITLE_SCRIPT_SPEED;
+ command.Speed = Math::Max(1, Math::Min(4, atoi(part1) & 0xFF));
+ }
+ else if (_stricmp(token, "WAIT") == 0)
+ {
+ command.Type = TITLE_SCRIPT_WAIT;
+ command.Seconds = atoi(part1) & 0xFF;
+ }
+ else if (_stricmp(token, "RESTART") == 0)
+ {
+ command.Type = TITLE_SCRIPT_RESTART;
+ }
+ else if (_stricmp(token, "END") == 0)
+ {
+ command.Type = TITLE_SCRIPT_END;
+ }
+ else if (_stricmp(token, "LOADMM") == 0)
+ {
+ command.Type = TITLE_SCRIPT_LOADMM;
+ }
+ else if (_stricmp(token, "LOADRCT1") == 0)
+ {
+ command.Type = TITLE_SCRIPT_LOADRCT1;
+ command.SaveIndex = atoi(part1) & 0xFF;
+ }
+ }
+ if (command.Type != TITLE_SCRIPT_UNDEFINED)
+ {
+ commands.push_back(command);
+ }
+ } while (SDL_RWtell(file) < (int)scriptLength);
+ SDL_RWclose(file);
+
+ return commands;
+}
+
+static void LegacyScriptGetLine(SDL_RWops * file, char * parts)
+{
+ for (int i = 0; i < 3; i++)
+ {
+ parts[i * 128] = 0;
+ }
+ int part = 0;
+ int cindex = 0;
+ int whitespace = 1;
+ int comment = 0;
+ int load = 0;
+ for (; part < 3;)
+ {
+ int c = 0;
+ if (SDL_RWread(file, &c, 1, 1) != 1)
+ {
+ c = EOF;
+ }
+ if (c == '\n' || c == '\r' || c == EOF)
+ {
+ parts[part * 128 + cindex] = 0;
+ return;
+ }
+ else if (c == '#')
+ {
+ parts[part * 128 + cindex] = 0;
+ comment = 1;
+ }
+ else if (c == ' ' && !comment && !load)
+ {
+ if (!whitespace)
+ {
+ if (part == 0 && cindex == 4 && _strnicmp(parts, "LOAD", 4) == 0)
+ {
+ load = true;
+ }
+ parts[part * 128 + cindex] = 0;
+ part++;
+ cindex = 0;
+ }
+ }
+ else if (!comment)
+ {
+ whitespace = 0;
+ if (cindex < 127)
+ {
+ parts[part * 128 + cindex] = c;
+ cindex++;
+ }
+ else
+ {
+ parts[part * 128 + cindex] = 0;
+ part++;
+ cindex = 0;
+ }
+ }
+ }
+}
+
+static void * GetZipFileData(zip_t * zip, const char * name, size_t * outSize)
+{
+ void * data = nullptr;
+ size_t dataSize = 0;
+
+ zip_stat_t zipFileStat;
+ if (zip_stat(zip, name, 0, &zipFileStat) == ZIP_ER_OK)
+ {
+ zip_file_t * zipFile = zip_fopen(zip, name, 0);
+ if (zipFile != nullptr)
+ {
+ if (zipFileStat.size < SIZE_MAX)
+ {
+ dataSize = zipFileStat.size;
+ data = malloc(dataSize);
+ size_t readBytes = zip_fread(zipFile, data, dataSize);
+ if (readBytes != dataSize)
+ {
+ free(data);
+ data = NULL;
+ dataSize = 0;
+ }
+ zip_fclose(zipFile);
+ }
+ }
+ }
+
+ if (outSize != NULL) *outSize = dataSize;
+ return data;
+}
diff --git a/src/title/TitleSequence.h b/src/title/TitleSequence.h
new file mode 100644
index 0000000000..4f4d061970
--- /dev/null
+++ b/src/title/TitleSequence.h
@@ -0,0 +1,75 @@
+#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"
+
+typedef struct TitleCommand
+{
+ uint8 Type;
+ union {
+ uint8 SaveIndex; // LOAD (this index is internal only)
+ struct // LOCATION
+ {
+ uint8 X;
+ uint8 Y;
+ };
+ uint8 Rotations; // ROTATE (counter-clockwise)
+ uint8 Zoom; // ZOOM
+ uint8 Speed; // SPEED
+ uint8 Seconds; // WAIT
+ };
+} TitleCommand;
+
+typedef struct TitleSequence
+{
+ const utf8 * Name;
+ const utf8 * Path;
+
+ size_t NumCommands;
+ TitleCommand * Commands;
+
+ size_t NumSaves;
+ utf8 * * Saves;
+} TitleSequence;
+
+enum TITLE_SCRIPT
+{
+ TITLE_SCRIPT_UNDEFINED = 0xFF,
+ TITLE_SCRIPT_WAIT = 0,
+ TITLE_SCRIPT_LOADMM,
+ TITLE_SCRIPT_LOCATION,
+ TITLE_SCRIPT_ROTATE,
+ TITLE_SCRIPT_ZOOM,
+ TITLE_SCRIPT_RESTART,
+ TITLE_SCRIPT_LOAD,
+ TITLE_SCRIPT_END,
+ TITLE_SCRIPT_SPEED,
+ TITLE_SCRIPT_LOOP,
+ TITLE_SCRIPT_ENDLOOP,
+ TITLE_SCRIPT_LOADRCT1,
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+ TitleSequence * LoadTitleSequence(const utf8 * path);
+ void FreeTitleSequence(TitleSequence * seq);
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/windows/title_command_editor.c b/src/windows/title_command_editor.c
index 50cd0e8460..34b686eab6 100644
--- a/src/windows/title_command_editor.c
+++ b/src/windows/title_command_editor.c
@@ -26,6 +26,7 @@
#include "../interface/themes.h"
#include "../interface/title_sequences.h"
#include "../title.h"
+#include "../title/TitleSequence.h"
#include "../util/util.h"
#include "dropdown.h"
diff --git a/src/windows/title_editor.c b/src/windows/title_editor.c
index 003d846652..94a6dffa6b 100644
--- a/src/windows/title_editor.c
+++ b/src/windows/title_editor.c
@@ -30,6 +30,7 @@
#include "../interface/themes.h"
#include "../sprites.h"
#include "../title.h"
+#include "../title/TitleSequence.h"
#include "../interface/title_sequences.h"
#include "error.h"
#include "../scenario.h"