mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2025-12-10 01:22:25 +01:00
Use ZStandard for Park and Replay Files (#24734)
This commit is contained in:
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -489,7 +489,7 @@ jobs:
|
||||
name: Ubuntu Linux (AppImage, x86_64)
|
||||
runs-on: ubuntu-latest
|
||||
needs: [check-code-formatting, build_variables]
|
||||
container: openrct2/openrct2-build:14-jammy
|
||||
container: openrct2/openrct2-build:21-jammy
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
2
.github/workflows/clang-tidy.yml
vendored
2
.github/workflows/clang-tidy.yml
vendored
@@ -12,7 +12,7 @@ on:
|
||||
jobs:
|
||||
clang-tidy-check:
|
||||
runs-on: ubuntu-latest
|
||||
container: openrct2/openrct2-build:19-noble
|
||||
container: openrct2/openrct2-build:20-noble
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ZehMatt/clang-tidy-annotations@v1
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
0.4.26 (in development)
|
||||
------------------------------------------------------------------------
|
||||
- Improved: [#24734] Save files now use Zstd compression for faster saving and smaller files.
|
||||
- Improved: [#24893] The ride list now has headers, and can be sorted in both directions.
|
||||
- Fix: [#16988] AppImage version does not show changelog.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Last updated: 2024-11-19
|
||||
Last updated: 2025-08-04
|
||||
------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -155,6 +155,7 @@ zlib | zlib licence.
|
||||
Google Test | BSD 3 clause licence.
|
||||
Google Benchmark | Apache 2.0 licence.
|
||||
sfl | zlib licence.
|
||||
zstd | BSD 3 clause license.
|
||||
|
||||
Licences for sub-libraries used by the above may vary. For more information, visit the libraries' respective official websites.
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
<GenerateDebugInformation>DebugFull</GenerateDebugInformation>
|
||||
<AdditionalDependencies>brotlicommon.lib;brotlidec.lib;brotlienc.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies Condition="'$(Breakpad)'=='true' and ('$(Platform)'=='Win32' or '$(Platform)'=='x64')">libbreakpadd.lib;libbreakpad_clientd.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>bz2d.lib;discord-rpc.lib;flac.lib;freetyped.lib;libpng16d.lib;ogg.lib;speexdsp.lib;SDL2-staticd.lib;vorbis.lib;vorbisfile.lib;zip.lib;zlibd.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>bz2d.lib;discord-rpc.lib;flac.lib;freetyped.lib;libpng16d.lib;ogg.lib;speexdsp.lib;SDL2-staticd.lib;vorbis.lib;vorbisfile.lib;zip.lib;zlibd.lib;zstd.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release' or '$(Configuration)'=='ReleaseLTCG'">
|
||||
@@ -109,7 +109,7 @@
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>brotlicommon.lib;brotlidec.lib;brotlienc.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies Condition="'$(Breakpad)'=='true' and ('$(Platform)'=='Win32' or '$(Platform)'=='x64')">libbreakpad.lib;libbreakpad_client.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>bz2.lib;discord-rpc.lib;flac.lib;freetype.lib;libpng16.lib;ogg.lib;speexdsp.lib;SDL2-static.lib;vorbis.lib;vorbisfile.lib;zip.lib;zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>bz2.lib;discord-rpc.lib;flac.lib;freetype.lib;libpng16.lib;ogg.lib;speexdsp.lib;SDL2-static.lib;vorbis.lib;vorbisfile.lib;zip.lib;zlib.lib;zstd.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
|
||||
@@ -7,6 +7,7 @@ START_DIR=$(pwd)
|
||||
SPEEXDSP_ROOT=/ext/speexdsp
|
||||
ICU_ROOT=/ext/icu/icu4c/source
|
||||
LIBZIP_ROOT=/ext/libzip
|
||||
ZSTD_ROOT=/ext/zstd
|
||||
JSON_DIR=/usr/include/nlohmann/
|
||||
|
||||
emcmake cmake ../ \
|
||||
@@ -25,7 +26,8 @@ emcmake cmake ../ \
|
||||
-DICU_DATA_LIBRARIES=$ICU_ROOT/lib/libicuuc.so \
|
||||
-DICU_DT_LIBRARY_RELEASE="$ICU_ROOT/stubdata/libicudata.so" \
|
||||
-DLIBZIP_LIBRARIES="$LIBZIP_ROOT/build/lib/libzip.a" \
|
||||
-DEMSCRIPTEN_FLAGS="-s USE_SDL=2 -s USE_BZIP2=1 -s USE_LIBPNG=1 -pthread -O3" \
|
||||
-DZSTD_LIBRARIES="$ZSTD_ROOT/build/build/lib/libzstd.a" \
|
||||
-DEMSCRIPTEN_FLAGS="-s USE_SDL=2 -s USE_ZLIB=1 -s USE_BZIP2=1 -s USE_LIBPNG=1 -pthread -O3" \
|
||||
-DEMSCRIPTEN_LDFLAGS="-Wno-pthreads-mem-growth -s SAFE_HEAP=0 -s ALLOW_MEMORY_GROWTH=1 -s MAXIMUM_MEMORY=4GB -s INITIAL_MEMORY=2GB -s STACK_SIZE=8388608 -s MIN_WEBGL_VERSION=2 -s MAX_WEBGL_VERSION=2 -s PTHREAD_POOL_SIZE=120 -pthread -s EXPORTED_RUNTIME_METHODS=ccall,FS,callMain,UTF8ToString,stringToNewUTF8 -lidbfs.js --use-preload-plugins -s MODULARIZE=1 -s 'EXPORT_NAME=\"OPENRCT2_WEB\"'"
|
||||
|
||||
emmake ninja
|
||||
|
||||
@@ -46,6 +46,7 @@ ExternalProject_Add(libs
|
||||
${CMAKE_BINARY_DIR}/libs/lib/${CMAKE_SHARED_LIBRARY_PREFIX}icui18n${CMAKE_SHARED_LIBRARY_SUFFIX}
|
||||
${CMAKE_BINARY_DIR}/libs/lib/${CMAKE_SHARED_LIBRARY_PREFIX}icuuc${CMAKE_SHARED_LIBRARY_SUFFIX}
|
||||
${CMAKE_BINARY_DIR}/libs/lib/${CMAKE_SHARED_LIBRARY_PREFIX}z${CMAKE_SHARED_LIBRARY_SUFFIX}
|
||||
${CMAKE_BINARY_DIR}/libs/lib/${CMAKE_SHARED_LIBRARY_PREFIX}zstd${CMAKE_SHARED_LIBRARY_SUFFIX}
|
||||
${CMAKE_BINARY_DIR}/libs/lib/${CMAKE_STATIC_LIBRARY_PREFIX}SDL2main${CMAKE_STATIC_LIBRARY_SUFFIX}
|
||||
|
||||
LOG_DOWNLOAD 1
|
||||
@@ -78,6 +79,7 @@ add_custom_command(TARGET libs POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/libs/lib/libspeexdsp.so" ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/libs/lib/libssl.so" ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/libs/lib/libz.so" ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/libs/lib/libzstd.so" ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
|
||||
)
|
||||
|
||||
add_library(freetype SHARED IMPORTED)
|
||||
@@ -98,6 +100,12 @@ set_target_properties(z PROPERTIES IMPORTED_LOCATION
|
||||
)
|
||||
add_dependencies(z libs)
|
||||
|
||||
add_library(zstd SHARED IMPORTED)
|
||||
set_target_properties(zstd PROPERTIES IMPORTED_LOCATION
|
||||
${CMAKE_BINARY_DIR}/libs/lib/${CMAKE_SHARED_LIBRARY_PREFIX}zstd${CMAKE_SHARED_LIBRARY_SUFFIX}
|
||||
)
|
||||
add_dependencies(zstd libs)
|
||||
|
||||
add_library(SDL2 SHARED IMPORTED)
|
||||
set_target_properties(SDL2 PROPERTIES IMPORTED_LOCATION
|
||||
${CMAKE_BINARY_DIR}/libs/lib/${CMAKE_SHARED_LIBRARY_PREFIX}SDL2${CMAKE_SHARED_LIBRARY_SUFFIX}
|
||||
@@ -250,7 +258,7 @@ file(GLOB_RECURSE OPENRCT2_CLI_SOURCES
|
||||
"${ORCT2_ROOT}/src/openrct2-cli/*.hpp")
|
||||
|
||||
add_library(openrct2 SHARED ${LIBOPENRCT2_SOURCES})
|
||||
target_link_libraries(openrct2 android stdc++ log dl SDL2 png z icu icuuc icudata crypto ssl freetype)
|
||||
target_link_libraries(openrct2 android stdc++ log dl SDL2 png z zstd icu icuuc icudata crypto ssl freetype)
|
||||
|
||||
add_library(openrct2-ui SHARED ${OPENRCT2_GUI_SOURCES})
|
||||
target_link_libraries(openrct2-ui openrct2 android stdc++ GLESv1_CM GLESv2 SDL2main speexdsp brotlicommon brotlidec bz2 freetype ogg vorbis vorbisfile FLAC)
|
||||
|
||||
@@ -121,12 +121,14 @@ if (EMSCRIPTEN)
|
||||
elseif (MSVC)
|
||||
find_package(png 1.6 REQUIRED)
|
||||
find_package(zlib REQUIRED)
|
||||
find_package(zstd REQUIRED)
|
||||
|
||||
find_path(LIBZIP_INCLUDE_DIRS zip.h)
|
||||
find_library(LIBZIP_LIBRARIES zip)
|
||||
else ()
|
||||
PKG_CHECK_MODULES(LIBZIP REQUIRED IMPORTED_TARGET libzip>=1.0)
|
||||
PKG_CHECK_MODULES(ZLIB REQUIRED IMPORTED_TARGET zlib)
|
||||
PKG_CHECK_MODULES(ZSTD REQUIRED IMPORTED_TARGET libzstd)
|
||||
|
||||
PKG_CHECK_MODULES(PNG IMPORTED_TARGET libpng>=1.6)
|
||||
if (NOT PNG_FOUND)
|
||||
@@ -144,18 +146,21 @@ if (STATIC)
|
||||
target_link_libraries(libopenrct2
|
||||
${PNG_STATIC_LIBRARIES}
|
||||
${ZLIB_STATIC_LIBRARIES}
|
||||
${LIBZIP_STATIC_LIBRARIES})
|
||||
${LIBZIP_STATIC_LIBRARIES}
|
||||
${ZSTD_STATIC_LIBRARIES})
|
||||
else ()
|
||||
if (NOT MSVC AND NOT EMSCRIPTEN)
|
||||
target_link_libraries(libopenrct2
|
||||
PkgConfig::PNG
|
||||
PkgConfig::ZLIB
|
||||
PkgConfig::LIBZIP)
|
||||
PkgConfig::LIBZIP
|
||||
PkgConfig::ZSTD)
|
||||
else ()
|
||||
target_link_libraries(libopenrct2
|
||||
${PNG_LIBRARIES}
|
||||
${ZLIB_LIBRARIES}
|
||||
${LIBZIP_LIBRARIES})
|
||||
${LIBZIP_LIBRARIES}
|
||||
${ZSTD_LIBRARIES})
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
@@ -235,6 +240,7 @@ endif()
|
||||
target_include_directories(libopenrct2 SYSTEM PRIVATE ${LIBZIP_INCLUDE_DIRS})
|
||||
target_include_directories(libopenrct2 SYSTEM PRIVATE ${PNG_INCLUDE_DIRS}
|
||||
${ZLIB_INCLUDE_DIRS})
|
||||
target_include_directories(libopenrct2 SYSTEM PRIVATE ${ZSTD_INCLUDE_DIRS})
|
||||
include_directories(libopenrct2 SYSTEM ${CMAKE_CURRENT_LIST_DIR}/../thirdparty)
|
||||
|
||||
# To avoid unnecessary rebuilds set the current branch and
|
||||
|
||||
@@ -103,9 +103,10 @@ namespace OpenRCT2
|
||||
|
||||
class ReplayManager final : public IReplayManager
|
||||
{
|
||||
static constexpr uint16_t kReplayVersion = 10;
|
||||
static constexpr uint16_t kReplayVersion = 11;
|
||||
static constexpr uint16_t kReplayMinCompatVersion = 10;
|
||||
static constexpr uint32_t kReplayMagic = 0x5243524F; // ORCR.
|
||||
static constexpr int kReplayCompressionLevel = Compression::kZlibMaxCompressionLevel;
|
||||
static constexpr int kReplayCompressionLevel = 18;
|
||||
static constexpr int kNormalRecordingChecksumTicks = 1;
|
||||
static constexpr int kSilentRecordingChecksumTicks = 40; // Same as network server
|
||||
|
||||
@@ -313,8 +314,9 @@ namespace OpenRCT2
|
||||
|
||||
MemoryStream compressed;
|
||||
stream.SetPosition(0);
|
||||
bool compressStatus = Compression::zlibCompress(
|
||||
stream, stream.GetLength(), compressed, Compression::ZlibHeaderType::zlib, kReplayCompressionLevel);
|
||||
// header already has decompressed length, but no checksum, so use the ZStandard checksum
|
||||
bool compressStatus = Compression::zstdCompress(
|
||||
stream, stream.GetLength(), compressed, Compression::ZstdMetadata::checksum, kReplayCompressionLevel);
|
||||
if (!compressStatus)
|
||||
throw IOException("Compression Error");
|
||||
|
||||
@@ -563,12 +565,18 @@ namespace OpenRCT2
|
||||
|
||||
MemoryStream decompressed;
|
||||
bool decompressStatus = true;
|
||||
|
||||
recFile.data.SetPosition(0);
|
||||
if (recFile.version <= 10)
|
||||
{
|
||||
decompressStatus = Compression::zlibDecompress(
|
||||
recFile.data, recFile.data.GetLength(), decompressed, recFile.uncompressedSize,
|
||||
Compression::ZlibHeaderType::zlib);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
decompressStatus = Compression::zstdDecompress(
|
||||
recFile.data, recFile.data.GetLength(), decompressed, recFile.uncompressedSize);
|
||||
}
|
||||
if (!decompressStatus)
|
||||
throw IOException("Decompression Error");
|
||||
|
||||
@@ -683,7 +691,7 @@ namespace OpenRCT2
|
||||
|
||||
bool Compatible(ReplayRecordData& data)
|
||||
{
|
||||
return data.version == kReplayVersion;
|
||||
return data.version >= kReplayMinCompatVersion;
|
||||
}
|
||||
|
||||
bool Serialise(DataSerialiser& serialiser, ReplayRecordData& data)
|
||||
|
||||
@@ -108,6 +108,7 @@ consteval CommandLineCommand DefineSubCommand(const char* name, const CommandLin
|
||||
namespace OpenRCT2::CommandLine
|
||||
{
|
||||
extern const CommandLineCommand kRootCommands[];
|
||||
extern const CommandLineCommand kConvertCommands[];
|
||||
extern const CommandLineCommand kScreenshotCommands[];
|
||||
extern const CommandLineCommand kSpriteCommands[];
|
||||
extern const CommandLineCommand kSimulateCommands[];
|
||||
@@ -118,6 +119,5 @@ namespace OpenRCT2::CommandLine
|
||||
void PrintHelp(bool allCommands = false);
|
||||
exitcode_t HandleCommandDefault();
|
||||
|
||||
exitcode_t HandleCommandConvert(CommandLineArgEnumerator* enumerator);
|
||||
exitcode_t HandleCommandUri(CommandLineArgEnumerator* enumerator);
|
||||
} // namespace OpenRCT2::CommandLine
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include "../GameState.h"
|
||||
#include "../OpenRCT2.h"
|
||||
#include "../ParkImporter.h"
|
||||
#include "../PlatformEnvironment.h"
|
||||
#include "../config/Config.h"
|
||||
#include "../core/Console.hpp"
|
||||
#include "../core/Path.hpp"
|
||||
#include "../object/ObjectManager.h"
|
||||
@@ -20,14 +22,33 @@
|
||||
#include "CommandLine.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
|
||||
using namespace OpenRCT2;
|
||||
|
||||
static int32_t _compressLevel = kParkFileSaveCompressionLevel;
|
||||
|
||||
// clang-format off
|
||||
static constexpr CommandLineOptionDefinition kConvertOptions[]
|
||||
{
|
||||
{ CMDLINE_TYPE_INTEGER, &_compressLevel, 'l', "compress-level", "The compression level to use when writing the converted file" },
|
||||
kOptionTableEnd
|
||||
};
|
||||
|
||||
static exitcode_t HandleCommandConvert(CommandLineArgEnumerator* argEnumerator);
|
||||
|
||||
const CommandLineCommand CommandLine::kConvertCommands[]{
|
||||
// Main commands
|
||||
DefineCommand("", "<source> [destination]", kConvertOptions, HandleCommandConvert),
|
||||
kCommandTableEnd
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static void WriteConvertFromAndToMessage(FileExtension sourceFileType, FileExtension destinationFileType);
|
||||
static u8string GetFileTypeFriendlyName(FileExtension fileType);
|
||||
|
||||
exitcode_t CommandLine::HandleCommandConvert(CommandLineArgEnumerator* enumerator)
|
||||
static exitcode_t HandleCommandConvert(CommandLineArgEnumerator* enumerator)
|
||||
{
|
||||
exitcode_t result = CommandLine::HandleCommandDefault();
|
||||
if (result != EXITCODE_CONTINUE)
|
||||
@@ -48,10 +69,10 @@ exitcode_t CommandLine::HandleCommandConvert(CommandLineArgEnumerator* enumerato
|
||||
|
||||
// Get the destination path
|
||||
const utf8* rawDestinationPath;
|
||||
if (!enumerator->TryPopString(&rawDestinationPath))
|
||||
if (!enumerator->TryPopString(&rawDestinationPath) || String::startsWith(rawDestinationPath, "-"))
|
||||
{
|
||||
Console::Error::WriteLine("Expected a destination path.");
|
||||
return EXITCODE_FAIL;
|
||||
// if no destination path is provided, convert the park file in-place
|
||||
rawDestinationPath = rawSourcePath;
|
||||
}
|
||||
|
||||
const auto destinationPath = Path::GetAbsolute(rawDestinationPath);
|
||||
@@ -75,12 +96,12 @@ exitcode_t CommandLine::HandleCommandConvert(CommandLineArgEnumerator* enumerato
|
||||
case FileExtension::PARK:
|
||||
if (destinationFileType == FileExtension::PARK)
|
||||
{
|
||||
Console::Error::WriteLine("File is already an OpenRCT2 saved game or scenario.");
|
||||
return EXITCODE_FAIL;
|
||||
Console::Error::WriteLine(
|
||||
"File is already an OpenRCT2 saved game or scenario. Updating file version and recompressing.");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Console::Error::WriteLine("Only conversion from .SC4, .SV4, .SC6 or .SV6 is supported.");
|
||||
Console::Error::WriteLine("Only conversion from .SC4, .SV4, .SC6, .SV6, or .PARK is supported.");
|
||||
return EXITCODE_FAIL;
|
||||
}
|
||||
|
||||
@@ -125,7 +146,7 @@ exitcode_t CommandLine::HandleCommandConvert(CommandLineArgEnumerator* enumerato
|
||||
auto* windowMgr = Ui::GetWindowManager();
|
||||
windowMgr->CloseByClass(WindowClass::MainWindow);
|
||||
|
||||
exporter->Export(gameState, destinationPath);
|
||||
exporter->Export(gameState, destinationPath, static_cast<int16_t>(_compressLevel));
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
|
||||
@@ -133,7 +133,6 @@ const CommandLineCommand CommandLine::kRootCommands[]
|
||||
DefineCommand("join", "<hostname>", kStandardOptions, HandleCommandJoin ),
|
||||
#endif
|
||||
DefineCommand("set-rct2", "<path>", kStandardOptions, HandleCommandSetRCT2),
|
||||
DefineCommand("convert", "<source> <destination>", kStandardOptions, CommandLine::HandleCommandConvert),
|
||||
DefineCommand("scan-objects", "<path>", kStandardOptions, HandleCommandScanObjects),
|
||||
DefineCommand("handle-uri", "openrct2://.../", kStandardOptions, CommandLine::HandleCommandUri),
|
||||
|
||||
@@ -142,6 +141,7 @@ const CommandLineCommand CommandLine::kRootCommands[]
|
||||
#endif
|
||||
|
||||
// Sub-commands
|
||||
DefineSubCommand("convert", CommandLine::kConvertCommands ),
|
||||
DefineSubCommand("screenshot", CommandLine::kScreenshotCommands ),
|
||||
DefineSubCommand("sprite", CommandLine::kSpriteCommands ),
|
||||
DefineSubCommand("simulate", CommandLine::kSimulateCommands ),
|
||||
|
||||
@@ -23,26 +23,36 @@
|
||||
|
||||
using namespace OpenRCT2;
|
||||
|
||||
// clang-format off
|
||||
static constexpr CommandLineOptionDefinition kNoOptions[]
|
||||
{
|
||||
kOptionTableEnd
|
||||
};
|
||||
|
||||
static exitcode_t HandleSimulate(CommandLineArgEnumerator* argEnumerator);
|
||||
|
||||
const CommandLineCommand CommandLine::kSimulateCommands[]{ // Main commands
|
||||
DefineCommand("", "<ticks>", nullptr, HandleSimulate),
|
||||
const CommandLineCommand CommandLine::kSimulateCommands[]{
|
||||
// Main commands
|
||||
DefineCommand("", "<park file> <ticks>", kNoOptions, HandleSimulate),
|
||||
kCommandTableEnd
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static exitcode_t HandleSimulate(CommandLineArgEnumerator* argEnumerator)
|
||||
{
|
||||
const char** argv = const_cast<const char**>(argEnumerator->GetArguments()) + argEnumerator->GetIndex();
|
||||
int32_t argc = argEnumerator->GetCount() - argEnumerator->GetIndex();
|
||||
|
||||
if (argc < 2)
|
||||
const utf8* inputPath;
|
||||
if (!argEnumerator->TryPopString(&inputPath))
|
||||
{
|
||||
Console::Error::WriteLine("Missing arguments <sv6-file> <ticks>.");
|
||||
Console::Error::WriteLine("Expected a save file path");
|
||||
return EXITCODE_FAIL;
|
||||
}
|
||||
|
||||
const char* inputPath = argv[0];
|
||||
uint32_t ticks = atol(argv[1]);
|
||||
int32_t ticks;
|
||||
if (!argEnumerator->TryPopInteger(&ticks))
|
||||
{
|
||||
Console::Error::WriteLine("Expected a number of ticks to simulate");
|
||||
return EXITCODE_FAIL;
|
||||
}
|
||||
|
||||
gOpenRCT2Headless = true;
|
||||
|
||||
@@ -59,7 +69,7 @@ static exitcode_t HandleSimulate(CommandLineArgEnumerator* argEnumerator)
|
||||
}
|
||||
|
||||
Console::WriteLine("Running %d ticks...", ticks);
|
||||
for (uint32_t i = 0; i < ticks; i++)
|
||||
for (int32_t i = 0; i < ticks; i++)
|
||||
{
|
||||
gameStateUpdateLogic();
|
||||
}
|
||||
|
||||
@@ -19,12 +19,13 @@
|
||||
|
||||
#include <limits>
|
||||
#include <zlib.h>
|
||||
#include <zstd.h>
|
||||
|
||||
namespace OpenRCT2::Compression
|
||||
{
|
||||
constexpr size_t kZlibChunkSize = 128 * 1024;
|
||||
constexpr size_t kZlibMaxChunkSize = static_cast<size_t>(std::numeric_limits<uInt>::max());
|
||||
constexpr int kZlibWindowBits[] = { -15, 15, 15 + 16 };
|
||||
static constexpr size_t kZlibChunkSize = 128 * 1024;
|
||||
static constexpr size_t kZlibMaxChunkSize = static_cast<size_t>(std::numeric_limits<uInt>::max());
|
||||
static constexpr int kZlibWindowBits[] = { -15, 15, 15 + 16 };
|
||||
|
||||
/*
|
||||
* Modified copy of compressBound() from zlib 1.3.1, with the argument type changed from ULong
|
||||
@@ -32,7 +33,7 @@ namespace OpenRCT2::Compression
|
||||
*/
|
||||
static uint64_t zlibCompressBound(uint64_t length)
|
||||
{
|
||||
return length + (length >> 12) + (length >> 14) + (length >> 25) + 13 + (18 - 6);
|
||||
return length + (length >> 12) + (length >> 14) + (length >> 25) + 13uLL + (18uLL - 6uLL);
|
||||
}
|
||||
|
||||
bool zlibCompress(IStream& source, uint64_t sourceLength, IStream& dest, ZlibHeaderType header, int16_t level)
|
||||
@@ -138,4 +139,137 @@ namespace OpenRCT2::Compression
|
||||
inflateEnd(&strm);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Modified copy of ZSTD_COMPRESSBOUND / ZSTD_compressBound() from zstd 1.5.7, with the argument
|
||||
* type changed from size_t (which may be only 32 bits) to uint64_t, and removes the error handling.
|
||||
*/
|
||||
static uint64_t zstdCompressBound(uint64_t length)
|
||||
{
|
||||
return length + (length >> 8)
|
||||
+ ((length < (128uLL << 10)) ? (((128uLL << 10) - length) >> 11) /* margin, from 64 to 0 */ : 0uLL);
|
||||
}
|
||||
|
||||
bool zstdCompress(IStream& source, uint64_t sourceLength, IStream& dest, ZstdMetadata metadata, int16_t level)
|
||||
{
|
||||
Guard::Assert(sourceLength <= source.GetLength() - source.GetPosition());
|
||||
|
||||
size_t ret;
|
||||
StreamReadBuffer sourceBuf(source, sourceLength, ZSTD_CStreamInSize());
|
||||
StreamWriteBuffer destBuf(dest, zstdCompressBound(sourceLength), ZSTD_CStreamOutSize());
|
||||
unsigned metaFlags = static_cast<unsigned>(metadata);
|
||||
|
||||
const auto deleter = [](ZSTD_CCtx* ptr) { ZSTD_freeCCtx(ptr); };
|
||||
std::unique_ptr<ZSTD_CCtx, decltype(deleter)> ctx(ZSTD_createCCtx(), deleter);
|
||||
if (ctx == nullptr)
|
||||
{
|
||||
LOG_ERROR("Failed to create zstd context");
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = ZSTD_CCtx_setParameter(ctx.get(), ZSTD_c_compressionLevel, level);
|
||||
if (ZSTD_isError(ret))
|
||||
{
|
||||
LOG_ERROR("Failed to set compression level with error: %s", ZSTD_getErrorName(ret));
|
||||
return false;
|
||||
}
|
||||
// set options for content size (default on) and checksum (default off)
|
||||
ret = ZSTD_CCtx_setParameter(ctx.get(), ZSTD_c_contentSizeFlag, (metaFlags & 1) != 0);
|
||||
if (ZSTD_isError(ret))
|
||||
{
|
||||
LOG_ERROR("Failed to set content size flag with error: %s", ZSTD_getErrorName(ret));
|
||||
return false;
|
||||
}
|
||||
ret = ZSTD_CCtx_setParameter(ctx.get(), ZSTD_c_checksumFlag, (metaFlags & 2) != 0);
|
||||
if (ZSTD_isError(ret))
|
||||
{
|
||||
LOG_ERROR("Failed to set checksum flag with error: %s", ZSTD_getErrorName(ret));
|
||||
return false;
|
||||
}
|
||||
// unlike gzip, zstd puts the decompressed content size at the start of the file,
|
||||
// so we need to tell zstd how big the input is before we start compressing.
|
||||
ret = ZSTD_CCtx_setPledgedSrcSize(ctx.get(), sourceLength);
|
||||
if (ZSTD_isError(ret))
|
||||
{
|
||||
LOG_ERROR("Failed to set file length with error: %s", ZSTD_getErrorName(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
auto readBlock = sourceBuf.ReadBlock(source);
|
||||
ZSTD_inBuffer input = { readBlock.first, readBlock.second, 0 };
|
||||
|
||||
do
|
||||
{
|
||||
Guard::Assert(destBuf, "Compression Overruns Ouput Size");
|
||||
|
||||
auto writeBlock = destBuf.WriteBlockStart();
|
||||
ZSTD_outBuffer output = { writeBlock.first, writeBlock.second, 0 };
|
||||
|
||||
ret = ZSTD_compressStream2(ctx.get(), &output, &input, sourceBuf ? ZSTD_e_continue : ZSTD_e_end);
|
||||
if (ZSTD_isError(ret))
|
||||
{
|
||||
LOG_ERROR("Failed to compress data with error: %s", ZSTD_getErrorName(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
destBuf.WriteBlockCommit(dest, output.pos);
|
||||
} while (input.pos < input.size || (!sourceBuf && ret > 0));
|
||||
} while (sourceBuf);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool zstdDecompress(IStream& source, uint64_t sourceLength, IStream& dest, uint64_t decompressLength)
|
||||
{
|
||||
Guard::Assert(sourceLength <= source.GetLength() - source.GetPosition());
|
||||
|
||||
size_t ret;
|
||||
StreamReadBuffer sourceBuf(source, sourceLength, ZSTD_DStreamInSize());
|
||||
StreamWriteBuffer destBuf(dest, decompressLength, ZSTD_DStreamOutSize());
|
||||
|
||||
const auto deleter = [](ZSTD_DCtx* ptr) { ZSTD_freeDCtx(ptr); };
|
||||
std::unique_ptr<ZSTD_DCtx, decltype(deleter)> ctx(ZSTD_createDCtx(), deleter);
|
||||
if (ctx == nullptr)
|
||||
{
|
||||
LOG_ERROR("Failed to create zstd context");
|
||||
return false;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
auto readBlock = sourceBuf.ReadBlock(source);
|
||||
ZSTD_inBuffer input = { readBlock.first, readBlock.second, 0 };
|
||||
|
||||
do
|
||||
{
|
||||
if (!destBuf)
|
||||
{
|
||||
LOG_ERROR("Decompressed data larger than expected");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto writeBlock = destBuf.WriteBlockStart();
|
||||
ZSTD_outBuffer output = { writeBlock.first, writeBlock.second, 0 };
|
||||
|
||||
ret = ZSTD_decompressStream(ctx.get(), &output, &input);
|
||||
if (ZSTD_isError(ret))
|
||||
{
|
||||
LOG_ERROR("Failed to compress data with error: %s", ZSTD_getErrorName(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
destBuf.WriteBlockCommit(dest, output.pos);
|
||||
} while (input.pos < input.size || (!sourceBuf && ret > 0));
|
||||
} while (sourceBuf);
|
||||
|
||||
if (destBuf)
|
||||
{
|
||||
LOG_ERROR("Decompressed data smaller than expected");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace OpenRCT2::Compression
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
namespace OpenRCT2::Compression
|
||||
{
|
||||
// zlib doesn't use 0 as a real compression level, so use it to mean no compression
|
||||
// both zlib and zstd don't use 0 as a real compression level, so use it to mean no compression
|
||||
constexpr int16_t kNoCompressionLevel = 0;
|
||||
|
||||
// Zlib methods, using the DEFLATE compression algorithm
|
||||
@@ -36,4 +36,22 @@ namespace OpenRCT2::Compression
|
||||
int16_t level = kZlibDefaultCompressionLevel);
|
||||
bool zlibDecompress(
|
||||
IStream& source, uint64_t sourceLength, IStream& dest, uint64_t decompressLength, ZlibHeaderType header);
|
||||
|
||||
// Zstd methods, using the ZStandard compression algorithm
|
||||
constexpr int16_t kZstdDefaultCompressionLevel = 3;
|
||||
|
||||
// Options for optional metadata to attach to a ZStandard frame. Zstd default is length-only,
|
||||
// but callers to zstdCompress should use whatever is not already duplicated by other headers.
|
||||
enum class ZstdMetadata
|
||||
{
|
||||
none = 0,
|
||||
length = 1,
|
||||
checksum = 2,
|
||||
both = 3,
|
||||
};
|
||||
|
||||
bool zstdCompress(
|
||||
IStream& source, uint64_t sourceLength, IStream& dest, ZstdMetadata metadata,
|
||||
int16_t level = kZstdDefaultCompressionLevel);
|
||||
bool zstdDecompress(IStream& source, uint64_t sourceLength, IStream& dest, uint64_t decompressLength);
|
||||
} // namespace OpenRCT2::Compression
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace OpenRCT2
|
||||
{
|
||||
none,
|
||||
gzip,
|
||||
zstd,
|
||||
};
|
||||
|
||||
private:
|
||||
@@ -76,7 +77,7 @@ namespace OpenRCT2
|
||||
int16_t _compressionLevel;
|
||||
|
||||
public:
|
||||
OrcaStream(IStream& stream, const Mode mode, int16_t compressionLevel = Compression::kZlibDefaultCompressionLevel)
|
||||
OrcaStream(IStream& stream, const Mode mode, int16_t compressionLevel = Compression::kNoCompressionLevel)
|
||||
{
|
||||
_stream = &stream;
|
||||
_mode = mode;
|
||||
@@ -104,6 +105,10 @@ namespace OpenRCT2
|
||||
*_stream, _header.CompressedSize, _buffer, _header.UncompressedSize,
|
||||
Compression::ZlibHeaderType::gzip);
|
||||
break;
|
||||
case CompressionType::zstd:
|
||||
decompressStatus = Compression::zstdDecompress(
|
||||
*_stream, _header.CompressedSize, _buffer, _header.UncompressedSize);
|
||||
break;
|
||||
default:
|
||||
throw IOException("Unknown Park Compression Type");
|
||||
}
|
||||
@@ -131,7 +136,7 @@ namespace OpenRCT2
|
||||
{
|
||||
_header = {};
|
||||
_header.Compression = _compressionLevel == Compression::kNoCompressionLevel ? CompressionType::none
|
||||
: CompressionType::gzip;
|
||||
: CompressionType::zstd;
|
||||
_buffer = MemoryStream{};
|
||||
}
|
||||
}
|
||||
@@ -163,6 +168,11 @@ namespace OpenRCT2
|
||||
compressStatus = Compression::zlibCompress(
|
||||
_buffer, _buffer.GetLength(), compressed, Compression::ZlibHeaderType::gzip, _compressionLevel);
|
||||
break;
|
||||
case CompressionType::zstd:
|
||||
// PARK header already has length and checksum, so exclude them in the compression frame
|
||||
compressStatus = Compression::zstdCompress(
|
||||
_buffer, _buffer.GetLength(), compressed, Compression::ZstdMetadata::none, _compressionLevel);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ using namespace OpenRCT2;
|
||||
// It is used for making sure only compatible builds get connected, even within
|
||||
// single OpenRCT2 version.
|
||||
|
||||
constexpr uint8_t kNetworkStreamVersion = 0;
|
||||
constexpr uint8_t kNetworkStreamVersion = 1;
|
||||
|
||||
const std::string kNetworkStreamID = std::string(kOpenRCT2Version) + "-" + std::to_string(kNetworkStreamVersion);
|
||||
|
||||
@@ -2876,7 +2876,7 @@ bool NetworkBase::SaveMap(IStream* stream, const std::vector<const ObjectReposit
|
||||
exporter->ExportObjectsList = objects;
|
||||
|
||||
auto& gameState = getGameState();
|
||||
exporter->Export(gameState, *stream);
|
||||
exporter->Export(gameState, *stream, kParkFileNetCompressionLevel);
|
||||
result = true;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "../OpenRCT2.h"
|
||||
#include "../ParkImporter.h"
|
||||
#include "../Version.h"
|
||||
#include "../config/Config.h"
|
||||
#include "../core/Console.hpp"
|
||||
#include "../core/Crypt.h"
|
||||
#include "../core/DataSerialiser.h"
|
||||
@@ -2766,7 +2767,7 @@ int32_t ScenarioSave(GameState_t& gameState, u8string_view path, int32_t flags)
|
||||
{
|
||||
// s6exporter->SaveGame(path);
|
||||
}
|
||||
parkFile->Save(gameState, path, Compression::kZlibDefaultCompressionLevel);
|
||||
parkFile->Save(gameState, path, gIsAutosave ? kParkFileAutoCompressionLevel : kParkFileSaveCompressionLevel);
|
||||
result = true;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
|
||||
@@ -22,10 +22,10 @@ namespace OpenRCT2
|
||||
struct GameState_t;
|
||||
|
||||
// Current version that is saved.
|
||||
constexpr uint32_t kParkFileCurrentVersion = 56;
|
||||
constexpr uint32_t kParkFileCurrentVersion = 57;
|
||||
|
||||
// The minimum version that is forwards compatible with the current version.
|
||||
constexpr uint32_t kParkFileMinVersion = 55;
|
||||
constexpr uint32_t kParkFileMinVersion = 57;
|
||||
|
||||
// The minimum version that is backwards compatible with the current version.
|
||||
// If this is increased beyond 0, uncomment the checks in ParkFile.cpp and Context.cpp!
|
||||
@@ -33,6 +33,11 @@ namespace OpenRCT2
|
||||
|
||||
constexpr uint32_t kParkFileMagic = 0x4B524150; // PARK
|
||||
|
||||
// ZStd compression levels to use for various types of saves
|
||||
constexpr int16_t kParkFileSaveCompressionLevel = 7;
|
||||
constexpr int16_t kParkFileAutoCompressionLevel = 4;
|
||||
constexpr int16_t kParkFileNetCompressionLevel = 4;
|
||||
|
||||
struct IStream;
|
||||
|
||||
// As uint16_t, in order to allow comparison with int32_t
|
||||
@@ -62,11 +67,7 @@ namespace OpenRCT2
|
||||
public:
|
||||
std::vector<const ObjectRepositoryItem*> ExportObjectsList;
|
||||
|
||||
void Export(
|
||||
OpenRCT2::GameState_t& gameState, std::string_view path,
|
||||
int16_t compressionLevel = Compression::kZlibDefaultCompressionLevel);
|
||||
void Export(
|
||||
OpenRCT2::GameState_t& gameState, OpenRCT2::IStream& stream,
|
||||
int16_t compressionLevel = Compression::kZlibDefaultCompressionLevel);
|
||||
void Export(OpenRCT2::GameState_t& gameState, std::string_view path, int16_t compressionLevel);
|
||||
void Export(OpenRCT2::GameState_t& gameState, OpenRCT2::IStream& stream, int16_t compressionLevel);
|
||||
};
|
||||
} // namespace OpenRCT2
|
||||
|
||||
@@ -139,6 +139,8 @@ static bool OnCrash(
|
||||
FileStream source(dumpFilePath, FileMode::open);
|
||||
FileStream dest(dumpFilePathGZIP, FileMode::write);
|
||||
|
||||
// We could switch this to zstdCompress() if supported by backtrace.io. If you switch it,
|
||||
// use the extension .zst and ZstdMetadataType::both to use the appropriate metadata.
|
||||
if (Compression::zlibCompress(source, source.GetLength(), dest, Compression::ZlibHeaderType::gzip))
|
||||
{
|
||||
// TODO: enable upload of gzip-compressed dumps once supported on
|
||||
@@ -183,7 +185,7 @@ static bool OnCrash(
|
||||
exporter->ExportObjectsList = objManager.GetPackableObjects();
|
||||
|
||||
auto& gameState = getGameState();
|
||||
exporter->Export(gameState, saveFilePathUTF8.c_str());
|
||||
exporter->Export(gameState, saveFilePathUTF8.c_str(), kParkFileSaveCompressionLevel);
|
||||
savedGameDumped = true;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
|
||||
@@ -120,7 +120,7 @@ static bool ExportSave(MemoryStream& stream, std::unique_ptr<IContext>& context)
|
||||
exporter->ExportObjectsList = objManager.GetPackableObjects();
|
||||
|
||||
auto& gameState = getGameState();
|
||||
exporter->Export(gameState, stream);
|
||||
exporter->Export(gameState, stream, OpenRCT2::kParkFileSaveCompressionLevel);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user