From 4eb7a7ee0e28b4da46ce0ab9559e6ec97ac4f274 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 8 May 2016 01:22:32 +0100 Subject: [PATCH] add S6 exporter --- openrct2.vcxproj | 2 + openrct2.vcxproj.filters | 2 + src/rct2/S6Exporter.cpp | 332 +++++++++++++++++++++++++++++++++++++++ src/rct2/S6Exporter.h | 46 ++++++ src/rct2/S6Importer.cpp | 8 +- src/rct2/S6Importer.h | 1 - src/scenario.c | 98 +----------- src/scenario.h | 5 + 8 files changed, 395 insertions(+), 99 deletions(-) create mode 100644 src/rct2/S6Exporter.cpp create mode 100644 src/rct2/S6Exporter.h diff --git a/openrct2.vcxproj b/openrct2.vcxproj index 51cb591bc9..f030bcdc6a 100644 --- a/openrct2.vcxproj +++ b/openrct2.vcxproj @@ -112,6 +112,7 @@ + @@ -371,6 +372,7 @@ + diff --git a/openrct2.vcxproj.filters b/openrct2.vcxproj.filters index c7fa8868ba..9f4c8a18c3 100644 --- a/openrct2.vcxproj.filters +++ b/openrct2.vcxproj.filters @@ -271,6 +271,7 @@ + @@ -384,6 +385,7 @@ + diff --git a/src/rct2/S6Exporter.cpp b/src/rct2/S6Exporter.cpp new file mode 100644 index 0000000000..56166030e5 --- /dev/null +++ b/src/rct2/S6Exporter.cpp @@ -0,0 +1,332 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include "../core/Exception.hpp" +#include "../core/IStream.hpp" +#include "../core/String.hpp" +#include "S6Exporter.h" + +extern "C" +{ + #include "../config.h" + #include "../game.h" + #include "../interface/viewport.h" + #include "../interface/window.h" + #include "../localisation/date.h" + #include "../localisation/localisation.h" + #include "../management/finance.h" + #include "../management/marketing.h" + #include "../management/news_item.h" + #include "../management/research.h" + #include "../object.h" + #include "../openrct2.h" + #include "../peep/staff.h" + #include "../ride/ride.h" + #include "../ride/ride_ratings.h" + #include "../scenario.h" + #include "../util/sawyercoding.h" + #include "../world/climate.h" + #include "../world/map_animation.h" + #include "../world/park.h" +} + +S6Exporter::S6Exporter() +{ + memset(&_s6, 0, sizeof(_s6)); +} + +void S6Exporter::SaveGame(const utf8 * path) +{ + SDL_RWops * rw = SDL_RWFromFile(path, "rb"); + if (rw == nullptr) + { + throw IOException("Unable to write to destination file."); + } + + SaveGame(rw); + + SDL_RWclose(rw); +} + +void S6Exporter::SaveGame(SDL_RWops *rw) +{ + Save(rw, false); +} + +void S6Exporter::SaveScenario(const utf8 * path) +{ + SDL_RWops * rw = SDL_RWFromFile(path, "rb"); + if (rw == nullptr) + { + throw IOException("Unable to write to destination file."); + } + + SaveGame(rw); + + SDL_RWclose(rw); +} + +void S6Exporter::SaveScenario(SDL_RWops *rw) +{ + Save(rw, true); +} + +void S6Exporter::Save(SDL_RWops * rw, bool isScenario) +{ + _s6.header.type = isScenario ? S6_TYPE_SAVEDGAME : S6_TYPE_SCENARIO; + _s6.header.num_packed_objects = scenario_get_num_packed_objects_to_write(); + _s6.header.version = S6_RCT2_VERSION; + _s6.header.magic_number = S6_MAGIC_NUMBER; + + uint8 * buffer = (uint8 *)malloc(0x600000); + if (buffer == NULL) + { + log_error("Unable to allocate enough space for a write buffer."); + throw Exception("Unable to allocate memory."); + } + + sawyercoding_chunk_header chunkHeader; + int encodedLength; + + // 0: Write header chunk + chunkHeader.encoding = CHUNK_ENCODING_ROTATE; + chunkHeader.length = sizeof(rct_s6_header); + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.header, chunkHeader); + SDL_RWwrite(rw, buffer, encodedLength, 1); + + // 1: Write scenario info chunk + if (_s6.header.type == S6_TYPE_SCENARIO) + { + chunkHeader.encoding = CHUNK_ENCODING_ROTATE; + chunkHeader.length = sizeof(rct_s6_info); + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.info, chunkHeader); + SDL_RWwrite(rw, buffer, encodedLength, 1); + } + + // 2: Write packed objects + if (_s6.header.num_packed_objects > 0) + { + if (!scenario_write_packed_objects(rw)) + { + free(buffer); + throw Exception("Unable to pack objects."); + } + } + + // 3: Write available objects chunk + chunkHeader.encoding = CHUNK_ENCODING_ROTATE; + chunkHeader.length = 721 * sizeof(rct_object_entry); + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)_s6.objects, chunkHeader); + SDL_RWwrite(rw, buffer, encodedLength, 1); + + // 4: Misc fields (data, rand...) chunk + chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; + chunkHeader.length = 16; + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.elapsed_months, chunkHeader); + SDL_RWwrite(rw, buffer, encodedLength, 1); + + // 5: Map elements + sprites and other fields chunk + chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; + chunkHeader.length = 0x180000; + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)_s6.map_elements, chunkHeader); + SDL_RWwrite(rw, buffer, encodedLength, 1); + + if (_s6.header.type == S6_TYPE_SCENARIO) + { + // 6: + chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; + chunkHeader.length = 0x27104C; + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.dword_010E63B8, chunkHeader); + SDL_RWwrite(rw, buffer, encodedLength, 1); + + // 7: + chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; + chunkHeader.length = 4; + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.guests_in_park, chunkHeader); + SDL_RWwrite(rw, buffer, encodedLength, 1); + + // 8: + chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; + chunkHeader.length = 8; + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.last_guests_in_park, chunkHeader); + SDL_RWwrite(rw, buffer, encodedLength, 1); + + // 9: + chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; + chunkHeader.length = 2; + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.park_rating, chunkHeader); + SDL_RWwrite(rw, buffer, encodedLength, 1); + + // 10: + chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; + chunkHeader.length = 1082; + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.active_research_types, chunkHeader); + SDL_RWwrite(rw, buffer, encodedLength, 1); + + // 11: + chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; + chunkHeader.length = 16; + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.current_expenditure, chunkHeader); + SDL_RWwrite(rw, buffer, encodedLength, 1); + + // 12: + chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; + chunkHeader.length = 4; + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.park_value, chunkHeader); + SDL_RWwrite(rw, buffer, encodedLength, 1); + + // 13: + chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; + chunkHeader.length = 0x761E8; + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.completed_company_value, chunkHeader); + SDL_RWwrite(rw, buffer, encodedLength, 1); + } + else + { + // 6: Everything else... + chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; + chunkHeader.length = 0x2E8570; + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.dword_010E63B8, chunkHeader); + SDL_RWwrite(rw, buffer, encodedLength, 1); + } + + free(buffer); + + // Determine number of bytes written + size_t fileSize = (size_t)SDL_RWtell(rw); + SDL_RWseek(rw, 0, RW_SEEK_SET); + + // Read all written bytes back into a single buffer + buffer = (uint8 *)malloc(fileSize); + SDL_RWread(rw, buffer, fileSize, 1); + uint32 checksum = sawyercoding_calculate_checksum(buffer, fileSize); + free(buffer); + + // Append the checksum + SDL_RWseek(rw, fileSize, RW_SEEK_SET); + SDL_RWwrite(rw, &checksum, sizeof(uint32), 1); +} + +void S6Exporter::Export() +{ + _s6.info = *(RCT2_ADDRESS(0x0141F570, rct_s6_info)); + + for (int i = 0; i < 721; i++) + { + rct_object_entry_extended *entry = &(RCT2_ADDRESS(0x00F3F03C, rct_object_entry_extended)[i]); + + if (gObjectList[i] == (void *)0xFFFFFFFF) + { + memset(&_s6.objects[i], 0xFF, sizeof(rct_object_entry)); + } + else + { + _s6.objects[i] = *((rct_object_entry*)entry); + } + } + + _s6.elapsed_months = gDateMonthsElapsed; + _s6.current_day = gDateMonthTicks; + _s6.scenario_ticks = RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_TICKS, uint32); + _s6.scenario_srand_0 = gScenarioSrand0; + _s6.scenario_srand_1 = gScenarioSrand1; + + memcpy(&_s6.elapsed_months, (void*)0x00F663A8, 16); + memcpy(_s6.map_elements, (void*)0x00F663B8, 0x180000); + memcpy(&_s6.dword_010E63B8, (void*)0x010E63B8, 0x2E8570); + + String::Set(_s6.scenario_filename, sizeof(_s6.scenario_filename), _scenarioFileName); + + scenario_fix_ghosts(&_s6); + game_convert_strings_to_rct2(&_s6); +} + +extern "C" +{ + // Save game state without modifying any of the state for multiplayer + int scenario_save_network(SDL_RWops * rw) + { + // Set saved view + sint16 viewX, viewY; + uint8 viewZoom, viewRotation; + rct_window * w = window_get_main(); + if (w != nullptr) + { + rct_viewport *viewport = w->viewport; + + viewX = viewport->view_width / 2 + viewport->view_x; + viewY = viewport->view_height / 2 + viewport->view_y; + viewZoom = viewport->zoom; + viewRotation = get_current_rotation(); + } + else + { + viewX = 0; + viewY = 0; + viewZoom = 0; + viewRotation = 0; + } + + gSavedViewX = viewX; + gSavedViewY = viewY; + gSavedViewZoom = viewZoom; + gSavedViewRotation = viewRotation; + + bool result = false; + auto s6exporter = new S6Exporter(); + try + { + s6exporter->Export(); + s6exporter->SaveGame(rw); + result = true; + } + catch (Exception) + { + } + delete s6exporter; + + if (!result) + { + return 0; + } + + reset_loaded_objects(); + + // Write other data not in normal save files + SDL_WriteLE32(rw, gGamePaused); + SDL_WriteLE32(rw, _guestGenerationProbability); + SDL_WriteLE32(rw, _suggestedGuestMaximum); + SDL_WriteU8(rw, gCheatsSandboxMode); + SDL_WriteU8(rw, gCheatsDisableClearanceChecks); + SDL_WriteU8(rw, gCheatsDisableSupportLimits); + SDL_WriteU8(rw, gCheatsDisableTrainLengthLimit); + SDL_WriteU8(rw, gCheatsShowAllOperatingModes); + SDL_WriteU8(rw, gCheatsShowVehiclesFromOtherTrackTypes); + SDL_WriteU8(rw, gCheatsFastLiftHill); + SDL_WriteU8(rw, gCheatsDisableBrakesFailure); + SDL_WriteU8(rw, gCheatsDisableAllBreakdowns); + SDL_WriteU8(rw, gCheatsUnlockAllPrices); + SDL_WriteU8(rw, gCheatsBuildInPauseMode); + SDL_WriteU8(rw, gCheatsIgnoreRideIntensity); + SDL_WriteU8(rw, gCheatsDisableVandalism); + SDL_WriteU8(rw, gCheatsDisableLittering); + SDL_WriteU8(rw, gCheatsNeverendingMarketing); + SDL_WriteU8(rw, gCheatsFreezeClimate); + + gfx_invalidate_screen(); + return 1; + } +} diff --git a/src/rct2/S6Exporter.h b/src/rct2/S6Exporter.h new file mode 100644 index 0000000000..7eddc93b13 --- /dev/null +++ b/src/rct2/S6Exporter.h @@ -0,0 +1,46 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include "../common.h" + +extern "C" +{ + #include "../scenario.h" +} + +/** + * Class to export RollerCoaster Tycoon 2 scenarios (*.SC6) and saved games (*.SV6). + */ +class S6Exporter +{ +public: + bool ExportObjects; + + S6Exporter(); + + void SaveGame(const utf8 * path); + void SaveGame(SDL_RWops *rw); + void SaveScenario(const utf8 * path); + void SaveScenario(SDL_RWops *rw); + void Export(); + +private: + rct_s6_data _s6; + + void Save(SDL_RWops *rw, bool isScenario); +}; diff --git a/src/rct2/S6Importer.cpp b/src/rct2/S6Importer.cpp index 9e7e6e58a7..e7f3f84b71 100644 --- a/src/rct2/S6Importer.cpp +++ b/src/rct2/S6Importer.cpp @@ -54,7 +54,8 @@ S6Importer::S6Importer() void S6Importer::LoadSavedGame(const utf8 * path) { SDL_RWops * rw = SDL_RWFromFile(path, "rb"); - if (rw == nullptr) { + if (rw == nullptr) + { throw IOException("Unable to open SV6."); } @@ -77,7 +78,8 @@ void S6Importer::LoadSavedGame(const utf8 * path) void S6Importer::LoadScenario(const utf8 * path) { SDL_RWops * rw = SDL_RWFromFile(path, "rb"); - if (rw == nullptr) { + if (rw == nullptr) + { throw IOException("Unable to open SV6."); } @@ -452,7 +454,7 @@ extern "C" catch (IOException) { gErrorType = ERROR_TYPE_FILE_LOAD; - gErrorStringId = STR_UNABLE_TO_LOAD_FILE; + gErrorStringId = STR_GAME_SAVE_FAILED; } catch (Exception) { diff --git a/src/rct2/S6Importer.h b/src/rct2/S6Importer.h index f0da502f82..49d6a329b4 100644 --- a/src/rct2/S6Importer.h +++ b/src/rct2/S6Importer.h @@ -17,7 +17,6 @@ #pragma once #include "../common.h" -#include "../core/List.hpp" extern "C" { diff --git a/src/scenario.c b/src/scenario.c index 75591e9672..8a8e70f7fc 100644 --- a/src/scenario.c +++ b/src/scenario.c @@ -55,7 +55,7 @@ const rct_string_id ScenarioCategoryStringIds[SCENARIO_CATEGORY_COUNT] = { }; static char _scenarioPath[MAX_PATH]; -static const char *_scenarioFileName = ""; +const char *_scenarioFileName = ""; char *gScenarioName = RCT2_ADDRESS(RCT2_ADDRESS_SCENARIO_NAME, char); char *gScenarioDetails = RCT2_ADDRESS(RCT2_ADDRESS_SCENARIO_DETAILS, char); @@ -789,9 +789,9 @@ static void sub_674BCF() } /** - * Modifys the given S6 data so that ghost elements, rides with no track elements or unused banners / user strings are saved. + * Modifies the given S6 data so that ghost elements, rides with no track elements or unused banners / user strings are saved. */ -static void scenario_fix_ghosts(rct_s6_data *s6) +void scenario_fix_ghosts(rct_s6_data *s6) { // Remove all ghost elements size_t mapElementTotalSize = MAX_MAP_ELEMENTS * sizeof(rct_map_element); @@ -928,98 +928,6 @@ int scenario_save(SDL_RWops* rw, int flags) return 1; } -// Save game state without modifying any of the state for multiplayer -int scenario_save_network(SDL_RWops* rw) -{ - rct_window *w; - rct_viewport *viewport; - int viewX, viewY, viewZoom, viewRotation; - - /*map_reorganise_elements(); - reset_0x69EBE4(); - sprite_clear_all_unused(); - sub_677552(); - sub_674BCF();*/ - - // Set saved view - w = window_get_main(); - if (w != NULL) { - viewport = w->viewport; - - viewX = viewport->view_width / 2 + viewport->view_x; - viewY = viewport->view_height / 2 + viewport->view_y; - viewZoom = viewport->zoom; - viewRotation = get_current_rotation(); - } else { - viewX = 0; - viewY = 0; - viewZoom = 0; - viewRotation = 0; - } - - gSavedViewX = viewX; - gSavedViewY = viewY; - gSavedViewZoom = viewZoom; - gSavedViewRotation = viewRotation; - - // Prepare S6 - rct_s6_data *s6 = malloc(sizeof(rct_s6_data)); - s6->header.type = S6_TYPE_SAVEDGAME; - s6->header.num_packed_objects = scenario_get_num_packed_objects_to_write(); - s6->header.version = S6_RCT2_VERSION; - s6->header.magic_number = S6_MAGIC_NUMBER; - - memcpy(&s6->info, (rct_s6_info*)0x0141F570, sizeof(rct_s6_info)); - - for (int i = 0; i < 721; i++) { - rct_object_entry_extended *entry = &(RCT2_ADDRESS(0x00F3F03C, rct_object_entry_extended)[i]); - - if (gObjectList[i] == (void *)0xFFFFFFFF) { - memset(&s6->objects[i], 0xFF, sizeof(rct_object_entry)); - } else { - s6->objects[i] = *((rct_object_entry*)entry); - } - } - - memcpy(&s6->elapsed_months, (void*)0x00F663A8, 16); - memcpy(s6->map_elements, (void*)0x00F663B8, 0x180000); - memcpy(&s6->dword_010E63B8, (void*)0x010E63B8, 0x2E8570); - - safe_strcpy(s6->scenario_filename, _scenarioFileName, sizeof(s6->scenario_filename)); - - scenario_fix_ghosts(s6); - game_convert_strings_to_rct2(s6); - scenario_save_s6(rw, s6); - - free(s6); - - reset_loaded_objects(); - - // Write other data not in normal save files - SDL_WriteLE32(rw, gGamePaused); - SDL_WriteLE32(rw, _guestGenerationProbability); - SDL_WriteLE32(rw, _suggestedGuestMaximum); - SDL_WriteU8(rw, gCheatsSandboxMode); - SDL_WriteU8(rw, gCheatsDisableClearanceChecks); - SDL_WriteU8(rw, gCheatsDisableSupportLimits); - SDL_WriteU8(rw, gCheatsDisableTrainLengthLimit); - SDL_WriteU8(rw, gCheatsShowAllOperatingModes); - SDL_WriteU8(rw, gCheatsShowVehiclesFromOtherTrackTypes); - SDL_WriteU8(rw, gCheatsFastLiftHill); - SDL_WriteU8(rw, gCheatsDisableBrakesFailure); - SDL_WriteU8(rw, gCheatsDisableAllBreakdowns); - SDL_WriteU8(rw, gCheatsUnlockAllPrices); - SDL_WriteU8(rw, gCheatsBuildInPauseMode); - SDL_WriteU8(rw, gCheatsIgnoreRideIntensity); - SDL_WriteU8(rw, gCheatsDisableVandalism); - SDL_WriteU8(rw, gCheatsDisableLittering); - SDL_WriteU8(rw, gCheatsNeverendingMarketing); - SDL_WriteU8(rw, gCheatsFreezeClimate); - - gfx_invalidate_screen(); - return 1; -} - bool scenario_save_s6(SDL_RWops* rw, rct_s6_data *s6) { uint8 *buffer; diff --git a/src/scenario.h b/src/scenario.h index 95b121f6b7..35f3235606 100644 --- a/src/scenario.h +++ b/src/scenario.h @@ -455,6 +455,8 @@ extern char gScenarioSavePath[MAX_PATH]; extern int gFirstTimeSave; extern uint32 gLastAutoSaveTick; +extern const char *_scenarioFileName; + bool scenario_scores_save(); void scenario_load_list(); void scenario_list_dispose(); @@ -473,6 +475,9 @@ int scenario_prepare_for_save(); int scenario_save(SDL_RWops* rw, int flags); int scenario_save_network(SDL_RWops* rw); bool scenario_save_s6(SDL_RWops* rw, rct_s6_data *s6); +int scenario_get_num_packed_objects_to_write(); +int scenario_write_packed_objects(SDL_RWops* rw); +void scenario_fix_ghosts(rct_s6_data *s6); void scenario_set_filename(const char *value); void scenario_failure(); void scenario_success();