diff --git a/src/openrct2/Context.cpp b/src/openrct2/Context.cpp index e582493241..e947d4b92c 100644 --- a/src/openrct2/Context.cpp +++ b/src/openrct2/Context.cpp @@ -587,6 +587,12 @@ namespace OpenRCT2 { network_send_map(); } +#ifdef USE_BREAKPAD + if (network_get_mode() == NETWORK_MODE_NONE) + { + start_silent_record(); + } +#endif return true; } catch (const ObjectLoadException& e) diff --git a/src/openrct2/Game.cpp b/src/openrct2/Game.cpp index d7464b4393..111e5d368f 100644 --- a/src/openrct2/Game.cpp +++ b/src/openrct2/Game.cpp @@ -17,11 +17,13 @@ #include "Input.h" #include "OpenRCT2.h" #include "ParkImporter.h" +#include "PlatformEnvironment.h" #include "ReplayManager.h" #include "actions/LoadOrQuitAction.hpp" #include "audio/audio.h" #include "config/Config.h" #include "core/FileScanner.h" +#include "core/Path.hpp" #include "interface/Screenshot.h" #include "interface/Viewport.h" #include "interface/Window.h" @@ -845,3 +847,45 @@ void game_load_or_quit_no_save_prompt() break; } } + +void start_silent_record() +{ + std::string name = Path::Combine( + OpenRCT2::GetContext()->GetPlatformEnvironment()->GetDirectoryPath(OpenRCT2::DIRBASE::USER), "debug_replay.sv6r"); + auto* replayManager = OpenRCT2::GetContext()->GetReplayManager(); + if (replayManager->StartRecording(name, OpenRCT2::k_MaxReplayTicks, OpenRCT2::IReplayManager::RecordType::SILENT)) + { + OpenRCT2::ReplayRecordInfo info; + replayManager->GetCurrentReplayInfo(info); + safe_strcpy(gSilentRecordingName, info.FilePath.c_str(), MAX_PATH); + + const char* logFmt = "Silent replay recording started: (%s) %s"; + printf(logFmt, info.Name.c_str(), info.FilePath.c_str()); + } +} + +bool stop_silent_record() +{ + auto* replayManager = OpenRCT2::GetContext()->GetReplayManager(); + if (!replayManager->IsRecording() && !replayManager->IsNormalising()) + { + return false; + } + + OpenRCT2::ReplayRecordInfo info; + replayManager->GetCurrentReplayInfo(info); + + if (replayManager->StopRecording()) + { + const char* logFmt = "Replay recording stopped: (%s) %s\n" + " Ticks: %u\n" + " Commands: %u\n" + " Checksums: %u"; + + printf(logFmt, info.Name.c_str(), info.FilePath.c_str(), info.Ticks, info.NumCommands, info.NumChecksums); + + return true; + } + + return false; +} diff --git a/src/openrct2/Game.h b/src/openrct2/Game.h index b226f2594a..dd244943d6 100644 --- a/src/openrct2/Game.h +++ b/src/openrct2/Game.h @@ -171,3 +171,5 @@ void game_convert_strings_to_rct2(rct_s6_data* s6); void utf8_to_rct2_self(char* buffer, size_t length); void rct2_to_utf8_self(char* buffer, size_t length); void game_fix_save_vars(); +void start_silent_record(); +bool stop_silent_record(); diff --git a/src/openrct2/OpenRCT2.cpp b/src/openrct2/OpenRCT2.cpp index a6212ef2e3..387f03f4bc 100644 --- a/src/openrct2/OpenRCT2.cpp +++ b/src/openrct2/OpenRCT2.cpp @@ -17,6 +17,7 @@ utf8 gCustomOpenrctDataPath[MAX_PATH] = { 0 }; utf8 gCustomRCT1DataPath[MAX_PATH] = { 0 }; utf8 gCustomRCT2DataPath[MAX_PATH] = { 0 }; utf8 gCustomPassword[MAX_PATH] = { 0 }; +utf8 gSilentRecordingName[MAX_PATH] = { 0 }; bool gOpenRCT2Headless = false; bool gOpenRCT2NoGraphics = false; diff --git a/src/openrct2/OpenRCT2.h b/src/openrct2/OpenRCT2.h index fea147ec55..eeeaf5217c 100644 --- a/src/openrct2/OpenRCT2.h +++ b/src/openrct2/OpenRCT2.h @@ -46,6 +46,7 @@ extern bool gOpenRCT2Headless; extern bool gOpenRCT2NoGraphics; extern bool gOpenRCT2ShowChangelog; extern bool gOpenRCT2SilentBreakpad; +extern utf8 gSilentRecordingName[MAX_PATH]; #ifndef DISABLE_NETWORK extern int32_t gNetworkStart; diff --git a/src/openrct2/ReplayManager.cpp b/src/openrct2/ReplayManager.cpp index 74664636c9..2483747fd7 100644 --- a/src/openrct2/ReplayManager.cpp +++ b/src/openrct2/ReplayManager.cpp @@ -98,6 +98,8 @@ namespace OpenRCT2 static constexpr uint16_t ReplayVersion = 3; static constexpr uint32_t ReplayMagic = 0x5243524F; // ORCR. static constexpr int ReplayCompressionLevel = 9; + static constexpr int NormalRecordingChecksumTicks = 1; + static constexpr int SilentRecordingChecksumTicks = 40; // Same as network server enum class ReplayMode { @@ -127,6 +129,11 @@ namespace OpenRCT2 return _mode == ReplayMode::NORMALISATION; } + virtual bool ShouldDisplayNotice() const override + { + return IsRecording() && _recordType == RecordType::NORMAL; + } + virtual void AddGameAction(uint32_t tick, const GameAction* action) override { if (_currentRecording == nullptr) @@ -153,7 +160,7 @@ namespace OpenRCT2 rct_sprite_checksum checksum = sprite_checksum(); AddChecksum(gCurrentTicks, std::move(checksum)); - _nextChecksumTick = gCurrentTicks + 1; + _nextChecksumTick = gCurrentTicks + ChecksumTicksDelta(); } if (_mode == ReplayMode::RECORDING) @@ -197,8 +204,14 @@ namespace OpenRCT2 } } - virtual bool StartRecording(const std::string& name, uint32_t maxTicks /*= k_MaxReplayTicks*/) override + virtual bool StartRecording( + const std::string& name, uint32_t maxTicks /*= k_MaxReplayTicks*/, RecordType rt /*= RecordType::NORMAL*/) override { + // If using silent recording, discard whatever recording there is going on, even if a new silent recording is to be + // started. + if (_mode == ReplayMode::RECORDING && _recordType == RecordType::SILENT) + StopRecording(true); + if (_mode != ReplayMode::NONE && _mode != ReplayMode::NORMALISATION) return false; @@ -213,9 +226,7 @@ namespace OpenRCT2 else replayData->tickEnd = k_MaxReplayTicks; - std::string replayName = String::StdFormat("%s.sv6r", name.c_str()); - std::string outPath = GetContext()->GetPlatformEnvironment()->GetDirectoryPath(DIRBASE::USER, DIRID::REPLAY); - replayData->filePath = Path::Combine(outPath, replayName); + replayData->filePath = name; auto context = GetContext(); auto& objManager = context->GetObjectManager(); @@ -239,18 +250,31 @@ namespace OpenRCT2 _mode = ReplayMode::RECORDING; _currentRecording = std::move(replayData); + _recordType = rt; _nextChecksumTick = gCurrentTicks + 1; return true; } - virtual bool StopRecording() override + virtual bool StopRecording(bool discard = false) override { if (_mode != ReplayMode::RECORDING && _mode != ReplayMode::NORMALISATION) return false; + if (discard) + { + _currentRecording.reset(); + _mode = ReplayMode::NONE; + return true; + } + _currentRecording->tickEnd = gCurrentTicks; + { + rct_sprite_checksum checksum = sprite_checksum(); + AddChecksum(gCurrentTicks, std::move(checksum)); + } + // Serialise Body. DataSerialiser recSerialiser(true); Serialise(recSerialiser, *_currentRecording); @@ -409,7 +433,7 @@ namespace OpenRCT2 return false; } - if (!StartRecording(outFile, k_MaxReplayTicks)) + if (!StartRecording(outFile, k_MaxReplayTicks, RecordType::NORMAL)) { StopPlayback(); return false; @@ -421,6 +445,18 @@ namespace OpenRCT2 } private: + int ChecksumTicksDelta() const + { + switch (_recordType) + { + default: + case RecordType::NORMAL: + return NormalRecordingChecksumTicks; + case RecordType::SILENT: + return SilentRecordingChecksumTicks; + } + } + bool LoadReplayDataMap(ReplayRecordData& data) { try @@ -787,6 +823,7 @@ namespace OpenRCT2 uint32_t _commandId = 0; uint32_t _nextChecksumTick = 0; uint32_t _nextReplayTick = 0; + RecordType _recordType = RecordType::NORMAL; }; std::unique_ptr CreateReplayManager() diff --git a/src/openrct2/ReplayManager.h b/src/openrct2/ReplayManager.h index 9e18692fa1..3922c3f1d0 100644 --- a/src/openrct2/ReplayManager.h +++ b/src/openrct2/ReplayManager.h @@ -35,6 +35,12 @@ namespace OpenRCT2 interface IReplayManager { public: + enum class RecordType + { + NORMAL, + SILENT, + }; + virtual ~IReplayManager() = default; virtual void Update() = 0; @@ -42,11 +48,14 @@ namespace OpenRCT2 virtual bool IsReplaying() const = 0; virtual bool IsRecording() const = 0; virtual bool IsNormalising() const = 0; + virtual bool ShouldDisplayNotice() const = 0; virtual void AddGameAction(uint32_t tick, const GameAction* action) = 0; - virtual bool StartRecording(const std::string& name, uint32_t maxTicks = k_MaxReplayTicks) = 0; - virtual bool StopRecording() = 0; + virtual bool StartRecording( + const std::string& name, uint32_t maxTicks = k_MaxReplayTicks, RecordType rt = RecordType::NORMAL) + = 0; + virtual bool StopRecording(bool discard = false) = 0; virtual bool GetCurrentReplayInfo(ReplayRecordInfo & info) const = 0; virtual bool StartPlayback(const std::string& file) = 0; diff --git a/src/openrct2/interface/InteractiveConsole.cpp b/src/openrct2/interface/InteractiveConsole.cpp index d6d75759d6..ad96ce2cca 100644 --- a/src/openrct2/interface/InteractiveConsole.cpp +++ b/src/openrct2/interface/InteractiveConsole.cpp @@ -13,6 +13,7 @@ #include "../EditorObjectSelectionSession.h" #include "../Game.h" #include "../OpenRCT2.h" +#include "../PlatformEnvironment.h" #include "../ReplayManager.h" #include "../Version.h" #include "../actions/ClimateSetAction.hpp" @@ -22,6 +23,7 @@ #include "../actions/StaffSetCostumeAction.hpp" #include "../config/Config.h" #include "../core/Guard.hpp" +#include "../core/Path.hpp" #include "../core/String.hpp" #include "../drawing/Drawing.h" #include "../drawing/Font.h" @@ -1366,6 +1368,14 @@ static int32_t cc_replay_startrecord(InteractiveConsole& console, const argument std::string name = argv[0]; + if (!String::EndsWith(name, ".sv6r", true)) + { + name += ".sv6r"; + } + std::string outPath = OpenRCT2::GetContext()->GetPlatformEnvironment()->GetDirectoryPath( + OpenRCT2::DIRBASE::USER, OpenRCT2::DIRID::REPLAY); + name = Path::Combine(outPath, name); + // If ticks are specified by user use that otherwise maximum ticks specified by const. uint32_t maxTicks = OpenRCT2::k_MaxReplayTicks; if (argv.size() >= 2) diff --git a/src/openrct2/paint/Painter.cpp b/src/openrct2/paint/Painter.cpp index e9498eff9e..f5667016af 100644 --- a/src/openrct2/paint/Painter.cpp +++ b/src/openrct2/paint/Painter.cpp @@ -64,7 +64,7 @@ void Painter::Paint(IDrawingEngine& de) if (replayManager->IsReplaying()) text = "Replaying..."; - else if (replayManager->IsRecording()) + else if (replayManager->ShouldDisplayNotice()) text = "Recording..."; else if (replayManager->IsNormalising()) text = "Normalising..."; diff --git a/src/openrct2/platform/Crash.cpp b/src/openrct2/platform/Crash.cpp index fef8f25ef4..eadccf6123 100644 --- a/src/openrct2/platform/Crash.cpp +++ b/src/openrct2/platform/Crash.cpp @@ -24,6 +24,8 @@ # error Breakpad support not implemented yet for this platform # endif +# include "../Game.h" +# include "../OpenRCT2.h" # include "../Version.h" # include "../config/Config.h" # include "../core/Console.hpp" @@ -108,10 +110,12 @@ static bool OnCrash( wchar_t saveFilePath[MAX_PATH]; wchar_t configFilePath[MAX_PATH]; wchar_t saveFilePathGZIP[MAX_PATH]; + wchar_t recordFilePathNew[MAX_PATH]; swprintf_s(dumpFilePath, std::size(dumpFilePath), L"%s\\%s.dmp", dumpPath, miniDumpId); swprintf_s(saveFilePath, std::size(saveFilePath), L"%s\\%s.sv6", dumpPath, miniDumpId); swprintf_s(configFilePath, std::size(configFilePath), L"%s\\%s.ini", dumpPath, miniDumpId); swprintf_s(saveFilePathGZIP, std::size(saveFilePathGZIP), L"%s\\%s.sv6.gz", dumpPath, miniDumpId); + swprintf_s(recordFilePathNew, std::size(recordFilePathNew), L"%s\\%s.sv6r", dumpPath, miniDumpId); wchar_t dumpFilePathNew[MAX_PATH]; swprintf_s( @@ -141,6 +145,8 @@ static bool OnCrash( fclose(dest); } + bool with_record = stop_silent_record(); + // Try to rename the files if (_wrename(dumpFilePath, dumpFilePathNew) == 0) { @@ -201,6 +207,20 @@ static bool OnCrash( uploadFiles[L"attachment_screenshot.png"] = screenshotPathW; } + if (with_record) + { + auto sv6rPathW = String::ToWideChar(gSilentRecordingName); + bool record_copied = CopyFileW(sv6rPathW.c_str(), recordFilePathNew, true); + if (record_copied) + { + uploadFiles[L"attachment_replay.sv6r"] = recordFilePathNew; + } + else + { + with_record = false; + } + } + if (gOpenRCT2SilentBreakpad) { int error; @@ -243,7 +263,7 @@ static bool OnCrash( if (SUCCEEDED(coInitializeResult)) { LPITEMIDLIST pidl = ILCreateFromPathW(dumpPath); - LPITEMIDLIST files[3]; + LPITEMIDLIST files[4]; uint32_t numFiles = 0; files[numFiles++] = ILCreateFromPathW(dumpFilePath); @@ -254,6 +274,10 @@ static bool OnCrash( { files[numFiles++] = ILCreateFromPathW(saveFilePath); } + if (with_record) + { + files[numFiles++] = ILCreateFromPathW(recordFilePathNew); + } if (pidl != nullptr) { SHOpenFolderAndSelectItems(pidl, numFiles, (LPCITEMIDLIST*)files, 0);