mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-15 11:03:00 +01:00
Sanitize screenshot path
This commit is contained in:
@@ -116,6 +116,7 @@
|
||||
- Fix: [#8588] Guest list scrolling breaks above ~2000 guests.
|
||||
- Fix: [#8591] Game loop does not run at a consistent tick rate of 40 Hz.
|
||||
- Fix: [#8647] Marketing campaigns check for entry fees below £1 (original bug).
|
||||
- Fix: [#8598] Sanitize screenshot parknames.
|
||||
- Fix: [#8653] Crash when peeps attempt to enter a ride with no vehicles.
|
||||
- Fix: [#8720] Desync due to boats colliding with ghost pieces.
|
||||
- Fix: [#8736] Incomplete warning when all ride slots are full.
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "../audio/audio.h"
|
||||
#include "../core/Console.hpp"
|
||||
#include "../core/Imaging.h"
|
||||
#include "../core/Optional.hpp"
|
||||
#include "../drawing/Drawing.h"
|
||||
#include "../drawing/X8DrawingEngine.h"
|
||||
#include "../localisation/Localisation.h"
|
||||
@@ -28,10 +29,13 @@
|
||||
#include "../world/Surface.h"
|
||||
#include "Viewport.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
using namespace std::literals::string_literals;
|
||||
using namespace OpenRCT2;
|
||||
using namespace OpenRCT2::Drawing;
|
||||
|
||||
@@ -96,88 +100,93 @@ static void screenshot_get_rendered_palette(rct_palette* palette)
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t screenshot_get_next_path(char* path, size_t size)
|
||||
static std::string screenshot_get_park_name()
|
||||
{
|
||||
char buffer[512];
|
||||
format_string(buffer, sizeof(buffer), gParkName, &gParkNameArgs);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static opt::optional<std::string> screenshot_get_directory()
|
||||
{
|
||||
char screenshotPath[MAX_PATH];
|
||||
|
||||
platform_get_user_directory(screenshotPath, "screenshot", sizeof(screenshotPath));
|
||||
if (!platform_ensure_directory_exists(screenshotPath))
|
||||
|
||||
if (platform_ensure_directory_exists(screenshotPath))
|
||||
{
|
||||
log_error("Unable to save screenshots in OpenRCT2 screenshot directory.\n");
|
||||
return -1;
|
||||
return opt::make_optional<std::string>(screenshotPath);
|
||||
}
|
||||
|
||||
char park_name[128];
|
||||
format_string(park_name, 128, gParkName, &gParkNameArgs);
|
||||
log_error("Unable to save screenshots in OpenRCT2 screenshot directory.\n");
|
||||
return opt::nullopt;
|
||||
}
|
||||
|
||||
// Retrieve current time
|
||||
rct2_date currentDate;
|
||||
platform_get_date_local(¤tDate);
|
||||
rct2_time currentTime;
|
||||
platform_get_time_local(¤tTime);
|
||||
static std::tuple<rct2_date, rct2_time> screenshot_get_date_time()
|
||||
{
|
||||
rct2_date date;
|
||||
platform_get_date_local(&date);
|
||||
|
||||
#ifdef _WIN32
|
||||
// On NTFS filesystems, a colon (:) in a path
|
||||
// indicates you want to write a file stream
|
||||
// (hidden metadata). This will pass the
|
||||
// file_exists and fopen checks, since it is
|
||||
// technically valid. We don't want that, so
|
||||
// replace colons with hyphens in the park name.
|
||||
char* foundColon = park_name;
|
||||
while ((foundColon = strchr(foundColon, ':')) != nullptr)
|
||||
{
|
||||
*foundColon = '-';
|
||||
}
|
||||
#endif
|
||||
rct2_time time;
|
||||
platform_get_time_local(&time);
|
||||
|
||||
// Glue together path and filename
|
||||
safe_strcpy(path, screenshotPath, size);
|
||||
path_end_with_separator(path, size);
|
||||
auto fileNameCh = strchr(path, '\0');
|
||||
if (fileNameCh == nullptr)
|
||||
{
|
||||
log_error("Unable to generate a screenshot filename.");
|
||||
return -1;
|
||||
}
|
||||
const size_t leftBytes = size - strlen(path);
|
||||
return { date, time };
|
||||
}
|
||||
|
||||
static std::string screenshot_get_formatted_date_time()
|
||||
{
|
||||
auto [date, time] = screenshot_get_date_time();
|
||||
char formatted[64];
|
||||
snprintf(
|
||||
fileNameCh, leftBytes, "%s %d-%02d-%02d %02d-%02d-%02d.png", park_name, currentDate.year, currentDate.month,
|
||||
currentDate.day, currentTime.hour, currentTime.minute, currentTime.second);
|
||||
formatted, sizeof(formatted), "%4d-%02d-%02d %02d-%02d-%02d", date.year, date.month, date.day, time.hour, time.minute,
|
||||
time.second);
|
||||
return formatted;
|
||||
}
|
||||
|
||||
if (!platform_file_exists(path))
|
||||
static opt::optional<std::string> screenshot_get_next_path()
|
||||
{
|
||||
std::string dir, name, suffix = ".png", path;
|
||||
|
||||
auto screenshotDirectory = screenshot_get_directory();
|
||||
|
||||
if (screenshotDirectory == opt::nullopt)
|
||||
{
|
||||
return 0; // path ok
|
||||
return opt::nullopt;
|
||||
}
|
||||
|
||||
// multiple screenshots with same timestamp
|
||||
// might be possible when switching timezones
|
||||
// in the unlikely case that this does happen,
|
||||
// append (%d) to the filename and increment
|
||||
// this int32_t until it doesn't overwrite any
|
||||
// other file in the directory.
|
||||
int32_t i;
|
||||
for (i = 1; i < 1000; i++)
|
||||
{
|
||||
// Glue together path and filename
|
||||
snprintf(
|
||||
fileNameCh, leftBytes, "%s %d-%02d-%02d %02d-%02d-%02d (%d).png", park_name, currentDate.year, currentDate.month,
|
||||
currentDate.day, currentTime.hour, currentTime.minute, currentTime.second, i);
|
||||
auto parkName = screenshot_get_park_name();
|
||||
auto dateTime = screenshot_get_formatted_date_time();
|
||||
|
||||
if (!platform_file_exists(path))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
dir = *screenshotDirectory;
|
||||
name = parkName + " " + dateTime;
|
||||
|
||||
// Generate a path with a `tries` number
|
||||
auto path_composer = [&dir, &name, &suffix ](int tries) -> auto
|
||||
{
|
||||
auto composed_filename = platform_sanitise_filename(
|
||||
name + ((tries > 0) ? " ("s + std::to_string(tries) + ")" : ""s) + suffix);
|
||||
return dir + PATH_SEPARATOR + composed_filename;
|
||||
};
|
||||
|
||||
for (int tries = 0; tries < 100; tries++)
|
||||
{
|
||||
path = path_composer(tries);
|
||||
if (platform_file_exists(path.c_str()))
|
||||
continue;
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
log_error("You have too many saved screenshots saved at exactly the same date and time.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return opt::nullopt;
|
||||
};
|
||||
|
||||
std::string screenshot_dump_png(rct_drawpixelinfo* dpi)
|
||||
{
|
||||
// Get a free screenshot path
|
||||
char path[MAX_PATH] = "";
|
||||
if (screenshot_get_next_path(path, MAX_PATH) == -1)
|
||||
auto path = screenshot_get_next_path();
|
||||
|
||||
if (path == opt::nullopt)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
@@ -185,9 +194,9 @@ std::string screenshot_dump_png(rct_drawpixelinfo* dpi)
|
||||
rct_palette renderedPalette;
|
||||
screenshot_get_rendered_palette(&renderedPalette);
|
||||
|
||||
if (WriteDpiToFile(path, dpi, renderedPalette))
|
||||
if (WriteDpiToFile(path->c_str(), dpi, renderedPalette))
|
||||
{
|
||||
return std::string(path);
|
||||
return *path;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -197,9 +206,9 @@ std::string screenshot_dump_png(rct_drawpixelinfo* dpi)
|
||||
|
||||
std::string screenshot_dump_png_32bpp(int32_t width, int32_t height, const void* pixels)
|
||||
{
|
||||
// Get a free screenshot path
|
||||
char path[MAX_PATH] = "";
|
||||
if (screenshot_get_next_path(path, MAX_PATH) == -1)
|
||||
auto path = screenshot_get_next_path();
|
||||
|
||||
if (path == opt::nullopt)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
@@ -215,8 +224,8 @@ std::string screenshot_dump_png_32bpp(int32_t width, int32_t height, const void*
|
||||
image.Depth = 32;
|
||||
image.Stride = width * 4;
|
||||
image.Pixels = std::vector<uint8_t>(pixels8, pixels8 + pixelsLen);
|
||||
Imaging::WriteToFile(path, image, IMAGE_FORMAT::PNG_32);
|
||||
return std::string(path);
|
||||
Imaging::WriteToFile(path->c_str(), image, IMAGE_FORMAT::PNG_32);
|
||||
return *path;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
@@ -301,9 +310,8 @@ void screenshot_giant()
|
||||
|
||||
viewport_render(&dpi, &viewport, 0, 0, viewport.width, viewport.height);
|
||||
|
||||
// Get a free screenshot path
|
||||
char path[MAX_PATH];
|
||||
if (screenshot_get_next_path(path, MAX_PATH) == -1)
|
||||
auto path = screenshot_get_next_path();
|
||||
if (path == opt::nullopt)
|
||||
{
|
||||
log_error("Giant screenshot failed, unable to find a suitable destination path.");
|
||||
context_show_error(STR_SCREENSHOT_FAILED, STR_NONE);
|
||||
@@ -313,13 +321,13 @@ void screenshot_giant()
|
||||
rct_palette renderedPalette;
|
||||
screenshot_get_rendered_palette(&renderedPalette);
|
||||
|
||||
WriteDpiToFile(path, &dpi, renderedPalette);
|
||||
WriteDpiToFile(path->c_str(), &dpi, renderedPalette);
|
||||
|
||||
free(dpi.bits);
|
||||
|
||||
// Show user that screenshot saved successfully
|
||||
set_format_arg(0, rct_string_id, STR_STRING);
|
||||
set_format_arg(2, char*, path_get_filename(path));
|
||||
set_format_arg(2, char*, path_get_filename(path->c_str()));
|
||||
context_show_error(STR_SCREENSHOT_SAVED_AS, STR_NONE);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "../world/Climate.h"
|
||||
#include "platform.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
@@ -198,6 +199,24 @@ uint8_t platform_get_currency_value(const char* currCode)
|
||||
return CURRENCY_POUNDS;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
std::string platform_sanitise_filename(const std::string& path)
|
||||
{
|
||||
auto sanitised = path;
|
||||
|
||||
std::vector<std::string::value_type> prohibited = { '/' };
|
||||
|
||||
std::replace_if(
|
||||
sanitised.begin(), sanitised.end(),
|
||||
[&prohibited](const std::string::value_type& ch) {
|
||||
return std::find(prohibited.begin(), prohibited.end(), ch) != prohibited.end();
|
||||
},
|
||||
'_');
|
||||
|
||||
return sanitised;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef __ANDROID__
|
||||
float platform_get_default_scale()
|
||||
{
|
||||
|
||||
@@ -28,8 +28,10 @@
|
||||
# include "../util/Util.h"
|
||||
# include "platform.h"
|
||||
|
||||
# include <algorithm>
|
||||
# include <iterator>
|
||||
# include <lmcons.h>
|
||||
# include <memory>
|
||||
# include <psapi.h>
|
||||
# include <shlobj.h>
|
||||
# include <sys/stat.h>
|
||||
@@ -253,6 +255,22 @@ std::string platform_get_rct2_steam_dir()
|
||||
return "Rollercoaster Tycoon 2";
|
||||
}
|
||||
|
||||
std::string platform_sanitise_filename(const std::string& path)
|
||||
{
|
||||
auto sanitised = path;
|
||||
|
||||
std::vector<std::string::value_type> prohibited = { '<', '>', '*', '\\', ':', '|', '?', '"', '/' };
|
||||
|
||||
std::replace_if(
|
||||
sanitised.begin(), sanitised.end(),
|
||||
[](const std::string::value_type& ch) {
|
||||
return std::find(prohibited.begin(), prohibited.end(), ch) != prohibited.end();
|
||||
},
|
||||
'_');
|
||||
|
||||
return sanitised;
|
||||
}
|
||||
|
||||
uint16_t platform_get_locale_language()
|
||||
{
|
||||
CHAR langCode[4];
|
||||
|
||||
@@ -126,6 +126,7 @@ bool platform_process_is_elevated();
|
||||
bool platform_get_steam_path(utf8* outPath, size_t outSize);
|
||||
std::string platform_get_rct1_steam_dir();
|
||||
std::string platform_get_rct2_steam_dir();
|
||||
std::string platform_sanitise_filename(const std::string&);
|
||||
|
||||
#ifndef NO_TTF
|
||||
bool platform_get_font_path(TTFFontDescriptor* font, utf8* buffer, size_t size);
|
||||
|
||||
Reference in New Issue
Block a user