diff --git a/openrct2.vcxproj b/openrct2.vcxproj
index a3867bc4d0..674ef44906 100644
--- a/openrct2.vcxproj
+++ b/openrct2.vcxproj
@@ -101,6 +101,7 @@
+
@@ -434,6 +435,7 @@
+
diff --git a/src/core/File.cpp b/src/core/File.cpp
new file mode 100644
index 0000000000..195136cf6d
--- /dev/null
+++ b/src/core/File.cpp
@@ -0,0 +1,44 @@
+#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 "Console.hpp"
+#include "File.h"
+#include "FileStream.hpp"
+#include "String.hpp"
+
+namespace File
+{
+ void * ReadAllBytes(const utf8 * path, size_t * length)
+ {
+ void * result = nullptr;
+
+ FileStream fs = FileStream(path, FILE_MODE_OPEN);
+ uint64 fsize = fs.GetLength();
+ if (fsize > SIZE_MAX)
+ {
+ std::string message = String::StdFormat("'%s' exceeds maximum length of %lld bytes.", SIZE_MAX);
+ throw IOException(message);
+ }
+ else
+ {
+ result = fs.ReadArray(fsize);
+ }
+ *length = fsize;
+ return result;
+ }
+}
diff --git a/src/core/File.h b/src/core/File.h
new file mode 100644
index 0000000000..a911c3fedc
--- /dev/null
+++ b/src/core/File.h
@@ -0,0 +1,24 @@
+#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"
+
+namespace File
+{
+ void * ReadAllBytes(const utf8 * path, size_t * length);
+}
diff --git a/src/core/IStream.hpp b/src/core/IStream.hpp
index ac49112b46..73a92bf73d 100644
--- a/src/core/IStream.hpp
+++ b/src/core/IStream.hpp
@@ -116,4 +116,5 @@ class IOException : public Exception
{
public:
IOException(const char * message) : Exception(message) { }
+ IOException(const std::string &message) : Exception(message) { }
};
diff --git a/src/core/String.cpp b/src/core/String.cpp
index 2240ffd32f..2a0223da3c 100644
--- a/src/core/String.cpp
+++ b/src/core/String.cpp
@@ -34,6 +34,15 @@ namespace String
else return std::string(str);
}
+ std::string StdFormat(const utf8 * format, ...)
+ {
+ va_list args;
+ va_start(args, format);
+ const utf8 * buffer = Format_VA(format, args);
+ va_end(args);
+ return ToStd(buffer);
+ }
+
bool IsNullOrEmpty(const utf8 * str)
{
return str == nullptr || str[0] == '\0';
@@ -150,6 +159,68 @@ namespace String
return buffer;
}
+ utf8 * Format(const utf8 * format, ...)
+ {
+ va_list args;
+ va_start(args, format);
+ utf8 * result = Format_VA(format, args);
+ va_end(args);
+ return result;
+ }
+
+ utf8 * Format_VA(const utf8 * format, va_list args)
+ {
+ va_list args1, args2;
+ va_copy(args1, args);
+ va_copy(args2, args);
+
+ // Try to format to a initial buffer, enlarge if not big enough
+ size_t bufferSize = 4096;
+ utf8 * buffer = Memory::Allocate(bufferSize);
+
+ // Start with initial buffer
+ int len = vsnprintf(buffer, bufferSize, format, args);
+ if (len < 0)
+ {
+ Memory::Free(buffer);
+ va_end(args1);
+ va_end(args2);
+
+ // An error occured...
+ return nullptr;
+ }
+
+ size_t requiredSize = (size_t)len + 1;
+ if (requiredSize > bufferSize)
+ {
+ // Try again with bigger buffer
+ buffer = Memory::Reallocate(buffer, bufferSize);
+ int len = vsnprintf(buffer, bufferSize, format, args);
+ if (len < 0)
+ {
+ Memory::Free(buffer);
+ va_end(args1);
+ va_end(args2);
+
+ // An error occured...
+ return nullptr;
+ }
+ }
+ else
+ {
+ // Reduce buffer size to only what was required
+ bufferSize = requiredSize;
+ buffer = Memory::Reallocate(buffer, bufferSize);
+ }
+
+ // Ensure buffer is terminated
+ buffer[bufferSize - 1] = '\0';
+
+ va_end(args1);
+ va_end(args2);
+ return buffer;
+ }
+
utf8 * AppendFormat(utf8 * buffer, size_t bufferSize, const utf8 * format, ...)
{
va_list args;
diff --git a/src/core/String.hpp b/src/core/String.hpp
index db0eaeb058..65d94e9c6b 100644
--- a/src/core/String.hpp
+++ b/src/core/String.hpp
@@ -24,6 +24,7 @@ namespace String
constexpr const utf8 * Empty = "";
std::string ToStd(const utf8 * str);
+ std::string StdFormat(const utf8 * format, ...);
bool IsNullOrEmpty(const utf8 * str);
bool Equals(const utf8 * a, const utf8 * b, bool ignoreCase = false);
@@ -44,6 +45,8 @@ namespace String
utf8 * Set(utf8 * buffer, size_t bufferSize, const utf8 * src, size_t srcSize);
utf8 * Append(utf8 * buffer, size_t bufferSize, const utf8 * src);
utf8 * Format(utf8 * buffer, size_t bufferSize, const utf8 * format, ...);
+ utf8 * Format(const utf8 * format, ...);
+ utf8 * Format_VA(const utf8 * format, va_list args);
utf8 * AppendFormat(utf8 * buffer, size_t bufferSize, const utf8 * format, ...);
utf8 * Duplicate(const utf8 * src);
diff --git a/src/title/TitleSequence.cpp b/src/title/TitleSequence.cpp
index 6e966eea59..a49e5179dc 100644
--- a/src/title/TitleSequence.cpp
+++ b/src/title/TitleSequence.cpp
@@ -19,6 +19,7 @@
#include "../common.h"
#include "../core/Collections.hpp"
#include "../core/Console.hpp"
+#include "../core/File.h"
#include "../core/FileScanner.h"
#include "../core/FileStream.hpp"
#include "../core/Guard.hpp"
@@ -30,6 +31,11 @@
#include "../core/Zip.h"
#include "TitleSequence.h"
+extern "C"
+{
+ #include "../platform/platform.h"
+}
+
static std::vector GetSaves(const utf8 * path);
static std::vector GetSaves(IZipArchive * zip);
static std::vector LegacyScriptRead(utf8 * script, size_t scriptLength, std::vector saves);
@@ -194,6 +200,72 @@ extern "C"
Memory::Free(script);
return success;
}
+
+ bool TileSequenceAddPark(TitleSequence * seq, const utf8 * path, const utf8 * name)
+ {
+ // Determine new path (relative for zip)
+ utf8 dstPath[260];
+ if (seq->IsZip)
+ {
+ String::Set(dstPath, sizeof(dstPath), name);
+ }
+ else
+ {
+ String::Set(dstPath, sizeof(dstPath), seq->Path);
+ Path::Append(dstPath, sizeof(dstPath), name);
+ }
+
+ // Get new save index
+ size_t index = SIZE_MAX;
+ for (size_t i = 0; i < seq->NumSaves; i++)
+ {
+ if (String::Equals(seq->Saves[i], path, true))
+ {
+ index = i;
+ break;
+ }
+ }
+ if (index == SIZE_MAX)
+ {
+ seq->Saves = Memory::ReallocateArray(seq->Saves, seq->NumSaves + 1);
+ Guard::Assert(seq->Saves != nullptr, GUARD_LINE);
+ index = seq->NumSaves;
+ seq->NumSaves++;
+ }
+ seq->Saves[index] = String::Duplicate(dstPath);
+
+ if (seq->IsZip)
+ {
+ try
+ {
+ size_t fsize;
+ void * fdata = File::ReadAllBytes(path, &fsize);
+
+ IZipArchive * zip = Zip::TryOpen(seq->Path, ZIP_ACCESS_WRITE);
+ if (zip == nullptr)
+ {
+ Console::Error::WriteLine("Unable to open '%s'", seq->Path);
+ return false;
+ }
+ zip->SetFileData(dstPath, fdata, fsize);
+ delete zip;
+ Memory::Free(fdata);
+ }
+ catch (Exception ex)
+ {
+ Console::Error::WriteLine(ex.GetMessage());
+ }
+ }
+ else
+ {
+ if (!platform_file_copy(path, dstPath, true))
+ {
+ Console::Error::WriteLine("Unable to copy '%s' to '%s'", path, dstPath);
+ return false;
+ }
+ }
+ return true;
+ }
}
static std::vector GetSaves(const utf8 * directory)
diff --git a/src/title/TitleSequence.h b/src/title/TitleSequence.h
index 77be35e7e3..a4466e4309 100644
--- a/src/title/TitleSequence.h
+++ b/src/title/TitleSequence.h
@@ -91,6 +91,7 @@ extern "C"
TitleSequenceParkHandle * TitleSequenceGetParkHandle(TitleSequence * seq, size_t index);
void TitleSequenceCloseParkHandle(TitleSequenceParkHandle * handle);
bool TileSequenceSave(TitleSequence * seq);
+ bool TileSequenceAddPark(TitleSequence * seq, const utf8 * path, const utf8 * name);
bool TitleSequenceIsLoadCommand(const TitleCommand * command);
#ifdef __cplusplus
diff --git a/src/windows/title_editor.c b/src/windows/title_editor.c
index b459813ba9..06d3a516dc 100644
--- a/src/windows/title_editor.c
+++ b/src/windows/title_editor.c
@@ -1022,7 +1022,7 @@ static void window_title_editor_add_park_callback(int result, const utf8 * path)
return;
}
- // title_sequence_add_save(gCurrentTitleSequence, path, newName);
+ TileSequenceAddPark(_editingTitleSequence, path, filename);
}
static void window_title_editor_save_sequence()