diff --git a/src/openrct2-ui/windows/LoadSave.cpp b/src/openrct2-ui/windows/LoadSave.cpp index 574cd128c4..030b9e7531 100644 --- a/src/openrct2-ui/windows/LoadSave.cpp +++ b/src/openrct2-ui/windows/LoadSave.cpp @@ -201,13 +201,13 @@ static const char* getFilterPatternByType(const int32_t type, const bool isSave) switch (type & 0x0E) { case LOADSAVETYPE_GAME: - return isSave ? "*.sv6" : "*.sv6;*.sc6;*.sc4;*.sv4;*.sv7;*.sea;"; + return isSave ? "*.park" : "*.sv6;*.sc6;*.sc4;*.sv4;*.sv7;*.sea;"; case LOADSAVETYPE_LANDSCAPE: - return isSave ? "*.sc6" : "*.sc6;*.sv6;*.sc4;*.sv4;*.sv7;*.sea;"; + return isSave ? "*.park" : "*.sc6;*.sv6;*.sc4;*.sv4;*.sv7;*.sea;"; case LOADSAVETYPE_SCENARIO: - return "*.sc6"; + return "*.park"; case LOADSAVETYPE_TRACK: return isSave ? "*.td6" : "*.td6;*.td4"; diff --git a/src/openrct2/ParkFile.cpp b/src/openrct2/ParkFile.cpp index 949ab45be4..45ac9d21ff 100644 --- a/src/openrct2/ParkFile.cpp +++ b/src/openrct2/ParkFile.cpp @@ -1,9 +1,19 @@ #include "ParkFile.h" + +#include "OpenRCT2.h" #include "Version.h" +#include "core/Crypt.h" +#include "drawing/Drawing.h" +#include "interface/Viewport.h" +#include "interface/Window.h" +#include "localisation/Date.h" +#include "scenario/Scenario.h" +#include "world/Map.h" +#include "world/Park.h" using namespace OpenRCT2; -constexpr uint32_t PARK_FILE_MAGIC = 0x4B526550; // PARK +constexpr uint32_t PARK_FILE_MAGIC = 0x4B524150; // PARK // Current version that is saved. constexpr uint32_t PARK_FILE_CURRENT_VERSION = 0x0; @@ -46,14 +56,164 @@ void ParkFile::Save(const std::string_view& path) _header.TargetVersion = PARK_FILE_CURRENT_VERSION; _header.MinVersion = PARK_FILE_MIN_VERSION; _header.Compression = COMPRESSION_NONE; + + WriteAuthoringChunk(); + WriteGeneralChunk(); + + // TODO avoid copying the buffer + auto uncompressedData = _buffer.str(); + + _header.NumChunks = (uint32_t)_chunks.size(); + _header.UncompressedSize = _buffer.tellp(); + _header.Sha1 = Crypt::SHA1(uncompressedData.data(), uncompressedData.size()); + + std::ofstream fs(std::string(path).c_str(), std::ios::binary); + WriteHeader(fs); + fs.write(uncompressedData.data(), uncompressedData.size()); } -void ParkFile::WriteHeader(std::ostream& stream, const Header& header, const std::vector& chunks) +void ParkFile::WriteHeader(std::ostream& fs) { - stream.seekp(0); - stream.write((const char*)&header, sizeof(header)); - for (const auto& chunk : chunks) + fs.seekp(0); + fs.write((const char*)&_header, sizeof(_header)); + for (const auto& chunk : _chunks) { - stream.write((const char*)&chunk, sizeof(chunk)); + fs.write((const char*)&chunk, sizeof(chunk)); } } + +void ParkFile::BeginChunk(uint32_t id) +{ + _currentChunk.Id = id; + _currentChunk.Offset = _buffer.tellp(); + _currentChunk.Length = 0; +} + +void ParkFile::EndChunk() +{ + _currentChunk.Length = (uint64_t)_buffer.tellp() - _currentChunk.Offset; + _chunks.push_back(_currentChunk); +} + +void ParkFile::BeginArray(size_t count, size_t elementSize) +{ + WriteValue((uint32_t)count); + WriteValue((uint32_t)elementSize); +} + +void ParkFile::EndArray() +{ +} + +void ParkFile::WriteBuffer(const void* buffer, size_t len) +{ + _buffer.write((char*)buffer, len); +} + +void ParkFile::WriteString(const std::string_view& s) +{ + char nullt = '\0'; + _buffer.write(s.data(), s.size()); + _buffer.write(&nullt, sizeof(nullt)); +} + +void ParkFile::WriteAuthoringChunk() +{ + BeginChunk(ParkFileChunkType::AUTHORING); + WriteString(gVersionInfoFull); + WriteString("Some notes..."); + EndChunk(); +} + +void ParkFile::WriteGeneralChunk() +{ + BeginChunk(ParkFileChunkType::GENERAL); + WriteValue(gScenarioTicks); + WriteValue(gDateMonthTicks); + WriteValue(gDateMonthsElapsed); + WriteValue(gScenarioSrand0); + WriteValue(gScenarioSrand1); + WriteValue(gInitialCash); + WriteValue(gGuestInitialCash); + WriteValue(gGuestInitialHunger); + WriteValue(gGuestInitialThirst); + + auto numSpawns = (gPeepSpawns[0].isNull() ? 1 : 0) + (gPeepSpawns[1].isNull() ? 1 : 0); + BeginArray(numSpawns, 13); + for (const auto& spawn : gPeepSpawns) + { + if (!gPeepSpawns->isNull()) + { + WriteValue(spawn.x); + WriteValue(spawn.y); + WriteValue(spawn.z); + WriteValue(spawn.direction); + } + } + EndArray(); + + WriteValue(gLandPrice); + WriteValue(gConstructionRightsPrice); + EndChunk(); +} + +enum : uint32_t +{ + S6_SAVE_FLAG_EXPORT = 1 << 0, + S6_SAVE_FLAG_SCENARIO = 1 << 1, + S6_SAVE_FLAG_AUTOMATIC = 1u << 31, +}; + +int32_t scenario_save(const utf8* path, int32_t flags) +{ + if (flags & S6_SAVE_FLAG_SCENARIO) + { + log_verbose("saving scenario"); + } + else + { + log_verbose("saving game"); + } + + if (!(flags & S6_SAVE_FLAG_AUTOMATIC)) + { + window_close_construction_windows(); + } + + map_reorganise_elements(); + viewport_set_saved_view(); + + bool result = false; + auto parkFile = std::make_unique(); + try + { + // if (flags & S6_SAVE_FLAG_EXPORT) + // { + // auto& objManager = OpenRCT2::GetContext()->GetObjectManager(); + // s6exporter->ExportObjectsList = objManager.GetPackableObjects(); + // } + // s6exporter->RemoveTracklessRides = true; + // s6exporter->Export(); + if (flags & S6_SAVE_FLAG_SCENARIO) + { + // s6exporter->SaveScenario(path); + } + else + { + // s6exporter->SaveGame(path); + } + parkFile->Save(path); + result = true; + } + catch (const std::exception&) + { + } + + gfx_invalidate_screen(); + + if (result && !(flags & S6_SAVE_FLAG_AUTOMATIC)) + { + gScreenAge = 0; + } + return result; +} diff --git a/src/openrct2/ParkFile.h b/src/openrct2/ParkFile.h index 7669e25c32..3593030bba 100644 --- a/src/openrct2/ParkFile.h +++ b/src/openrct2/ParkFile.h @@ -1,5 +1,7 @@ +#include #include #include +#include #include #include @@ -20,7 +22,7 @@ namespace OpenRCT2 uint32_t NumChunks{}; uint64_t UncompressedSize{}; uint32_t Compression{}; - uint8_t Sha1{}; + std::array Sha1; }; struct ChunkEntry @@ -33,10 +35,25 @@ namespace OpenRCT2 Header _header; std::vector _chunks; + std::stringstream _buffer; ChunkEntry _currentChunk; - void WriteHeader(std::ostream& stream, const Header& header, const std::vector& chunks); - void BeginChunk(std::ostream& stream); - uint64_t EndChunk(); + void WriteHeader(std::ostream& fs); + void BeginChunk(uint32_t id); + void EndChunk(); + + void WriteBuffer(const void* buffer, size_t len); + void WriteString(const std::string_view& s); + void BeginArray(size_t count, size_t elementSize); + void EndArray(); + + template + void WriteValue(T v) + { + WriteBuffer(&v, sizeof(T)); + } + + void WriteAuthoringChunk(); + void WriteGeneralChunk(); }; } // namespace OpenRCT2 diff --git a/src/openrct2/rct2/S6Exporter.cpp b/src/openrct2/rct2/S6Exporter.cpp index 6a1301a925..dcb8987e9b 100644 --- a/src/openrct2/rct2/S6Exporter.cpp +++ b/src/openrct2/rct2/S6Exporter.cpp @@ -1815,7 +1815,7 @@ enum : uint32_t * rct2: 0x006754F5 * @param flags bit 0: pack objects, 1: save as scenario */ -int32_t scenario_save(const utf8* path, int32_t flags) +int32_t scenario_save_old(const utf8* path, int32_t flags) { if (flags & S6_SAVE_FLAG_SCENARIO) {