1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-15 11:03:00 +01:00

Sanitize screenshot path

This commit is contained in:
Tom Lankhorst
2019-02-03 22:59:28 +01:00
committed by Ted John
parent ebefe5721b
commit bbd69496b4
5 changed files with 119 additions and 72 deletions

View File

@@ -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.

View File

@@ -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(&currentDate);
rct2_time currentTime;
platform_get_time_local(&currentTime);
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);
}

View File

@@ -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()
{

View File

@@ -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];

View File

@@ -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);