diff --git a/OpenRCT2.xcodeproj/project.pbxproj b/OpenRCT2.xcodeproj/project.pbxproj index 2cd9a92cb7..2be98385ab 100644 --- a/OpenRCT2.xcodeproj/project.pbxproj +++ b/OpenRCT2.xcodeproj/project.pbxproj @@ -10,7 +10,6 @@ 008BF72A1CDAA5C30019A2AD /* track_design_index.c in Sources */ = {isa = PBXBuildFile; fileRef = 008BF7261CDAA5C30019A2AD /* track_design_index.c */; }; 008BF72B1CDAA5C30019A2AD /* track_design_save.c in Sources */ = {isa = PBXBuildFile; fileRef = 008BF7271CDAA5C30019A2AD /* track_design_save.c */; }; 008BF72C1CDAA5C30019A2AD /* track_design.c in Sources */ = {isa = PBXBuildFile; fileRef = 008BF7281CDAA5C30019A2AD /* track_design.c */; }; - C61FAAE21CD1643A0010C9D8 /* paint_surface.c in Sources */ = {isa = PBXBuildFile; fileRef = C61FAAE01CD1643A0010C9D8 /* paint_surface.c */; }; C650B2191CCABBDD00B4D91C /* S4Importer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C650B2151CCABBDD00B4D91C /* S4Importer.cpp */; }; C650B21A1CCABBDD00B4D91C /* tables.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C650B2171CCABBDD00B4D91C /* tables.cpp */; }; C650B21C1CCABC4400B4D91C /* ConvertCommand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C650B21B1CCABC4400B4D91C /* ConvertCommand.cpp */; }; @@ -104,6 +103,8 @@ C686F9541CDBC3B7009F9BFC /* submarine_ride.c in Sources */ = {isa = PBXBuildFile; fileRef = C686F9091CDBC3B7009F9BFC /* submarine_ride.c */; }; C686F9551CDBC3B7009F9BFC /* water_coaster.c in Sources */ = {isa = PBXBuildFile; fileRef = C686F90A1CDBC3B7009F9BFC /* water_coaster.c */; }; C686F9581CDBC4C7009F9BFC /* vehicle_paint.c in Sources */ = {isa = PBXBuildFile; fileRef = C686F9561CDBC4C7009F9BFC /* vehicle_paint.c */; }; + C6B5A7D41CDFE4CB00C9C006 /* S6Exporter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C6B5A7D01CDFE4CB00C9C006 /* S6Exporter.cpp */; }; + C6B5A7D51CDFE4CB00C9C006 /* S6Importer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C6B5A7D21CDFE4CB00C9C006 /* S6Importer.cpp */; }; D41B73EF1C2101890080A7B9 /* libcurl.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D41B73EE1C2101890080A7B9 /* libcurl.tbd */; }; D41B73F11C21018C0080A7B9 /* libssl.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D41B73F01C21018C0080A7B9 /* libssl.tbd */; }; D41B741D1C210A7A0080A7B9 /* libiconv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D41B741C1C210A7A0080A7B9 /* libiconv.tbd */; }; @@ -326,8 +327,6 @@ 008BF7271CDAA5C30019A2AD /* track_design_save.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = track_design_save.c; sourceTree = ""; }; 008BF7281CDAA5C30019A2AD /* track_design.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = track_design.c; sourceTree = ""; }; 008BF7291CDAA5C30019A2AD /* track_design.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = track_design.h; sourceTree = ""; }; - C61FAAE01CD1643A0010C9D8 /* paint_surface.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = paint_surface.c; sourceTree = ""; }; - C61FAAE11CD1643A0010C9D8 /* paint_surface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = paint_surface.h; sourceTree = ""; }; C650B2151CCABBDD00B4D91C /* S4Importer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = S4Importer.cpp; sourceTree = ""; }; C650B2161CCABBDD00B4D91C /* S4Importer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S4Importer.h; sourceTree = ""; }; C650B2171CCABBDD00B4D91C /* tables.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tables.cpp; sourceTree = ""; }; @@ -429,6 +428,10 @@ C686F90A1CDBC3B7009F9BFC /* water_coaster.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = water_coaster.c; sourceTree = ""; }; C686F9561CDBC4C7009F9BFC /* vehicle_paint.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = vehicle_paint.c; sourceTree = ""; }; C686F9571CDBC4C7009F9BFC /* vehicle_paint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vehicle_paint.h; sourceTree = ""; }; + C6B5A7D01CDFE4CB00C9C006 /* S6Exporter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = S6Exporter.cpp; sourceTree = ""; }; + C6B5A7D11CDFE4CB00C9C006 /* S6Exporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S6Exporter.h; sourceTree = ""; }; + C6B5A7D21CDFE4CB00C9C006 /* S6Importer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = S6Importer.cpp; sourceTree = ""; }; + C6B5A7D31CDFE4CB00C9C006 /* S6Importer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S6Importer.h; sourceTree = ""; }; D41B73EE1C2101890080A7B9 /* libcurl.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libcurl.tbd; path = usr/lib/libcurl.tbd; sourceTree = SDKROOT; }; D41B73F01C21018C0080A7B9 /* libssl.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libssl.tbd; path = usr/lib/libssl.tbd; sourceTree = SDKROOT; }; D41B741C1C210A7A0080A7B9 /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = usr/lib/libiconv.tbd; sourceTree = SDKROOT; }; @@ -981,6 +984,18 @@ path = water; sourceTree = ""; }; + C6B5A7CF1CDFE4CB00C9C006 /* rct2 */ = { + isa = PBXGroup; + children = ( + C6B5A7D01CDFE4CB00C9C006 /* S6Exporter.cpp */, + C6B5A7D11CDFE4CB00C9C006 /* S6Exporter.h */, + C6B5A7D21CDFE4CB00C9C006 /* S6Importer.cpp */, + C6B5A7D31CDFE4CB00C9C006 /* S6Importer.h */, + ); + name = rct2; + path = src/rct2; + sourceTree = ""; + }; D41B72431C21015A0080A7B9 /* Sources */ = { isa = PBXGroup; children = ( @@ -996,6 +1011,7 @@ D442715B1CC81B3200D84D28 /* peep */, D44271601CC81B3200D84D28 /* platform */, C650B2141CCABBDD00B4D91C /* rct1 */, + C6B5A7CF1CDFE4CB00C9C006 /* rct2 */, D442716E1CC81B3200D84D28 /* ride */, D44271881CC81B3200D84D28 /* util */, D442718E1CC81B3200D84D28 /* windows */, @@ -1874,6 +1890,7 @@ D442720E1CC81B3200D84D28 /* rect.c in Sources */, C686F9171CDBC3B7009F9BFC /* lim_launched_roller_coaster.c in Sources */, C686F9101CDBC3B7009F9BFC /* giga_coaster.c in Sources */, + C6B5A7D41CDFE4CB00C9C006 /* S6Exporter.cpp in Sources */, D44272351CC81B3200D84D28 /* twitch.cpp in Sources */, D44272691CC81B3200D84D28 /* loadsave.c in Sources */, D44272061CC81B3200D84D28 /* textinputbuffer.c in Sources */, @@ -1973,6 +1990,7 @@ D442727A1CC81B3200D84D28 /* research.c in Sources */, D442722E1CC81B3200D84D28 /* award.c in Sources */, D44272861CC81B3200D84D28 /* staff_fire_prompt.c in Sources */, + C6B5A7D51CDFE4CB00C9C006 /* S6Importer.cpp in Sources */, D44272221CC81B3200D84D28 /* widget.c in Sources */, D44271F71CC81B3200D84D28 /* mixer.cpp in Sources */, D442723D1CC81B3200D84D28 /* osx.m in Sources */, diff --git a/openrct2.vcxproj b/openrct2.vcxproj index fc17260493..f030bcdc6a 100644 --- a/openrct2.vcxproj +++ b/openrct2.vcxproj @@ -112,6 +112,8 @@ + + @@ -370,6 +372,8 @@ + + diff --git a/openrct2.vcxproj.filters b/openrct2.vcxproj.filters index 8a5673ba04..9f4c8a18c3 100644 --- a/openrct2.vcxproj.filters +++ b/openrct2.vcxproj.filters @@ -270,6 +270,8 @@ + + @@ -382,6 +384,8 @@ + + diff --git a/src/addresses.h b/src/addresses.h index 4dbf6177b4..4636f4614f 100644 --- a/src/addresses.h +++ b/src/addresses.h @@ -320,8 +320,8 @@ #define RCT2_ADDRESS_CURRENT_MONEY_ENCRYPTED 0x013587F8 #define RCT2_ADDRESS_CURRENT_INTEREST_RATE 0x0135934A #define RCT2_ADDRESS_SAME_PRICE_THROUGHOUT 0x01358838 +#define RCT2_ADDRESS_LAST_ENTRANCE_STYLE 0x01358840 #define RCT2_ADDRESS_SAME_PRICE_THROUGHOUT_EXTENDED 0x0135934C - #define RCT2_ADDRESS_GUEST_INITIAL_CASH 0x013580F4 #define RCT2_ADDRESS_GUEST_INITIAL_HAPPINESS 0x013580E9 #define RCT2_ADDRESS_GUEST_INITIAL_HUNGER 0x013580F6 diff --git a/src/game.c b/src/game.c index 26b06f3e15..6d7f5fb42a 100644 --- a/src/game.c +++ b/src/game.c @@ -733,75 +733,6 @@ void game_convert_strings_to_rct2(rct_s6_data *s6) } } -/** - * - * rct2: 0x00675E1B - */ -int game_load_sv6(SDL_RWops* rw) -{ - int i, j; - - if (!sawyercoding_validate_checksum(rw)) { - log_error("invalid checksum"); - - gErrorType = ERROR_TYPE_FILE_LOAD; - gGameCommandErrorTitle = STR_FILE_CONTAINS_INVALID_DATA; - return 0; - } - - rct_s6_header *s6Header = (rct_s6_header*)0x009E34E4; - rct_s6_info *s6Info = (rct_s6_info*)0x0141F570; - - // Read first chunk - sawyercoding_read_chunk(rw, (uint8*)s6Header); - if (s6Header->type == S6_TYPE_SAVEDGAME) { - // Read packed objects - if (s6Header->num_packed_objects > 0) { - j = 0; - for (i = 0; i < s6Header->num_packed_objects; i++) - j += object_load_packed(rw); - if (j > 0) - object_list_load(); - } - } - - uint8 load_success = object_read_and_load_entries(rw); - - // Read flags (16 bytes) - sawyercoding_read_chunk(rw, (uint8*)RCT2_ADDRESS_CURRENT_MONTH_YEAR); - - // Read map elements - memset((void*)RCT2_ADDRESS_MAP_ELEMENTS, 0, MAX_MAP_ELEMENTS * sizeof(rct_map_element)); - sawyercoding_read_chunk(rw, (uint8*)RCT2_ADDRESS_MAP_ELEMENTS); - - // Read game data, including sprites - sawyercoding_read_chunk(rw, (uint8*)0x010E63B8); - - if (!load_success){ - set_load_objects_fail_reason(); - if (gInputFlags & INPUT_FLAG_5){ - //call 0x0040705E Sets cursor position and something else. Calls maybe wind func 8 probably pointless - gInputFlags &= ~INPUT_FLAG_5; - } - - return 0;//This never gets called - } - - // The rest is the same as in scenario_load - reset_loaded_objects(); - map_update_tile_pointers(); - reset_0x69EBE4(); - openrct2_reset_object_tween_locations(); - game_convert_strings_to_utf8(); - game_fix_save_vars(); // OpenRCT2 fix broken save games - - // #2407: Resetting screen time to not open a save prompt shortly after loading a park. - gScreenAge = 0; - - gLastAutoSaveTick = SDL_GetTicks(); - return 1; -} - // OpenRCT2 workaround to recalculate some values which are saved redundantly in the save to fix corrupted files. // For example recalculate guest count by looking at all the guests instead of trusting the value in the file. void game_fix_save_vars() { @@ -835,84 +766,6 @@ void game_fix_save_vars() { } } -// Load game state for multiplayer -int game_load_network(SDL_RWops* rw) -{ - int i, j; - - rct_s6_header *s6Header = (rct_s6_header*)0x009E34E4; - rct_s6_info *s6Info = (rct_s6_info*)0x0141F570; - - // Read first chunk - sawyercoding_read_chunk(rw, (uint8*)s6Header); - if (s6Header->type == S6_TYPE_SAVEDGAME) { - // Read packed objects - if (s6Header->num_packed_objects > 0) { - j = 0; - for (i = 0; i < s6Header->num_packed_objects; i++) - j += object_load_packed(rw); - if (j > 0) - object_list_load(); - } - } - - uint8 load_success = object_read_and_load_entries(rw); - - // Read flags (16 bytes) - sawyercoding_read_chunk(rw, (uint8*)RCT2_ADDRESS_CURRENT_MONTH_YEAR); - - // Read map elements - memset((void*)RCT2_ADDRESS_MAP_ELEMENTS, 0, MAX_MAP_ELEMENTS * sizeof(rct_map_element)); - sawyercoding_read_chunk(rw, (uint8*)RCT2_ADDRESS_MAP_ELEMENTS); - - // Read game data, including sprites - sawyercoding_read_chunk(rw, (uint8*)0x010E63B8); - - // Read checksum - uint32 checksum; - SDL_RWread(rw, &checksum, sizeof(uint32), 1); - - // Read other data not in normal save files - gGamePaused = SDL_ReadLE32(rw); - _guestGenerationProbability = SDL_ReadLE32(rw); - _suggestedGuestMaximum = SDL_ReadLE32(rw); - gCheatsSandboxMode = SDL_ReadU8(rw); - gCheatsDisableClearanceChecks = SDL_ReadU8(rw); - gCheatsDisableSupportLimits = SDL_ReadU8(rw); - gCheatsDisableTrainLengthLimit = SDL_ReadU8(rw); - gCheatsShowAllOperatingModes = SDL_ReadU8(rw); - gCheatsShowVehiclesFromOtherTrackTypes = SDL_ReadU8(rw); - gCheatsFastLiftHill = SDL_ReadU8(rw); - gCheatsDisableBrakesFailure = SDL_ReadU8(rw); - gCheatsDisableAllBreakdowns = SDL_ReadU8(rw); - gCheatsUnlockAllPrices = SDL_ReadU8(rw); - gCheatsBuildInPauseMode = SDL_ReadU8(rw); - gCheatsIgnoreRideIntensity = SDL_ReadU8(rw); - gCheatsDisableVandalism = SDL_ReadU8(rw); - gCheatsDisableLittering = SDL_ReadU8(rw); - gCheatsNeverendingMarketing = SDL_ReadU8(rw); - gCheatsFreezeClimate = SDL_ReadU8(rw); - - if (!load_success){ - set_load_objects_fail_reason(); - if (gInputFlags & INPUT_FLAG_5){ - //call 0x0040705E Sets cursor position and something else. Calls maybe wind func 8 probably pointless - gInputFlags &= ~INPUT_FLAG_5; - } - - return 0;//This never gets called - } - - // The rest is the same as in scenario load and play - reset_loaded_objects(); - map_update_tile_pointers(); - reset_0x69EBE4(); - openrct2_reset_object_tween_locations(); - game_convert_strings_to_utf8(); - gLastAutoSaveTick = SDL_GetTicks(); - return 1; -} - /** * * rct2: 0x00675E1B diff --git a/src/interface/viewport.c b/src/interface/viewport.c index 26b5fec266..19fbb254ad 100644 --- a/src/interface/viewport.c +++ b/src/interface/viewport.c @@ -1668,3 +1668,26 @@ sint16 get_height_marker_offset() // Height labels in metres return 2 * 256; } + +void viewport_set_saved_view() +{ + sint16 viewX = 0; + sint16 viewY = 0; + uint8 viewZoom = 0; + uint8 viewRotation = 0; + + rct_window * w = window_get_main(); + if (w != NULL) { + 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(); + } + + gSavedViewX = viewX; + gSavedViewY = viewY; + gSavedViewZoom = viewZoom; + gSavedViewRotation = viewRotation; +} diff --git a/src/interface/viewport.h b/src/interface/viewport.h index 6a9dc679de..d890ec9175 100644 --- a/src/interface/viewport.h +++ b/src/interface/viewport.h @@ -148,4 +148,6 @@ void screen_get_map_xy_side_with_z(sint16 screenX, sint16 screenY, sint16 z, sin uint8 get_current_rotation(); sint16 get_height_marker_offset(); +void viewport_set_saved_view(); + #endif diff --git a/src/management/news_item.h b/src/management/news_item.h index 992def6738..cf4e348231 100644 --- a/src/management/news_item.h +++ b/src/management/news_item.h @@ -49,6 +49,8 @@ typedef struct { #define MAX_NEWS_ITEMS 60 +extern rct_news_item *gNewsItems; + void news_item_init_queue(); void news_item_update_current(); void news_item_close_current(); diff --git a/src/object.h b/src/object.h index 39c49a6ffb..d01c7df67f 100644 --- a/src/object.h +++ b/src/object.h @@ -104,7 +104,8 @@ extern void *gLastLoadedObjectChunkData; int object_load_entry(const utf8 *path, rct_object_entry *outEntry); void object_list_load(); void set_load_objects_fail_reason(); -int object_read_and_load_entries(SDL_RWops* rw); +bool object_read_and_load_entries(SDL_RWops* rw); +bool object_load_entries(rct_object_entry* entries); int object_load_packed(SDL_RWops* rw); void object_unload_all(); diff --git a/src/object_list.c b/src/object_list.c index e441841654..3281dbc6f8 100644 --- a/src/object_list.c +++ b/src/object_list.c @@ -530,28 +530,32 @@ void set_load_objects_fail_reason() * * rct2: 0x006AA0C6 */ -int object_read_and_load_entries(SDL_RWops* rw) +bool object_read_and_load_entries(SDL_RWops* rw) { object_unload_all(); - int i, j; - rct_object_entry *entries; + // Read all the object entries + rct_object_entry *entries = malloc(OBJECT_ENTRY_COUNT * sizeof(rct_object_entry)); + sawyercoding_read_chunk(rw, (uint8*)entries); + bool result = object_load_entries(entries); + free(entries); + return result; +} +bool object_load_entries(rct_object_entry* entries) +{ log_verbose("loading required objects"); - // Read all the object entries - entries = malloc(OBJECT_ENTRY_COUNT * sizeof(rct_object_entry)); - sawyercoding_read_chunk(rw, (uint8*)entries); - - uint8 load_fail = 0; + bool loadFailed = false; // Load each object - for (i = 0; i < OBJECT_ENTRY_COUNT; i++) { - if (!check_object_entry(&entries[i])) + for (int i = 0; i < OBJECT_ENTRY_COUNT; i++) { + if (!check_object_entry(&entries[i])) { continue; + } // Get entry group index int entryGroupIndex = i; - for (j = 0; j < countof(object_entry_group_counts); j++) { + for (int j = 0; j < countof(object_entry_group_counts); j++) { if (entryGroupIndex < object_entry_group_counts[j]) break; entryGroupIndex -= object_entry_group_counts[j]; @@ -561,18 +565,17 @@ int object_read_and_load_entries(SDL_RWops* rw) if (!object_load_chunk(entryGroupIndex, &entries[i], NULL)) { log_error("failed to load entry: %.8s", entries[i].name); memcpy((char*)RCT2_ADDRESS_COMMON_FORMAT_ARGS, &entries[i], sizeof(rct_object_entry)); - load_fail = 1; + loadFailed = true; } } - free(entries); - if (load_fail){ + if (loadFailed) { object_unload_all(); - return 0; + return false; } log_verbose("finished loading required objects"); - return 1; + return true; } diff --git a/src/rct2/S6Exporter.cpp b/src/rct2/S6Exporter.cpp new file mode 100644 index 0000000000..6356104e70 --- /dev/null +++ b/src/rct2/S6Exporter.cpp @@ -0,0 +1,580 @@ +#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() +{ + ExportObjects = false; + RemoveTracklessRides = false; + 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_SCENARIO : 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; + + _s6.game_version_number = 201028; + + 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.map_elements, gMapElements, sizeof(_s6.map_elements)); + + _s6.dword_010E63B8 = RCT2_GLOBAL(0x0010E63B8, uint32); + memcpy(_s6.sprites, g_sprite_list, sizeof(_s6.sprites)); + + _s6.sprites_next_index = RCT2_GLOBAL(RCT2_ADDRESS_SPRITES_NEXT_INDEX, uint16); + _s6.sprites_start_vehicle = RCT2_GLOBAL(RCT2_ADDRESS_SPRITES_START_VEHICLE, uint16); + _s6.sprites_start_peep = RCT2_GLOBAL(RCT2_ADDRESS_SPRITES_START_PEEP, uint16); + _s6.sprites_start_textfx = RCT2_GLOBAL(RCT2_ADDRESS_SPRITES_START_MISC, uint16); + _s6.sprites_start_litter = RCT2_GLOBAL(RCT2_ADDRESS_SPRITES_START_LITTER, uint16); + // pad_013573C6 + _s6.word_013573C8 = RCT2_GLOBAL(0x13573C8, uint16); + _s6.sprites_next_index = RCT2_GLOBAL(RCT2_ADDRESS_SPRITES_COUNT_VEHICLE, uint16); + _s6.sprites_count_peep = RCT2_GLOBAL(RCT2_ADDRESS_SPRITES_COUNT_PEEP, uint16); + _s6.sprites_count_misc = RCT2_GLOBAL(RCT2_ADDRESS_SPRITES_COUNT_MISC, uint16); + _s6.sprites_count_litter = RCT2_GLOBAL(RCT2_ADDRESS_SPRITES_COUNT_LITTER, uint16); + // pad_013573D2 + _s6.park_name = gParkName; + // pad_013573D6 + _s6.park_name_args = gParkNameArgs; + _s6.initial_cash = gInitialCash; + _s6.current_loan = gBankLoan; + _s6.park_flags = gParkFlags; + _s6.park_entrance_fee = gParkEntranceFee; + // rct1_park_entrance_x + // rct1_park_entrance_y + // pad_013573EE + // rct1_park_entrance_z + memcpy(_s6.peep_spawns, gPeepSpawns, sizeof(_s6.peep_spawns)); + _s6.guest_count_change_modifier = gGuestChangeModifier; + _s6.current_research_level = gResearchFundingLevel; + // pad_01357400 + memcpy(_s6.ride_types_researched, RCT2_ADDRESS(0x01357404, uint32), sizeof(_s6.ride_types_researched)); + memcpy(_s6.ride_entries_researched, RCT2_ADDRESS(0x01357424, uint32), sizeof(_s6.ride_entries_researched)); + memcpy(_s6.dword_01357444, RCT2_ADDRESS(0x01357444, uint32), sizeof(_s6.dword_01357444)); + memcpy(_s6.dword_01357644, RCT2_ADDRESS(0x01357644, uint32), sizeof(_s6.dword_01357644)); + + _s6.guests_in_park = gNumGuestsInPark; + _s6.guests_heading_for_park = gNumGuestsHeadingForPark; + + memcpy(_s6.expenditure_table, RCT2_ADDRESS(RCT2_ADDRESS_EXPENDITURE_TABLE, money32), sizeof(_s6.expenditure_table)); + memcpy(_s6.dword_01357880, RCT2_ADDRESS(0x01357880, uint32), sizeof(_s6.dword_01357880)); + _s6.monthly_ride_income = RCT2_GLOBAL(RCT2_ADDRESS_MONTHLY_RIDE_INCOME, money32); + _s6.dword_01357898 = RCT2_GLOBAL(0x01357898, money32); + _s6.dword_0135789C = RCT2_GLOBAL(0x0135789C, money32); + _s6.dword_013578A0 = RCT2_GLOBAL(0x013578A0, money32); + memcpy(_s6.dword_013578A4, RCT2_ADDRESS(0x013578A4, money32), sizeof(_s6.dword_013578A4)); + + _s6.last_guests_in_park = RCT2_GLOBAL(RCT2_ADDRESS_LAST_GUESTS_IN_PARK, uint16); + // pad_01357BCA + _s6.handyman_colour = gStaffHandymanColour; + _s6.mechanic_colour = gStaffMechanicColour; + _s6.security_colour = gStaffSecurityColour; + + memcpy(_s6.dword_01357BD0, RCT2_ADDRESS(0x01357BD0, uint32), sizeof(_s6.dword_01357BD0)); + + _s6.park_rating = gParkRating; + + memcpy(_s6.park_rating_history, gParkRatingHistory, sizeof(_s6.park_rating_history)); + memcpy(_s6.guests_in_park_history, gGuestsInParkHistory, sizeof(_s6.guests_in_park_history)); + + _s6.active_research_types = gResearchPriorities; + _s6.research_progress_stage = gResearchProgressStage; + _s6.last_researched_item_subject = RCT2_GLOBAL(RCT2_ADDRESS_LAST_RESEARCHED_ITEM_SUBJECT, uint32); + // pad_01357CF8 + _s6.next_research_item = gResearchNextItem; + _s6.research_progress = gResearchProgress; + _s6.next_research_category = gResearchNextCategory; + _s6.next_research_expected_day = gResearchExpectedDay; + _s6.next_research_expected_month = gResearchExpectedMonth; + _s6.guest_initial_happiness = gGuestInitialHappiness; + _s6.park_size = gParkSize; + _s6.guest_generation_probability = _guestGenerationProbability; + _s6.total_ride_value = gTotalRideValue; + _s6.maximum_loan = gMaxBankLoan; + _s6.guest_initial_cash = gGuestInitialCash; + _s6.guest_initial_hunger = gGuestInitialHunger; + _s6.guest_initial_thirst = gGuestInitialThirst; + _s6.objective_type = gScenarioObjectiveType; + _s6.objective_year = gScenarioObjectiveYear; + // pad_013580FA + _s6.objective_currency = gScenarioObjectiveCurrency; + _s6.objective_guests = gScenarioObjectiveNumGuests; + memcpy(_s6.campaign_weeks_left, gMarketingCampaignDaysLeft, sizeof(_s6.campaign_weeks_left)); + memcpy(_s6.campaign_ride_index, gMarketingCampaignRideIndex, sizeof(_s6.campaign_ride_index)); + + memcpy(_s6.balance_history, gCashHistory, sizeof(_s6.balance_history)); + + _s6.current_expenditure = RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_EXPENDITURE, money32); + _s6.current_profit = RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_PROFIT, money32); + _s6.dword_01358334 = RCT2_GLOBAL(0x01358334, uint32); + _s6.word_01358338 = RCT2_GLOBAL(0x01358338, uint16); + // pad_0135833A + + memcpy(_s6.weekly_profit_history, gWeeklyProfitHistory, sizeof(_s6.weekly_profit_history)); + + _s6.park_value = gParkValue; + + memcpy(_s6.park_value_history, gParkValueHistory, sizeof(_s6.park_value_history)); + + _s6.completed_company_value = gScenarioCompletedCompanyValue; + _s6.total_admissions = RCT2_GLOBAL(RCT2_ADDRESS_TOTAL_ADMISSIONS, money32); + _s6.income_from_admissions = RCT2_GLOBAL(RCT2_ADDRESS_INCOME_FROM_ADMISSIONS, money32); + _s6.company_value = gCompanyValue; + memcpy(_s6.byte_01358750, RCT2_ADDRESS(0x01358750, uint8), sizeof(_s6.byte_01358750)); + memcpy(_s6.awards, gCurrentAwards, sizeof(_s6.awards)); + _s6.land_price = gLandPrice; + _s6.construction_rights_price = gConstructionRightsPrice; + _s6.word_01358774 = RCT2_GLOBAL(0x01358774, uint16); + // pad_01358776 + memcpy(_s6.dword_01358778, RCT2_ADDRESS(0x01358778, uint32), sizeof(_s6.dword_01358778)); + // _s6.game_version_number + _s6.dword_013587C0 = RCT2_GLOBAL(0x013587C0, uint32); + _s6.loan_hash = RCT2_GLOBAL(RCT2_ADDRESS_LOAN_HASH, uint32); + _s6.ride_count = RCT2_GLOBAL(RCT2_ADDRESS_RIDE_COUNT, uint16); + // pad_013587CA + _s6.dword_013587D0 = RCT2_GLOBAL(0x013587D0, uint32); + // pad_013587D4 + memcpy(_s6.scenario_completed_name, gScenarioCompletedBy, sizeof(_s6.scenario_completed_name)); + _s6.cash = gCashEncrypted; + // pad_013587FC + _s6.word_0135882E = RCT2_GLOBAL(0x0135882E, uint16); + _s6.map_size_units = gMapSizeUnits; + _s6.map_size_minus_2 = gMapSizeMinus2; + _s6.map_size = gMapSize; + _s6.map_max_xy = gMapSizeMaxXY; + _s6.same_price_throughout = RCT2_GLOBAL(RCT2_ADDRESS_SAME_PRICE_THROUGHOUT, uint32); + _s6.suggested_max_guests = _suggestedGuestMaximum; + _s6.park_rating_warning_days = gScenarioParkRatingWarningDays; + _s6.last_entrance_style = RCT2_GLOBAL(RCT2_ADDRESS_LAST_ENTRANCE_STYLE, uint8); + // rct1_water_colour + // pad_01358842 + memcpy(_s6.research_items, gResearchItems, sizeof(_s6.research_items)); + _s6.word_01359208 = RCT2_GLOBAL(0x01359208, uint16); + memcpy(_s6.scenario_name, gScenarioName, sizeof(_s6.scenario_name)); + memcpy(_s6.scenario_description, gScenarioDetails, sizeof(_s6.scenario_description)); + _s6.current_interest_rate = gBankLoanInterestRate; + // pad_0135934B + _s6.same_price_throughout_extended = RCT2_GLOBAL(RCT2_ADDRESS_SAME_PRICE_THROUGHOUT_EXTENDED, uint32); + memcpy(_s6.park_entrance_x, gParkEntranceX, sizeof(_s6.park_entrance_x)); + memcpy(_s6.park_entrance_y, gParkEntranceY, sizeof(_s6.park_entrance_y)); + memcpy(_s6.park_entrance_z, gParkEntranceZ, sizeof(_s6.park_entrance_z)); + memcpy(_s6.park_entrance_direction, gParkEntranceDirection, sizeof(_s6.park_entrance_direction)); + memcpy(_s6.scenario_filename, RCT2_ADDRESS(0x0135936C, char), sizeof(_s6.scenario_filename)); + memcpy(_s6.saved_expansion_pack_names, RCT2_ADDRESS(0x0135946C, uint8), sizeof(_s6.saved_expansion_pack_names)); + memcpy(_s6.banners, gBanners, sizeof(_s6.banners)); + memcpy(_s6.custom_strings, gUserStrings, sizeof(_s6.custom_strings)); + _s6.game_ticks_1 = RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_TICKS, uint32); + memcpy(_s6.rides, RCT2_ADDRESS(RCT2_ADDRESS_RIDE_LIST, rct_ride*), sizeof(_s6.rides)); + _s6.saved_age = RCT2_GLOBAL(RCT2_ADDRESS_SAVED_AGE, uint16); + _s6.saved_view_x = RCT2_GLOBAL(RCT2_ADDRESS_SAVED_VIEW_X, uint16); + _s6.saved_view_y = RCT2_GLOBAL(RCT2_ADDRESS_SAVED_VIEW_Y, uint16); + _s6.saved_view_zoom_and_rotation = RCT2_GLOBAL(RCT2_ADDRESS_SAVED_VIEW_ZOOM_AND_ROTATION, uint16); + memcpy(_s6.map_animations, RCT2_ADDRESS(gAnimatedObjects, rct_map_animation), sizeof(_s6.map_animations)); + // rct1_map_animations + _s6.num_map_animations = RCT2_GLOBAL(0x0138B580, uint16); + // pad_0138B582 + + _s6.ride_ratings_proximity_x = _rideRatingsProximityX; + _s6.ride_ratings_proximity_y = _rideRatingsProximityY; + _s6.ride_ratings_proximity_z = _rideRatingsProximityZ; + _s6.ride_ratings_proximity_start_x = _rideRatingsProximityStartX; + _s6.ride_ratings_proximity_start_y = _rideRatingsProximityStartY; + _s6.ride_ratings_proximity_start_z = _rideRatingsProximityStartZ; + _s6.ride_ratings_current_ride = _rideRatingsCurrentRide; + _s6.ride_ratings_state = _rideRatingsState; + _s6.ride_ratings_proximity_track_type = _rideRatingsProximityTrackType; + _s6.ride_ratings_proximity_base_height = _rideRatingsProximityBaseHeight; + _s6.ride_ratings_proximity_total = _rideRatingsProximityTotal; + memcpy(_s6.ride_ratings_proximity_scores, _proximityScores, sizeof(_s6.ride_ratings_proximity_scores)); + _s6.ride_ratings_num_brakes = _rideRatingsNumBrakes; + _s6.ride_ratings_num_reversers = _rideRatingsNumReversers; + _s6.word_0138B5CE = RCT2_GLOBAL(0x00138B5CE, uint16); + memcpy(_s6.ride_measurements, RCT2_ADDRESS(RCT2_ADDRESS_RIDE_MEASUREMENTS, rct_ride_measurement), sizeof(_s6.ride_measurements)); + _s6.next_guest_index = RCT2_GLOBAL(0x013B0E6C, uint32); + _s6.grass_and_scenery_tilepos = gGrassSceneryTileLoopPosition; + memcpy(_s6.patrol_areas, gStaffPatrolAreas, sizeof(_s6.patrol_areas)); + memcpy(_s6.staff_modes, gStaffModes, sizeof(_s6.staff_modes)); + // unk_13CA73E + // pad_13CA73F + _s6.byte_13CA740 = RCT2_GLOBAL(0x013CA740, uint8); + _s6.climate = gClimate; + // pad_13CA741; + memcpy(_s6.byte_13CA742, RCT2_ADDRESS(0x013CA742, uint8), sizeof(_s6.byte_13CA742)); + // pad_013CA747 + _s6.climate_update_timer = gClimateUpdateTimer; + _s6.current_weather = gClimateCurrentWeather; + _s6.next_weather = gClimateNextWeather; + _s6.temperature = gClimateNextTemperature; + _s6.current_weather_effect = gClimateCurrentWeatherEffect; + _s6.next_weather_effect = gClimateNextWeatherEffect; + _s6.current_weather_gloom = gClimateCurrentWeatherGloom; + _s6.next_weather_gloom = gClimateNextWeatherGloom; + _s6.current_rain_level = gClimateCurrentRainLevel; + _s6.next_rain_level = gClimateNextRainLevel; + memcpy(_s6.news_items, gNewsItems, sizeof(_s6.news_items)); + // pad_13CE730 + // rct1_scenario_flags + _s6.wide_path_tile_loop_x = gWidePathTileLoopX; + _s6.wide_path_tile_loop_y = gWidePathTileLoopY; + // pad_13CE778 + + String::Set(_s6.scenario_filename, sizeof(_s6.scenario_filename), _scenarioFileName); + + if (RemoveTracklessRides) + { + scenario_remove_trackless_rides(&_s6); + } + + scenario_fix_ghosts(&_s6); + game_convert_strings_to_rct2(&_s6); +} + +extern "C" +{ + enum { + S6_SAVE_FLAG_EXPORT = 1 << 0, + S6_SAVE_FLAG_SCENARIO = 1 << 1, + S6_SAVE_FLAG_AUTOMATIC = 1 << 31, + }; + + /** + * + * rct2: 0x006754F5 + * @param flags bit 0: pack objects, 1: save as scenario + */ + int scenario_save(SDL_RWops* rw, int 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(); + reset_0x69EBE4(); + sprite_clear_all_unused(); + + viewport_set_saved_view(); + + bool result = false; + auto s6exporter = new S6Exporter(); + try + { + s6exporter->ExportObjects = (flags & S6_SAVE_FLAG_EXPORT); + s6exporter->RemoveTracklessRides = true; + s6exporter->Export(); + if (flags & S6_SAVE_FLAG_SCENARIO) + { + s6exporter->SaveScenario(rw); + } + else + { + s6exporter->SaveGame(rw); + } + result = true; + } + catch (Exception) + { + } + delete s6exporter; + + reset_loaded_objects(); + gfx_invalidate_screen(); + + if (result && !(flags & S6_SAVE_FLAG_AUTOMATIC)) + { + gScreenAge = 0; + } + return result; + } + + // Save game state without modifying any of the state for multiplayer + int scenario_save_network(SDL_RWops * rw) + { + viewport_set_saved_view(); + + 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..b715c71b22 --- /dev/null +++ b/src/rct2/S6Exporter.h @@ -0,0 +1,47 @@ +#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; + bool RemoveTracklessRides; + + 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 new file mode 100644 index 0000000000..5e223284dc --- /dev/null +++ b/src/rct2/S6Importer.cpp @@ -0,0 +1,534 @@ +#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 "S6Importer.h" + +extern "C" +{ + #include "../config.h" + #include "../game.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 "../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" +} + +class ObjectLoadException : public Exception +{ +public: + ObjectLoadException() : Exception("Unable to load objects.") { } + ObjectLoadException(const char * message) : Exception(message) { } +}; + +S6Importer::S6Importer() +{ + FixIssues = false; + memset(&_s6, 0, sizeof(_s6)); +} + +void S6Importer::LoadSavedGame(const utf8 * path) +{ + SDL_RWops * rw = SDL_RWFromFile(path, "rb"); + if (rw == nullptr) + { + throw IOException("Unable to open SV6."); + } + + if (!sawyercoding_validate_checksum(rw)) + { + gErrorType = ERROR_TYPE_FILE_LOAD; + gGameCommandErrorTitle = STR_FILE_CONTAINS_INVALID_DATA; + + log_error("failed to load saved game, invalid checksum"); + throw IOException("Invalid SV6 checksum."); + } + + LoadSavedGame(rw); + + SDL_RWclose(rw); + + _s6Path = path; +} + +void S6Importer::LoadScenario(const utf8 * path) +{ + SDL_RWops * rw = SDL_RWFromFile(path, "rb"); + if (rw == nullptr) + { + throw IOException("Unable to open SV6."); + } + + if (!gConfigGeneral.allow_loading_with_incorrect_checksum && !sawyercoding_validate_checksum(rw)) + { + SDL_RWclose(rw); + + gErrorType = ERROR_TYPE_FILE_LOAD; + gErrorStringId = STR_FILE_CONTAINS_INVALID_DATA; + + log_error("failed to load scenario, invalid checksum"); + throw IOException("Invalid SC6 checksum."); + } + + LoadScenario(rw); + + SDL_RWclose(rw); + + _s6Path = path; +} + +void S6Importer::LoadSavedGame(SDL_RWops *rw) +{ + auto meh = SDL_RWtell(rw); + + sawyercoding_read_chunk(rw, (uint8*)&_s6.header); + if (_s6.header.type != S6_TYPE_SAVEDGAME) + { + throw Exception("Data is not a saved game."); + } + + // Read packed objects + // TODO try to contain this more and not store objects until later + if (_s6.header.num_packed_objects > 0) { + int j = 0; + for (uint16 i = 0; i < _s6.header.num_packed_objects; i++) + { + j += object_load_packed(rw); + } + if (j > 0) + { + object_list_load(); + } + } + + sawyercoding_read_chunk(rw, (uint8*)&_s6.objects); + sawyercoding_read_chunk(rw, (uint8*)&_s6.elapsed_months); + sawyercoding_read_chunk(rw, (uint8*)&_s6.map_elements); + sawyercoding_read_chunk(rw, (uint8*)&_s6.dword_010E63B8); +} + +void S6Importer::LoadScenario(SDL_RWops *rw) +{ + sawyercoding_read_chunk(rw, (uint8*)&_s6.header); + if (_s6.header.type != S6_TYPE_SCENARIO) + { + throw Exception("Data is not a scenario."); + } + + sawyercoding_read_chunk(rw, (uint8*)&_s6.info); + + // Read packed objects + // TODO try to contain this more and not store objects until later + if (_s6.header.num_packed_objects > 0) { + int j = 0; + for (uint16 i = 0; i < _s6.header.num_packed_objects; i++) + { + j += object_load_packed(rw); + } + if (j > 0) + { + object_list_load(); + } + } + + sawyercoding_read_chunk(rw, (uint8*)&_s6.objects); + sawyercoding_read_chunk(rw, (uint8*)&_s6.elapsed_months); + sawyercoding_read_chunk(rw, (uint8*)&_s6.map_elements); + sawyercoding_read_chunk(rw, (uint8*)&_s6.dword_010E63B8); + sawyercoding_read_chunk(rw, (uint8*)&_s6.guests_in_park); + sawyercoding_read_chunk(rw, (uint8*)&_s6.last_guests_in_park); + sawyercoding_read_chunk(rw, (uint8*)&_s6.park_rating); + sawyercoding_read_chunk(rw, (uint8*)&_s6.active_research_types); + sawyercoding_read_chunk(rw, (uint8*)&_s6.current_expenditure); + sawyercoding_read_chunk(rw, (uint8*)&_s6.park_value); + sawyercoding_read_chunk(rw, (uint8*)&_s6.completed_company_value); +} + +void S6Importer::Import() +{ + RCT2_GLOBAL(0x009E34E4, rct_s6_header) = _s6.header; + + gDateMonthsElapsed = _s6.elapsed_months; + gDateMonthTicks = _s6.current_day; + RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_TICKS, uint32) = _s6.scenario_ticks; + gScenarioSrand0 = _s6.scenario_srand_0; + gScenarioSrand1 = _s6.scenario_srand_1; + + memcpy(gMapElements, _s6.map_elements, sizeof(_s6.map_elements)); + + RCT2_GLOBAL(0x0010E63B8, uint32) = _s6.dword_010E63B8; + memcpy(g_sprite_list, _s6.sprites, sizeof(_s6.sprites)); + + RCT2_GLOBAL(RCT2_ADDRESS_SPRITES_NEXT_INDEX, uint16) = _s6.sprites_next_index; + RCT2_GLOBAL(RCT2_ADDRESS_SPRITES_START_VEHICLE, uint16) = _s6.sprites_start_vehicle; + RCT2_GLOBAL(RCT2_ADDRESS_SPRITES_START_PEEP, uint16) = _s6.sprites_start_peep; + RCT2_GLOBAL(RCT2_ADDRESS_SPRITES_START_MISC, uint16) = _s6.sprites_start_textfx; + RCT2_GLOBAL(RCT2_ADDRESS_SPRITES_START_LITTER, uint16) = _s6.sprites_start_litter; + // pad_013573C6 + RCT2_GLOBAL(0x13573C8, uint16) = _s6.word_013573C8; + RCT2_GLOBAL(RCT2_ADDRESS_SPRITES_COUNT_VEHICLE, uint16) = _s6.sprites_next_index; + RCT2_GLOBAL(RCT2_ADDRESS_SPRITES_COUNT_PEEP, uint16) = _s6.sprites_count_peep; + RCT2_GLOBAL(RCT2_ADDRESS_SPRITES_COUNT_MISC, uint16) = _s6.sprites_count_misc; + RCT2_GLOBAL(RCT2_ADDRESS_SPRITES_COUNT_LITTER, uint16) = _s6.sprites_count_litter; + // pad_013573D2 + gParkName = _s6.park_name; + // pad_013573D6 + gParkNameArgs = _s6.park_name_args; + gInitialCash = _s6.initial_cash; + gBankLoan = _s6.current_loan; + gParkFlags = _s6.park_flags; + gParkEntranceFee = _s6.park_entrance_fee; + // rct1_park_entrance_x + // rct1_park_entrance_y + // pad_013573EE + // rct1_park_entrance_z + memcpy(gPeepSpawns, _s6.peep_spawns, sizeof(_s6.peep_spawns)); + gGuestChangeModifier = _s6.guest_count_change_modifier; + gResearchFundingLevel = _s6.current_research_level; + // pad_01357400 + memcpy(RCT2_ADDRESS(0x01357404, uint32), _s6.ride_types_researched, sizeof(_s6.ride_types_researched)); + memcpy(RCT2_ADDRESS(0x01357424, uint32), _s6.ride_entries_researched, sizeof(_s6.ride_entries_researched)); + memcpy(RCT2_ADDRESS(0x01357444, uint32), _s6.dword_01357444, sizeof(_s6.dword_01357444)); + memcpy(RCT2_ADDRESS(0x01357644, uint32), _s6.dword_01357644, sizeof(_s6.dword_01357644)); + + gNumGuestsInPark = _s6.guests_in_park; + gNumGuestsHeadingForPark = _s6.guests_heading_for_park; + + memcpy(RCT2_ADDRESS(RCT2_ADDRESS_EXPENDITURE_TABLE, money32), _s6.expenditure_table, sizeof(_s6.expenditure_table)); + memcpy(RCT2_ADDRESS(0x01357880, uint32), _s6.dword_01357880, sizeof(_s6.dword_01357880)); + RCT2_GLOBAL(RCT2_ADDRESS_MONTHLY_RIDE_INCOME, money32) = _s6.monthly_ride_income; + RCT2_GLOBAL(0x01357898, money32) = _s6.dword_01357898; + RCT2_GLOBAL(0x0135789C, money32) = _s6.dword_0135789C; + RCT2_GLOBAL(0x013578A0, money32) = _s6.dword_013578A0; + memcpy(RCT2_ADDRESS(0x013578A4, money32), _s6.dword_013578A4, sizeof(_s6.dword_013578A4)); + + RCT2_GLOBAL(RCT2_ADDRESS_LAST_GUESTS_IN_PARK, uint16) = _s6.last_guests_in_park; + // pad_01357BCA + gStaffHandymanColour = _s6.handyman_colour; + gStaffMechanicColour = _s6.mechanic_colour; + gStaffSecurityColour = _s6.security_colour; + + memcpy(RCT2_ADDRESS(0x01357BD0, uint32), _s6.dword_01357BD0, sizeof(_s6.dword_01357BD0)); + + gParkRating = _s6.park_rating; + + memcpy(gParkRatingHistory, _s6.park_rating_history, sizeof(_s6.park_rating_history)); + memcpy(gGuestsInParkHistory, _s6.guests_in_park_history, sizeof(_s6.guests_in_park_history)); + + gResearchPriorities = _s6.active_research_types; + gResearchProgressStage = _s6.research_progress_stage; + RCT2_GLOBAL(RCT2_ADDRESS_LAST_RESEARCHED_ITEM_SUBJECT, uint32) = _s6.last_researched_item_subject; + // pad_01357CF8 + gResearchNextItem = _s6.next_research_item; + gResearchProgress = _s6.research_progress; + gResearchNextCategory = _s6.next_research_category; + gResearchExpectedDay = _s6.next_research_expected_day; + gResearchExpectedMonth = _s6.next_research_expected_month; + gGuestInitialHappiness = _s6.guest_initial_happiness; + gParkSize = _s6.park_size; + _guestGenerationProbability = _s6.guest_generation_probability; + gTotalRideValue = _s6.total_ride_value; + gMaxBankLoan = _s6.maximum_loan; + gGuestInitialCash = _s6.guest_initial_cash; + gGuestInitialHunger = _s6.guest_initial_hunger; + gGuestInitialThirst = _s6.guest_initial_thirst; + gScenarioObjectiveType = _s6.objective_type; + gScenarioObjectiveYear = _s6.objective_year; + // pad_013580FA + gScenarioObjectiveCurrency = _s6.objective_currency; + gScenarioObjectiveNumGuests = _s6.objective_guests; + memcpy(gMarketingCampaignDaysLeft, _s6.campaign_weeks_left, sizeof(_s6.campaign_weeks_left)); + memcpy(gMarketingCampaignRideIndex, _s6.campaign_ride_index, sizeof(_s6.campaign_ride_index)); + + memcpy(gCashHistory, _s6.balance_history, sizeof(_s6.balance_history)); + + RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_EXPENDITURE, money32) = _s6.current_expenditure; + RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_PROFIT, money32) = _s6.current_profit; + RCT2_GLOBAL(0x01358334, uint32) = _s6.dword_01358334; + RCT2_GLOBAL(0x01358338, uint16) = _s6.word_01358338; + // pad_0135833A + + memcpy(gWeeklyProfitHistory, _s6.weekly_profit_history, sizeof(_s6.weekly_profit_history)); + + gParkValue = _s6.park_value; + + memcpy(gParkValueHistory, _s6.park_value_history, sizeof(_s6.park_value_history)); + + gScenarioCompletedCompanyValue = _s6.completed_company_value; + RCT2_GLOBAL(RCT2_ADDRESS_TOTAL_ADMISSIONS, money32) = _s6.total_admissions; + RCT2_GLOBAL(RCT2_ADDRESS_INCOME_FROM_ADMISSIONS, money32) = _s6.income_from_admissions; + gCompanyValue = _s6.company_value; + memcpy(RCT2_ADDRESS(0x01358750, uint8), _s6.byte_01358750, sizeof(_s6.byte_01358750)); + memcpy(gCurrentAwards, _s6.awards, sizeof(_s6.awards)); + gLandPrice = _s6.land_price; + gConstructionRightsPrice = _s6.construction_rights_price; + RCT2_GLOBAL(0x01358774, uint16) = _s6.word_01358774; + // pad_01358776 + memcpy(RCT2_ADDRESS(0x01358778, uint32), _s6.dword_01358778, sizeof(_s6.dword_01358778)); + _gameVersion = _s6.game_version_number; + RCT2_GLOBAL(0x013587C0, uint32) = _s6.dword_013587C0; + RCT2_GLOBAL(RCT2_ADDRESS_LOAN_HASH, uint32) = _s6.loan_hash; + RCT2_GLOBAL(RCT2_ADDRESS_RIDE_COUNT, uint16) = _s6.ride_count; + // pad_013587CA + RCT2_GLOBAL(0x013587D0, uint32) = _s6.dword_013587D0; + // pad_013587D4 + memcpy(gScenarioCompletedBy, _s6.scenario_completed_name, sizeof(_s6.scenario_completed_name)); + gCashEncrypted = _s6.cash; + // pad_013587FC + RCT2_GLOBAL(0x0135882E, uint16) = _s6.word_0135882E; + gMapSizeUnits = _s6.map_size_units; + gMapSizeMinus2 = _s6.map_size_minus_2; + gMapSize = _s6.map_size; + gMapSizeMaxXY = _s6.map_max_xy; + RCT2_GLOBAL(RCT2_ADDRESS_SAME_PRICE_THROUGHOUT, uint32) = _s6.same_price_throughout; + _suggestedGuestMaximum = _s6.suggested_max_guests; + gScenarioParkRatingWarningDays = _s6.park_rating_warning_days; + RCT2_GLOBAL(RCT2_ADDRESS_LAST_ENTRANCE_STYLE, uint8) = _s6.last_entrance_style; + // rct1_water_colour + // pad_01358842 + memcpy(gResearchItems, _s6.research_items, sizeof(_s6.research_items)); + RCT2_GLOBAL(0x01359208, uint16) = _s6.word_01359208; + memcpy(gScenarioName, _s6.scenario_name, sizeof(_s6.scenario_name)); + memcpy(gScenarioDetails, _s6.scenario_description, sizeof(_s6.scenario_description)); + gBankLoanInterestRate = _s6.current_interest_rate; + // pad_0135934B + RCT2_GLOBAL(RCT2_ADDRESS_SAME_PRICE_THROUGHOUT_EXTENDED, uint32) = _s6.same_price_throughout_extended; + memcpy(gParkEntranceX, _s6.park_entrance_x, sizeof(_s6.park_entrance_x)); + memcpy(gParkEntranceY, _s6.park_entrance_y, sizeof(_s6.park_entrance_y)); + memcpy(gParkEntranceZ, _s6.park_entrance_z, sizeof(_s6.park_entrance_z)); + memcpy(gParkEntranceDirection, _s6.park_entrance_direction, sizeof(_s6.park_entrance_direction)); + memcpy(RCT2_ADDRESS(0x0135936C, char), _s6.scenario_filename, sizeof(_s6.scenario_filename)); + memcpy(RCT2_ADDRESS(0x0135946C, uint8), _s6.saved_expansion_pack_names, sizeof(_s6.saved_expansion_pack_names)); + memcpy(gBanners, _s6.banners, sizeof(_s6.banners)); + memcpy(gUserStrings, _s6.custom_strings, sizeof(_s6.custom_strings)); + RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_TICKS, uint32) = _s6.game_ticks_1; + memcpy(RCT2_ADDRESS(RCT2_ADDRESS_RIDE_LIST, rct_ride*), _s6.rides, sizeof(_s6.rides)); + RCT2_GLOBAL(RCT2_ADDRESS_SAVED_AGE, uint16) = _s6.saved_age; + RCT2_GLOBAL(RCT2_ADDRESS_SAVED_VIEW_X, uint16) = _s6.saved_view_x; + RCT2_GLOBAL(RCT2_ADDRESS_SAVED_VIEW_Y, uint16) = _s6.saved_view_y; + RCT2_GLOBAL(RCT2_ADDRESS_SAVED_VIEW_ZOOM_AND_ROTATION, uint16) = _s6.saved_view_zoom_and_rotation; + memcpy(RCT2_ADDRESS(gAnimatedObjects, rct_map_animation), _s6.map_animations, sizeof(_s6.map_animations)); + // rct1_map_animations + RCT2_GLOBAL(0x0138B580, uint16) = _s6.num_map_animations; + // pad_0138B582 + + _rideRatingsProximityX = _s6.ride_ratings_proximity_x; + _rideRatingsProximityY = _s6.ride_ratings_proximity_y; + _rideRatingsProximityZ = _s6.ride_ratings_proximity_z; + _rideRatingsProximityStartX = _s6.ride_ratings_proximity_start_x; + _rideRatingsProximityStartY = _s6.ride_ratings_proximity_start_y; + _rideRatingsProximityStartZ = _s6.ride_ratings_proximity_start_z; + _rideRatingsCurrentRide = _s6.ride_ratings_current_ride; + _rideRatingsState = _s6.ride_ratings_state; + _rideRatingsProximityTrackType = _s6.ride_ratings_proximity_track_type; + _rideRatingsProximityBaseHeight = _s6.ride_ratings_proximity_base_height; + _rideRatingsProximityTotal = _s6.ride_ratings_proximity_total; + memcpy(_proximityScores, _s6.ride_ratings_proximity_scores, sizeof(_s6.ride_ratings_proximity_scores)); + _rideRatingsNumBrakes = _s6.ride_ratings_num_brakes; + _rideRatingsNumReversers = _s6.ride_ratings_num_reversers; + RCT2_GLOBAL(0x00138B5CE, uint16) = _s6.word_0138B5CE; + memcpy(RCT2_ADDRESS(RCT2_ADDRESS_RIDE_MEASUREMENTS, rct_ride_measurement), _s6.ride_measurements, sizeof(_s6.ride_measurements)); + RCT2_GLOBAL(0x013B0E6C, uint32) = _s6.next_guest_index; + gGrassSceneryTileLoopPosition = _s6.grass_and_scenery_tilepos; + memcpy(gStaffPatrolAreas, _s6.patrol_areas, sizeof(_s6.patrol_areas)); + memcpy(gStaffModes, _s6.staff_modes, sizeof(_s6.staff_modes)); + // unk_13CA73E + // pad_13CA73F + RCT2_GLOBAL(0x013CA740, uint8) = _s6.byte_13CA740; + gClimate = _s6.climate; + // pad_13CA741; + memcpy(RCT2_ADDRESS(0x013CA742, uint8), _s6.byte_13CA742, sizeof(_s6.byte_13CA742)); + // pad_013CA747 + gClimateUpdateTimer = _s6.climate_update_timer; + gClimateCurrentWeather = _s6.current_weather; + gClimateNextWeather = _s6.next_weather; + gClimateNextTemperature = _s6.temperature; + gClimateCurrentWeatherEffect = _s6.current_weather_effect; + gClimateNextWeatherEffect = _s6.next_weather_effect; + gClimateCurrentWeatherGloom = _s6.current_weather_gloom; + gClimateNextWeatherGloom = _s6.next_weather_gloom; + gClimateCurrentRainLevel = _s6.current_rain_level; + gClimateNextRainLevel = _s6.next_rain_level; + memcpy(gNewsItems, _s6.news_items, sizeof(_s6.news_items)); + // pad_13CE730 + // rct1_scenario_flags + gWidePathTileLoopX = _s6.wide_path_tile_loop_x; + gWidePathTileLoopY = _s6.wide_path_tile_loop_y; + // pad_13CE778 + + // Fix and set dynamic variables + if (!object_load_entries(_s6.objects)) { + throw ObjectLoadException(); + } + reset_loaded_objects(); + map_update_tile_pointers(); + reset_0x69EBE4(); + game_convert_strings_to_utf8(); + if (FixIssues) + { + game_fix_save_vars(); + } +} + +extern "C" +{ + /** + * + * rct2: 0x00675E1B + */ + int game_load_sv6(SDL_RWops * rw) + { + if (!sawyercoding_validate_checksum(rw)) + { + log_error("invalid checksum"); + + gErrorType = ERROR_TYPE_FILE_LOAD; + gGameCommandErrorTitle = STR_FILE_CONTAINS_INVALID_DATA; + return 0; + } + + bool result = false; + auto s6Importer = new S6Importer(); + try + { + s6Importer->FixIssues = true; + s6Importer->LoadSavedGame(rw); + s6Importer->Import(); + + openrct2_reset_object_tween_locations(); + result = true; + } + catch (ObjectLoadException) + { + set_load_objects_fail_reason(); + } + catch (Exception) + { + } + delete s6Importer; + + // #2407: Resetting screen time to not open a save prompt shortly after loading a park. + gScreenAge = 0; + gLastAutoSaveTick = SDL_GetTicks(); + return result; + } + + /** + * + * rct2: 0x00676053 + * scenario (ebx) + */ + int scenario_load(const char * path) + { + bool result = false; + auto s6Importer = new S6Importer(); + try + { + s6Importer->FixIssues = true; + s6Importer->LoadScenario(path); + s6Importer->Import(); + + openrct2_reset_object_tween_locations(); + result = true; + } + catch (ObjectLoadException) + { + set_load_objects_fail_reason(); + } + catch (IOException) + { + gErrorType = ERROR_TYPE_FILE_LOAD; + gErrorStringId = STR_GAME_SAVE_FAILED; + } + catch (Exception) + { + gErrorType = ERROR_TYPE_FILE_LOAD; + gErrorStringId = STR_FILE_CONTAINS_INVALID_DATA; + } + delete s6Importer; + + gScreenAge = 0; + gLastAutoSaveTick = SDL_GetTicks(); + return result; + } + + int game_load_network(SDL_RWops* rw) + { + bool result = false; + auto s6Importer = new S6Importer(); + try + { + s6Importer->LoadSavedGame(rw); + s6Importer->Import(); + + openrct2_reset_object_tween_locations(); + result = true; + } + catch (ObjectLoadException) + { + set_load_objects_fail_reason(); + } + catch (Exception) + { + } + delete s6Importer; + + if (!result) + { + return 0; + } + + // Read checksum + uint32 checksum; + SDL_RWread(rw, &checksum, sizeof(uint32), 1); + + // Read other data not in normal save files + gGamePaused = SDL_ReadLE32(rw); + _guestGenerationProbability = SDL_ReadLE32(rw); + _suggestedGuestMaximum = SDL_ReadLE32(rw); + gCheatsSandboxMode = SDL_ReadU8(rw) != 0; + gCheatsDisableClearanceChecks = SDL_ReadU8(rw) != 0; + gCheatsDisableSupportLimits = SDL_ReadU8(rw) != 0; + gCheatsDisableTrainLengthLimit = SDL_ReadU8(rw) != 0; + gCheatsShowAllOperatingModes = SDL_ReadU8(rw) != 0; + gCheatsShowVehiclesFromOtherTrackTypes = SDL_ReadU8(rw) != 0; + gCheatsFastLiftHill = SDL_ReadU8(rw) != 0; + gCheatsDisableBrakesFailure = SDL_ReadU8(rw) != 0; + gCheatsDisableAllBreakdowns = SDL_ReadU8(rw) != 0; + gCheatsUnlockAllPrices = SDL_ReadU8(rw) != 0; + gCheatsBuildInPauseMode = SDL_ReadU8(rw) != 0; + gCheatsIgnoreRideIntensity = SDL_ReadU8(rw) != 0; + gCheatsDisableVandalism = SDL_ReadU8(rw) != 0; + gCheatsDisableLittering = SDL_ReadU8(rw) != 0; + gCheatsNeverendingMarketing = SDL_ReadU8(rw) != 0; + gCheatsFreezeClimate = SDL_ReadU8(rw) != 0; + + gLastAutoSaveTick = SDL_GetTicks(); + return 1; + } +} diff --git a/src/rct2/S6Importer.h b/src/rct2/S6Importer.h new file mode 100644 index 0000000000..8070dabeae --- /dev/null +++ b/src/rct2/S6Importer.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 import RollerCoaster Tycoon 2 scenarios (*.SC6) and saved games (*.SV6). + */ +class S6Importer +{ +public: + bool FixIssues; + + S6Importer(); + + void LoadSavedGame(const utf8 * path); + void LoadSavedGame(SDL_RWops *rw); + void LoadScenario(const utf8 * path); + void LoadScenario(SDL_RWops *rw); + void Import(); + +private: + const utf8 * _s6Path; + rct_s6_data _s6; + uint8 _gameVersion; +}; diff --git a/src/ride/ride.c b/src/ride/ride.c index 9343184d61..9e79cb04d8 100644 --- a/src/ride/ride.c +++ b/src/ride/ride.c @@ -6494,7 +6494,7 @@ void game_command_set_ride_appearance(int *eax, int *ebx, int *ecx, int *edx, in case 6: if (apply) { ride->entrance_style = value; - RCT2_GLOBAL(0x01358840, uint8) = value; + RCT2_GLOBAL(RCT2_ADDRESS_LAST_ENTRANCE_STYLE, uint8) = value; gfx_invalidate_screen(); } break; diff --git a/src/ride/ride_ratings.c b/src/ride/ride_ratings.c index 79be12b056..e8261a268a 100644 --- a/src/ride/ride_ratings.c +++ b/src/ride/ride_ratings.c @@ -64,19 +64,7 @@ enum { typedef void (*ride_ratings_calculation)(rct_ride *ride); -#define _rideRatingsProximityX RCT2_GLOBAL(0x0138B584, uint16) -#define _rideRatingsProximityY RCT2_GLOBAL(0x0138B586, uint16) -#define _rideRatingsProximityZ RCT2_GLOBAL(0x0138B588, uint16) -#define _rideRatingsCurrentRide RCT2_GLOBAL(0x0138B590, uint8) -#define _rideRatingsState RCT2_GLOBAL(0x0138B591, uint8) -#define _rideRatingsProximityTrackType RCT2_GLOBAL(0x0138B592, uint8) -#define _rideRatingsProximityBaseHeight RCT2_GLOBAL(0x0138B593, uint8) -#define _rideRatingsProximityTotal RCT2_GLOBAL(0x0138B594, uint16) -#define _rideRatingsProximityStartX RCT2_GLOBAL(0x0138B58A, uint16) -#define _rideRatingsProximityStartY RCT2_GLOBAL(0x0138B58C, uint16) -#define _rideRatingsProximityStartZ RCT2_GLOBAL(0x0138B58E, uint16) - -static uint16 *_proximityScores = (uint16*)0x0138B596; +uint16 *_proximityScores = (uint16*)0x0138B596; static const ride_ratings_calculation ride_ratings_calculate_func_table[91]; @@ -605,8 +593,8 @@ static void ride_ratings_score_close_proximity(rct_map_element *inputMapElement) case TRACK_ELEM_BRAKES: RCT2_GLOBAL(0x0138B5CA, uint16)++; break; - case 211: - case 212: + case TRACK_ELEM_LEFT_REVERSER: + case TRACK_ELEM_RIGHT_REVERSER: RCT2_GLOBAL(0x0138B5CC, uint16)++; break; } diff --git a/src/ride/ride_ratings.h b/src/ride/ride_ratings.h index a589c7fcfe..7c8e3579ff 100644 --- a/src/ride/ride_ratings.h +++ b/src/ride/ride_ratings.h @@ -20,6 +20,23 @@ #include "../common.h" #include "ride.h" +#define _rideRatingsProximityX RCT2_GLOBAL(0x0138B584, uint16) +#define _rideRatingsProximityY RCT2_GLOBAL(0x0138B586, uint16) +#define _rideRatingsProximityZ RCT2_GLOBAL(0x0138B588, uint16) +#define _rideRatingsCurrentRide RCT2_GLOBAL(0x0138B590, uint8) +#define _rideRatingsState RCT2_GLOBAL(0x0138B591, uint8) +#define _rideRatingsProximityTrackType RCT2_GLOBAL(0x0138B592, uint8) +#define _rideRatingsProximityBaseHeight RCT2_GLOBAL(0x0138B593, uint8) +#define _rideRatingsProximityTotal RCT2_GLOBAL(0x0138B594, uint16) +#define _rideRatingsProximityStartX RCT2_GLOBAL(0x0138B58A, uint16) +#define _rideRatingsProximityStartY RCT2_GLOBAL(0x0138B58C, uint16) +#define _rideRatingsProximityStartZ RCT2_GLOBAL(0x0138B58E, uint16) + +#define _rideRatingsNumBrakes RCT2_GLOBAL(0x0138B5CA, uint16) +#define _rideRatingsNumReversers RCT2_GLOBAL(0x0138B5CC, uint16) + +extern uint16 *_proximityScores; + void ride_ratings_update_all(); #endif diff --git a/src/scenario.c b/src/scenario.c index 69c84f9721..e16144933c 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); @@ -95,109 +95,6 @@ bool scenario_load_basic(const char *path, rct_s6_header *header, rct_s6_info *i return false; } -/** - * - * rct2: 0x00676053 - * scenario (ebx) - */ -int scenario_load(const char *path) -{ - log_verbose("loading scenario, %s", path); - - SDL_RWops* rw; - int i, j; - rct_s6_header *s6Header = (rct_s6_header*)0x009E34E4; - rct_s6_info *s6Info = (rct_s6_info*)0x0141F570; - - rw = SDL_RWFromFile(path, "rb"); - if (rw != NULL) { - if (!sawyercoding_validate_checksum(rw) && !gConfigGeneral.allow_loading_with_incorrect_checksum) { - SDL_RWclose(rw); - gErrorType = ERROR_TYPE_FILE_LOAD; - gErrorStringId = STR_FILE_CONTAINS_INVALID_DATA; - - log_error("failed to load scenario, invalid checksum"); - return 0; - } - - // Read first chunk - sawyercoding_read_chunk(rw, (uint8*)s6Header); - if (s6Header->type == S6_TYPE_SCENARIO) { - // Read second chunk - sawyercoding_read_chunk(rw, (uint8*)s6Info); - - // Read packed objects - if (s6Header->num_packed_objects > 0) { - j = 0; - for (i = 0; i < s6Header->num_packed_objects; i++) - j += object_load_packed(rw); - if (j > 0) - object_list_load(); - } - - uint8 load_success = object_read_and_load_entries(rw); - - // Read flags (16 bytes). Loads: - // RCT2_ADDRESS_CURRENT_MONTH_YEAR - // RCT2_ADDRESS_CURRENT_MONTH_TICKS - // RCT2_ADDRESS_SCENARIO_TICKS - sawyercoding_read_chunk(rw, (uint8*)RCT2_ADDRESS_CURRENT_MONTH_YEAR); - - // Read map elements - memset((void*)RCT2_ADDRESS_MAP_ELEMENTS, 0, MAX_MAP_ELEMENTS * sizeof(rct_map_element)); - sawyercoding_read_chunk(rw, (uint8*)RCT2_ADDRESS_MAP_ELEMENTS); - - // Read game data, including sprites - sawyercoding_read_chunk(rw, (uint8*)0x010E63B8); - - // Read number of guests in park and something else - sawyercoding_read_chunk(rw, (uint8*)RCT2_ADDRESS_GUESTS_IN_PARK); - - // Read ? - sawyercoding_read_chunk(rw, (uint8*)RCT2_ADDRESS_LAST_GUESTS_IN_PARK); - - // Read park rating - sawyercoding_read_chunk(rw, (uint8*)RCT2_ADDRESS_CURRENT_PARK_RATING); - - // Read ? - sawyercoding_read_chunk(rw, (uint8*)RCT2_ADDRESS_ACTIVE_RESEARCH_TYPES); - - // Read ? - sawyercoding_read_chunk(rw, (uint8*)RCT2_ADDRESS_CURRENT_EXPENDITURE); - - // Read ? - sawyercoding_read_chunk(rw, (uint8*)RCT2_ADDRESS_CURRENT_PARK_VALUE); - - // Read more game data, including research items and rides - sawyercoding_read_chunk(rw, (uint8*)RCT2_ADDRESS_COMPLETED_COMPANY_VALUE); - - SDL_RWclose(rw); - if (!load_success){ - log_error("failed to load all entries."); - set_load_objects_fail_reason(); - return 0; - } - - reset_loaded_objects(); - map_update_tile_pointers(); - reset_0x69EBE4(); - openrct2_reset_object_tween_locations(); - game_convert_strings_to_utf8(); - game_fix_save_vars(); // OpenRCT2 fix broken save games - - gLastAutoSaveTick = SDL_GetTicks(); - return 1; - } - - SDL_RWclose(rw); - } - - log_error("failed to find scenario file."); - gErrorType = ERROR_TYPE_FILE_LOAD; - gErrorStringId = STR_FILE_CONTAINS_INVALID_DATA; - return 0; -} - int scenario_load_and_play_from_path(const char *path) { window_close_construction_windows(); @@ -353,7 +250,7 @@ void scenario_begin() duck_remove_all(); park_calculate_size(); staff_reset_stats(); - RCT2_GLOBAL(0x01358840, uint8) = 0; + RCT2_GLOBAL(RCT2_ADDRESS_LAST_ENTRANCE_STYLE, uint8) = 0; memset((void*)0x001358102, 0, 20); RCT2_GLOBAL(0x00135882E, uint16) = 0; @@ -870,31 +767,10 @@ int scenario_write_available_objects(FILE *file) return 1; } -static void sub_677552() -{ - RCT2_GLOBAL(RCT2_ADDRESS_GAME_VERSION_NUMBER, uint32) = 201028; - RCT2_GLOBAL(0x001358778, uint32) = RCT2_GLOBAL(0x009E2D28, uint32); -} - -static void sub_674BCF() -{ - char *savedExpansionPackNames = (char*)0x0135946C; - - for (int i = 0; i < 16; i++) { - char *dst = &savedExpansionPackNames[i * 128]; - if (RCT2_GLOBAL(RCT2_ADDRESS_EXPANSION_FLAGS, uint16) & (1 << i)) { - char *src = &(RCT2_ADDRESS(RCT2_ADDRESS_EXPANSION_NAMES, char)[i * 128]); - safe_strcpy(dst, src, 128); - } else { - *dst = 0; - } - } -} - /** - * 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); @@ -925,7 +801,7 @@ static void scenario_fix_ghosts(rct_s6_data *s6) } } -static void scenario_remove_trackless_rides(rct_s6_data *s6) +void scenario_remove_trackless_rides(rct_s6_data *s6) { bool rideHasTrack[MAX_RIDES]; ride_all_has_any_track_elements(rideHasTrack); @@ -943,314 +819,6 @@ static void scenario_remove_trackless_rides(rct_s6_data *s6) } } -/** - * - * rct2: 0x006754F5 - * @param flags bit 0: pack objects, 1: save as scenario - */ -int scenario_save(SDL_RWops* rw, int flags) -{ - rct_window *w; - rct_viewport *viewport; - int viewX, viewY, viewZoom, viewRotation; - - if (flags & 2) - log_verbose("saving scenario"); - else - log_verbose("saving game"); - - - if (!(flags & 0x80000000)) - window_close_construction_windows(); - - 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 = gSavedViewX; - viewY = gSavedViewY; - viewZoom = gSavedViewZoom; - viewRotation = gSavedViewRotation; - } - - gSavedViewX = viewX; - gSavedViewY = viewY; - gSavedViewZoom = viewZoom; - gSavedViewRotation = viewRotation; - - // Prepare S6 - rct_s6_data *s6 = malloc(sizeof(rct_s6_data)); - s6->header.type = flags & 2 ? S6_TYPE_SCENARIO : S6_TYPE_SAVEDGAME; - s6->header.num_packed_objects = flags & 1 ? scenario_get_num_packed_objects_to_write() : 0; - 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 < OBJECT_ENTRY_COUNT; 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); - scenario_remove_trackless_rides(s6); - game_convert_strings_to_rct2(s6); - scenario_save_s6(rw, s6); - - free(s6); - - if (flags & 1) - reset_loaded_objects(); - - gfx_invalidate_screen(); - if (!(flags & 0x80000000)) - gScreenAge = 0; - 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; - sawyercoding_chunk_header chunkHeader; - int encodedLength; - long fileSize; - uint32 checksum; - - buffer = malloc(0x600000); - if (buffer == NULL) { - log_error("Unable to allocate enough space for a write buffer."); - return false; - } - - // 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); - return false; - } - } - - // 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 - fileSize = (long)SDL_RWtell(rw); - SDL_RWseek(rw, 0, RW_SEEK_SET); - - // Read all written bytes back into a single buffer - buffer = malloc(fileSize); - SDL_RWread(rw, buffer, fileSize, 1); - 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); - return true; -} - static void scenario_objective_check_guests_by() { uint8 objectiveYear = gScenarioObjectiveYear; diff --git a/src/scenario.h b/src/scenario.h index b6266b46fc..ae2aa4de67 100644 --- a/src/scenario.h +++ b/src/scenario.h @@ -26,6 +26,7 @@ #include "platform/platform.h" #include "world/banner.h" #include "world/map.h" +#include "world/map_animation.h" #include "world/sprite.h" /** @@ -126,7 +127,7 @@ typedef struct { // SC6[4] uint16 elapsed_months; uint16 current_day; - uint32 dword_F663AC; + uint32 scenario_ticks; uint32 scenario_srand_0; uint32 scenario_srand_1; @@ -155,10 +156,10 @@ typedef struct { money32 current_loan; uint32 park_flags; money16 park_entrance_fee; - uint16 word_013573EA; - uint16 word_013573EC; + uint16 rct1_park_entrance_x; + uint16 rct1_park_entrance_y; uint8 pad_013573EE[2]; - uint8 byte_013573F0; + uint8 rct1_park_entrance_z; uint8 pad_013573F1; rct2_peep_spawn peep_spawns[2]; uint8 guest_count_change_modifier; @@ -176,7 +177,7 @@ typedef struct { // Ignored in scenario money32 expenditure_table[14]; uint32 dword_01357880[5]; - uint32 dword_01357894; + uint32 monthly_ride_income; uint32 dword_01357898; uint32 dword_0135789C; uint32 dword_013578A0; @@ -202,8 +203,8 @@ typedef struct { // SC6[10] uint8 active_research_types; uint8 research_progress_stage; - uint32 dword_01357CF4; - uint8 byte_01357CF8[1000]; + uint32 last_researched_item_subject; + uint8 pad_01357CF8[1000]; uint32 next_research_item; uint16 research_progress; uint8 next_research_category; @@ -274,7 +275,7 @@ typedef struct { uint32 same_price_throughout; uint16 suggested_max_guests; uint16 park_rating_warning_days; - uint8 word_01358840; + uint8 last_entrance_style; uint8 rct1_water_colour; uint8 pad_01358842[2]; rct_research_item research_items[500]; @@ -294,12 +295,12 @@ typedef struct { char custom_strings[0x8000]; uint32 game_ticks_1; rct_ride rides[255]; - uint16 word_01388698; + uint16 saved_age; uint16 saved_view_x; uint16 saved_view_y; uint16 saved_view_zoom_and_rotation; - uint8 map_animations[6000]; - uint8 byte_01389E10[6000]; + rct_map_animation map_animations[1000]; + rct_map_animation rct1_map_animations[1000]; uint16 num_map_animations; uint8 pad_0138B582[2]; uint16 ride_ratings_proximity_x; @@ -313,46 +314,20 @@ typedef struct { uint8 ride_ratings_proximity_track_type; uint8 ride_ratings_proximity_base_height; uint16 ride_ratings_proximity_total; - uint16 word_0138B596; - uint16 word_0138B598; - uint16 word_0138B59A; - uint16 word_0138B59C; - uint16 word_0138B59E; - uint16 word_0138B5A0; - uint16 word_0138B5A2; - uint16 word_0138B5A4; - uint16 word_0138B5A6; - uint16 word_0138B5A8; - uint16 word_0138B5AA; - uint16 word_0138B5AC; - uint16 word_0138B5AE; - uint16 word_0138B5B0; - uint16 word_0138B5B2; - uint16 word_0138B5B4; - uint16 word_0138B5B6; - uint16 word_0138B5B8; - uint16 word_0138B5BA; - uint16 word_0138B5BC; - uint16 word_0138B5BE; - uint16 word_0138B5C0; - uint16 word_0138B5C2; - uint16 word_0138B5C4; - uint16 word_0138B5C6; - uint16 word_0138B5C8; - uint16 word_0138B5CA; - uint16 word_0138B5CC; - uint16 word_0138B5CE[31]; - uint8 ride_measurements[0x25860]; + uint16 ride_ratings_proximity_scores[26]; + uint16 ride_ratings_num_brakes; + uint16 ride_ratings_num_reversers; + uint16 word_0138B5CE; + uint8 pad_0138B5D0[60]; + rct_ride_measurement ride_measurements[8]; uint32 next_guest_index; uint16 grass_and_scenery_tilepos; uint32 patrol_areas[0x6600]; // 512 bytes per staff peep - uint8 byte_13CA672[116]; - uint8 byte_13CA6E6[84]; - uint8 byte_13CA73A[4]; + uint8 staff_modes[204]; uint8 unk_13CA73E; uint8 pad_13CA73F; uint8 byte_13CA740; - uint8 byte_13CA741; + uint8 pad_13CA741; uint8 byte_13CA742[4]; uint8 climate; uint8 pad_013CA747; @@ -368,8 +343,8 @@ typedef struct { uint8 current_rain_level; uint8 next_rain_level; rct_news_item news_items[61]; - uint8 byte_13CE730[64]; - uint32 dword_13CE770; + uint8 pad_13CE730[64]; + uint32 rct1_scenario_flags; uint16 wide_path_tile_loop_x; uint16 wide_path_tile_loop_y; uint8 pad_13CE778[434]; @@ -480,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(); @@ -497,7 +474,10 @@ unsigned int scenario_rand_max(unsigned int max); 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_remove_trackless_rides(rct_s6_data *s6); +void scenario_fix_ghosts(rct_s6_data *s6); void scenario_set_filename(const char *value); void scenario_failure(); void scenario_success();