Merge pull request #3528 from IntelOrca/refactor/s6importer

Add S6Importer and S6Exporter
This commit is contained in:
Ted John 2016-05-08 23:08:08 +01:00
commit 0570ec27ff
19 changed files with 1339 additions and 669 deletions

View File

@ -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 = "<group>"; };
008BF7281CDAA5C30019A2AD /* track_design.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = track_design.c; sourceTree = "<group>"; };
008BF7291CDAA5C30019A2AD /* track_design.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = track_design.h; sourceTree = "<group>"; };
C61FAAE01CD1643A0010C9D8 /* paint_surface.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = paint_surface.c; sourceTree = "<group>"; };
C61FAAE11CD1643A0010C9D8 /* paint_surface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = paint_surface.h; sourceTree = "<group>"; };
C650B2151CCABBDD00B4D91C /* S4Importer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = S4Importer.cpp; sourceTree = "<group>"; };
C650B2161CCABBDD00B4D91C /* S4Importer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S4Importer.h; sourceTree = "<group>"; };
C650B2171CCABBDD00B4D91C /* tables.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tables.cpp; sourceTree = "<group>"; };
@ -429,6 +428,10 @@
C686F90A1CDBC3B7009F9BFC /* water_coaster.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = water_coaster.c; sourceTree = "<group>"; };
C686F9561CDBC4C7009F9BFC /* vehicle_paint.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = vehicle_paint.c; sourceTree = "<group>"; };
C686F9571CDBC4C7009F9BFC /* vehicle_paint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vehicle_paint.h; sourceTree = "<group>"; };
C6B5A7D01CDFE4CB00C9C006 /* S6Exporter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = S6Exporter.cpp; sourceTree = "<group>"; };
C6B5A7D11CDFE4CB00C9C006 /* S6Exporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S6Exporter.h; sourceTree = "<group>"; };
C6B5A7D21CDFE4CB00C9C006 /* S6Importer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = S6Importer.cpp; sourceTree = "<group>"; };
C6B5A7D31CDFE4CB00C9C006 /* S6Importer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S6Importer.h; sourceTree = "<group>"; };
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 = "<group>";
};
C6B5A7CF1CDFE4CB00C9C006 /* rct2 */ = {
isa = PBXGroup;
children = (
C6B5A7D01CDFE4CB00C9C006 /* S6Exporter.cpp */,
C6B5A7D11CDFE4CB00C9C006 /* S6Exporter.h */,
C6B5A7D21CDFE4CB00C9C006 /* S6Importer.cpp */,
C6B5A7D31CDFE4CB00C9C006 /* S6Importer.h */,
);
name = rct2;
path = src/rct2;
sourceTree = "<group>";
};
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 */,

View File

@ -112,6 +112,8 @@
<ClCompile Include="src\rct1\S4Importer.cpp" />
<ClCompile Include="src\rct1\Tables.cpp" />
<ClCompile Include="src\rct2.c" />
<ClCompile Include="src\rct2\S6Exporter.cpp" />
<ClCompile Include="src\rct2\S6Importer.cpp" />
<ClCompile Include="src\ride\cable_lift.c" />
<ClCompile Include="src\ride\coaster\air_powered_vertical_coaster.c" />
<ClCompile Include="src\ride\coaster\bobsleigh_coaster.c" />
@ -370,6 +372,8 @@
<ClInclude Include="src\rct1\Tables.h" />
<ClInclude Include="src\rct1\S4Importer.h" />
<ClInclude Include="src\rct2.h" />
<ClInclude Include="src\rct2\S6Exporter.h" />
<ClInclude Include="src\rct2\S6Importer.h" />
<ClInclude Include="src\ride\cable_lift.h" />
<ClInclude Include="src\ride\ride.h" />
<ClInclude Include="src\ride\ride_data.h" />

View File

@ -270,6 +270,8 @@
<ClCompile Include="src\ride\gentle\space_rings.c" />
<ClCompile Include="src\ride\gentle\spiral_slide.c" />
<ClCompile Include="src\ride\vehicle_paint.c" />
<ClCompile Include="src\rct2\S6Importer.cpp" />
<ClCompile Include="src\rct2\S6Exporter.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="resources\resource.h" />
@ -382,6 +384,8 @@
<ClInclude Include="src\world\sprite.h" />
<ClInclude Include="src\world\water.h" />
<ClInclude Include="src\ride\vehicle_paint.h" />
<ClInclude Include="src\rct2\S6Importer.h" />
<ClInclude Include="src\rct2\S6Exporter.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="resources\OpenRCT2.rc" />

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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();

View File

@ -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();

View File

@ -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;
}

580
src/rct2/S6Exporter.cpp Normal file
View File

@ -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;
}
}

47
src/rct2/S6Exporter.h Normal file
View File

@ -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);
};

534
src/rct2/S6Importer.cpp Normal file
View File

@ -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;
}
}

46
src/rct2/S6Importer.h Normal file
View File

@ -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;
};

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -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;

View File

@ -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();