diff --git a/src/openrct2/ParkImporter.h b/src/openrct2/ParkImporter.h index f66a062c1a..7a0bee4e89 100644 --- a/src/openrct2/ParkImporter.h +++ b/src/openrct2/ParkImporter.h @@ -44,9 +44,9 @@ interface IParkImporter public: virtual ~IParkImporter() = default; virtual park_load_result * Load(const utf8 * path) abstract; - virtual park_load_result * LoadSavedGame(const utf8 * path) abstract; - virtual park_load_result * LoadScenario(const utf8 * path) abstract; - virtual park_load_result * LoadFromStream(IStream * stream, bool isScenario) abstract; + virtual park_load_result * LoadSavedGame(const utf8 * path, bool skipObjectCheck = false) abstract; + virtual park_load_result * LoadScenario(const utf8 * path, bool skipObjectCheck = false) abstract; + virtual park_load_result * LoadFromStream(IStream * stream, bool isScenario, bool skipObjectCheck = false) abstract; virtual void Import() abstract; virtual bool GetDetails(scenario_index_entry * dst) abstract; }; diff --git a/src/openrct2/game.c b/src/openrct2/game.c index 4c712f40c2..c0d347b520 100644 --- a/src/openrct2/game.c +++ b/src/openrct2/game.c @@ -1119,19 +1119,9 @@ bool game_load_save(const utf8 *path) // This ensures that the newly loaded save reflects the user's // 'show real names of guests' option, now that it's a global setting peep_update_names(gConfigGeneral.show_real_names_of_guests); - return true; } else { - if (result->error == PARK_LOAD_ERROR_BAD_OBJECTS) - { - // The path needs to be duplicated as it's a const here - // which the window function doesn't like - window_object_load_error_open(strndup(path, strnlen(path, MAX_PATH)), result->object_validity); - } else { - // If loading the SV6 or SV4 failed for a reason other than invalid objects - // the current park state will be corrupted so just go back to the title screen. - title_load(); - } + handle_park_load_failure(result, path); return false; } } @@ -1372,6 +1362,7 @@ void rct2_exit() bool game_load_save_or_scenario(const utf8 * path) { + park_load_result* result; uint32 extension = get_file_extension_type(path); switch (extension) { case FILE_EXTENSION_SV4: @@ -1379,7 +1370,8 @@ bool game_load_save_or_scenario(const utf8 * path) return game_load_save(path); case FILE_EXTENSION_SC4: case FILE_EXTENSION_SC6: - return scenario_load_and_play_from_path(path); + result = scenario_load_and_play_from_path(path); + return (result->error == PARK_LOAD_ERROR_NONE); } return false; } diff --git a/src/openrct2/game.h b/src/openrct2/game.h index 74986880e3..2e273160fb 100644 --- a/src/openrct2/game.h +++ b/src/openrct2/game.h @@ -121,12 +121,6 @@ enum { ERROR_TYPE_FILE_LOAD = 255 }; -enum PARK_LOAD_ERROR { - PARK_LOAD_ERROR_NONE, - PARK_LOAD_ERROR_BAD_OBJECTS, - PARK_LOAD_ERROR_UNKNOWN = 255 -}; - typedef void (GAME_COMMAND_POINTER)(sint32* eax, sint32* ebx, sint32* ecx, sint32* edx, sint32* esi, sint32* edi, sint32* ebp); typedef void (GAME_COMMAND_CALLBACK_POINTER)(sint32 eax, sint32 ebx, sint32 ecx, sint32 edx, sint32 esi, sint32 edi, sint32 ebp); diff --git a/src/openrct2/park_load_result_types.h b/src/openrct2/park_load_result_types.h index 29e3f7c67f..6e43388c6b 100644 --- a/src/openrct2/park_load_result_types.h +++ b/src/openrct2/park_load_result_types.h @@ -19,6 +19,13 @@ #include "object.h" +enum PARK_LOAD_ERROR { + PARK_LOAD_ERROR_NONE, + PARK_LOAD_ERROR_BAD_OBJECTS, + PARK_LOAD_ERROR_INVALID_EXTENSION, + PARK_LOAD_ERROR_UNKNOWN = 255 +}; + typedef struct object_validity_result { uint16 invalid_object_count; diff --git a/src/openrct2/rct1.h b/src/openrct2/rct1.h index 5d2505f283..0126015adc 100644 --- a/src/openrct2/rct1.h +++ b/src/openrct2/rct1.h @@ -20,6 +20,7 @@ #include "management/award.h" #include "management/news_item.h" #include "management/research.h" +#include "park_load_result_types.h" #include "rct12.h" #include "rct2.h" #include "ride/ride.h" @@ -1209,8 +1210,8 @@ extern const uint8 gRideCategories[RIDE_TYPE_COUNT]; sint32 vehicle_preference_compare(uint8 rideType, const char * a, const char * b); bool rideTypeShouldLoseSeparateFlag(const rct_ride_entry *rideEntry); -bool rct1_load_saved_game(const char *path); -bool rct1_load_scenario(const char *path); +park_load_result * rct1_load_saved_game(const char *path); +park_load_result * rct1_load_scenario(const char *path); colour_t rct1_get_colour(colour_t colour); diff --git a/src/openrct2/rct1/S4Importer.cpp b/src/openrct2/rct1/S4Importer.cpp index d4c77d03b5..93ac2f85a1 100644 --- a/src/openrct2/rct1/S4Importer.cpp +++ b/src/openrct2/rct1/S4Importer.cpp @@ -27,6 +27,7 @@ #include "../core/String.hpp" #include "../core/Util.hpp" #include "../object/ObjectManager.h" +#include "../object/ObjectRepository.h" #include "../ParkImporter.h" #include "../scenario/ScenarioSources.h" #include "Tables.h" @@ -142,23 +143,23 @@ public: } } - park_load_result* LoadSavedGame(const utf8 * path) override + park_load_result* LoadSavedGame(const utf8 * path, bool skipObjectCheck = false) override { auto fs = FileStream(path, FILE_MODE_OPEN); - park_load_result* result = LoadFromStream(&fs, false); + park_load_result* result = LoadFromStream(&fs, false, skipObjectCheck); _s4Path = path; return result; } - park_load_result* LoadScenario(const utf8 * path) override + park_load_result* LoadScenario(const utf8 * path, bool skipObjectCheck = false) override { auto fs = FileStream(path, FILE_MODE_OPEN); - park_load_result* result = LoadFromStream(&fs, true); + park_load_result* result = LoadFromStream(&fs, true, skipObjectCheck); _s4Path = path; return result; } - park_load_result* LoadFromStream(IStream * stream, bool isScenario) override + park_load_result* LoadFromStream(IStream * stream, bool isScenario, bool skipObjectCheck = false) override { park_load_result* result = Memory::Allocate(sizeof(park_load_result)); result->error = PARK_LOAD_ERROR_UNKNOWN; @@ -182,12 +183,28 @@ public: { Memory::Copy(&_s4, decodedData.get(), sizeof(rct1_s4)); _s4Path = ""; + + if (!skipObjectCheck) + { + InitialiseEntryMaps(); + CreateAvailableObjectMappings(); + object_validity_result* object_result = GetInvalidObjects(); + + result->object_validity = object_result; + if (object_result->invalid_object_count > 0) + { + result->error = PARK_LOAD_ERROR_BAD_OBJECTS; + } + } + else + { + result->error = PARK_LOAD_ERROR_NONE; + } } else { throw Exception("Unable to decode park."); } - result->error = PARK_LOAD_ERROR_NONE; return result; } @@ -309,6 +326,7 @@ private: Memory::Set(_pathAdditionTypeToEntryMap, 255, sizeof(_pathAdditionTypeToEntryMap)); Memory::Set(_sceneryThemeTypeToEntryMap, 255, sizeof(_sceneryThemeTypeToEntryMap)); + InitialiseEntryMaps(); uint16 mapSize = _s4.map_size == 0 ? 128 : _s4.map_size; // Do map initialisation, same kind of stuff done when loading scenario editor @@ -319,6 +337,18 @@ private: gS6Info.category = SCENARIO_CATEGORY_OTHER; } + void InitialiseEntryMaps() + { + Memory::Set(_rideTypeToRideEntryMap, 255, sizeof(_rideTypeToRideEntryMap)); + Memory::Set(_vehicleTypeToRideEntryMap, 255, sizeof(_vehicleTypeToRideEntryMap)); + Memory::Set(_smallSceneryTypeToEntryMap, 255, sizeof(_smallSceneryTypeToEntryMap)); + Memory::Set(_largeSceneryTypeToEntryMap, 255, sizeof(_largeSceneryTypeToEntryMap)); + Memory::Set(_wallTypeToEntryMap, 255, sizeof(_wallTypeToEntryMap)); + Memory::Set(_pathTypeToEntryMap, 255, sizeof(_pathTypeToEntryMap)); + Memory::Set(_pathAdditionTypeToEntryMap, 255, sizeof(_pathAdditionTypeToEntryMap)); + Memory::Set(_sceneryThemeTypeToEntryMap, 255, sizeof(_sceneryThemeTypeToEntryMap)); + } + /** * Scans the map and research list for all the object types used and builds lists and * lookup tables for converting from hard coded RCT1 object types to dynamic object entries. @@ -1725,6 +1755,73 @@ private: } } + object_validity_result* GetInvalidObjects() + { + object_validity_result* result = Memory::Allocate(sizeof(object_validity_result)); + uint16 invalidObjectCount = 0; + rct_object_entry * * invalidEntries = Memory::AllocateArray(OBJECT_ENTRY_COUNT); + + result->invalid_object_count = invalidObjectCount; + result->invalid_objects = invalidEntries; + + GetInvalidObjects(OBJECT_TYPE_RIDE, _rideEntries.GetEntries(), *result); + GetInvalidObjects(OBJECT_TYPE_SMALL_SCENERY, _smallSceneryEntries.GetEntries(), *result); + GetInvalidObjects(OBJECT_TYPE_LARGE_SCENERY, _largeSceneryEntries.GetEntries(), *result); + GetInvalidObjects(OBJECT_TYPE_WALLS, _wallEntries.GetEntries(), *result); + GetInvalidObjects(OBJECT_TYPE_PATHS, _pathEntries.GetEntries(), *result); + GetInvalidObjects(OBJECT_TYPE_PATH_BITS, _pathAdditionEntries.GetEntries(), *result); + GetInvalidObjects(OBJECT_TYPE_SCENERY_SETS, _sceneryGroupEntries.GetEntries(), *result); + GetInvalidObjects(OBJECT_TYPE_BANNERS, std::vector({ + "BN1 ", + "BN2 ", + "BN3 ", + "BN4 ", + "BN5 ", + "BN6 ", + "BN7 ", + "BN8 ", + "BN9 " + }), *result); + GetInvalidObjects(OBJECT_TYPE_PARK_ENTRANCE, std::vector({ "PKENT1 " }), *result); + GetInvalidObjects(OBJECT_TYPE_WATER, _waterEntry.GetEntries(), *result); + + return result; + } + + void GetInvalidObjects(uint8 objectType, const std::vector &entries, object_validity_result &result) + { + IObjectRepository * objectRepository = GetObjectRepository(); + for (const char * objectName : entries) + { + rct_object_entry entry; + entry.flags = 0x00008000 + objectType; + Memory::Copy(entry.name, objectName, 8); + entry.checksum = 0; + + const ObjectRepositoryItem * ori = nullptr; + ori = objectRepository->FindObject(&entry); + if (ori == nullptr) + { + rct_object_entry * invalid_entry = Memory::Allocate(sizeof(rct_object_entry)); + invalid_entry->flags = entry.flags; + Memory::Copy(invalid_entry->name, objectName, 8); + result.invalid_objects[result.invalid_object_count++] = invalid_entry; + } + else + { + Object * object = objectRepository->LoadObject(ori); + if (object == nullptr && objectType != OBJECT_TYPE_SCENERY_SETS) + { + rct_object_entry * invalid_entry = Memory::Allocate(sizeof(rct_object_entry)); + invalid_entry->flags = entry.flags; + Memory::Copy(invalid_entry->name, objectName, 8); + result.invalid_objects[result.invalid_object_count++] = invalid_entry; + } + SafeFree(object); + } + } + } + void ImportMapElements() { Memory::Copy(gMapElements, _s4.map_elements, RCT1_MAX_MAP_ELEMENTS * sizeof(rct_map_element)); @@ -2568,37 +2665,44 @@ IParkImporter * ParkImporter::CreateS4() ///////////////////////////////////////// extern "C" { - bool rct1_load_saved_game(const utf8 * path) + park_load_result* rct1_load_saved_game(const utf8 * path) { - bool result; + park_load_result* result = {}; auto s4Importer = new S4Importer(); try { - s4Importer->LoadSavedGame(path); - s4Importer->Import(); - result = true; + result = s4Importer->LoadSavedGame(path); + if (result->error == PARK_LOAD_ERROR_NONE) + { + s4Importer->Import(); + } + } catch (const Exception &) { - result = false; + result = {}; + result->error = PARK_LOAD_ERROR_UNKNOWN; } delete s4Importer; return result; } - bool rct1_load_scenario(const utf8 * path) + park_load_result* rct1_load_scenario(const utf8 * path) { - bool result; + park_load_result* result = {}; auto s4Importer = new S4Importer(); try { - s4Importer->LoadScenario(path); - s4Importer->Import(); - result = true; + result = s4Importer->LoadSavedGame(path); + if (result->error == PARK_LOAD_ERROR_NONE) + { + s4Importer->Import(); + } } catch (const Exception &) { - result = false; + result = {}; + result->error = PARK_LOAD_ERROR_UNKNOWN; } delete s4Importer; return result; diff --git a/src/openrct2/rct2.c b/src/openrct2/rct2.c index 5c48cb7962..89fe7de289 100644 --- a/src/openrct2/rct2.c +++ b/src/openrct2/rct2.c @@ -339,9 +339,14 @@ bool rct2_open_file(const char *path) } } else if (_stricmp(extension, "sc6") == 0) { // TODO scenario install - if (scenario_load_and_play_from_path(path)) { + park_load_result *result = scenario_load_and_play_from_path(path); + if (result->error == PARK_LOAD_ERROR_NONE) { return true; } + else { + handle_park_load_failure(result, path); + return false; + } } else if (_stricmp(extension, "td6") == 0 || _stricmp(extension, "td4") == 0) { // TODO track design install return true; diff --git a/src/openrct2/rct2/S6Importer.cpp b/src/openrct2/rct2/S6Importer.cpp index 9ce3c36827..9ca6083054 100644 --- a/src/openrct2/rct2/S6Importer.cpp +++ b/src/openrct2/rct2/S6Importer.cpp @@ -98,25 +98,25 @@ public: } } - park_load_result* LoadSavedGame(const utf8 * path) override + park_load_result* LoadSavedGame(const utf8 * path, bool skipObjectCheck = false) override { auto fs = FileStream(path, FILE_MODE_OPEN); - park_load_result* result = LoadFromStream(&fs, false); + park_load_result* result = LoadFromStream(&fs, false, skipObjectCheck); _s6Path = path; return result; } - park_load_result* LoadScenario(const utf8 * path) override + park_load_result* LoadScenario(const utf8 * path, bool skipObjectCheck = false) override { auto fs = FileStream(path, FILE_MODE_OPEN); - park_load_result* result = LoadFromStream(&fs, true); + park_load_result* result = LoadFromStream(&fs, true, skipObjectCheck); _s6Path = path; return result; } - park_load_result* LoadFromStream(IStream * stream, bool isScenario) override + park_load_result* LoadFromStream(IStream * stream, bool isScenario, bool skipObjectCheck = false) override { park_load_result* result = Memory::Allocate(sizeof(park_load_result)); result->error = PARK_LOAD_ERROR_UNKNOWN; @@ -487,14 +487,14 @@ extern "C" * rct2: 0x00676053 * scenario (ebx) */ - park_load_result *scenario_load(const char * path) + park_load_result* scenario_load(const char * path) { park_load_result* result = {}; auto s6Importer = new S6Importer(GetObjectRepository(), GetObjectManager()); try { result = s6Importer->LoadScenario(path); - if (result->error != PARK_LOAD_ERROR_NONE) + if (result->error == PARK_LOAD_ERROR_NONE) { s6Importer->Import(); diff --git a/src/openrct2/scenario/ScenarioRepository.cpp b/src/openrct2/scenario/ScenarioRepository.cpp index b0d89666e6..d732fbb413 100644 --- a/src/openrct2/scenario/ScenarioRepository.cpp +++ b/src/openrct2/scenario/ScenarioRepository.cpp @@ -322,7 +322,7 @@ private: try { auto s4Importer = std::unique_ptr(ParkImporter::CreateS4()); - s4Importer->LoadScenario(path.c_str()); + s4Importer->LoadScenario(path.c_str(), true); if (s4Importer->GetDetails(entry)) { String::Set(entry->path, sizeof(entry->path), path.c_str()); diff --git a/src/openrct2/scenario/scenario.c b/src/openrct2/scenario/scenario.c index 20a4b8abc3..17ca3d4677 100644 --- a/src/openrct2/scenario/scenario.c +++ b/src/openrct2/scenario/scenario.c @@ -88,31 +88,29 @@ money32 gScenarioCompanyValueRecord; static sint32 scenario_create_ducks(); static void scenario_objective_check(); -sint32 scenario_load_and_play_from_path(const char *path) +park_load_result* scenario_load_and_play_from_path(const char *path) { window_close_construction_windows(); uint32 extension = get_file_extension_type(path); - park_load_result* result; + park_load_result* result = malloc(sizeof(park_load_result)); if (extension == FILE_EXTENSION_SC6) { result = scenario_load(path); if (result->error != PARK_LOAD_ERROR_NONE) { - if (result->error == PARK_LOAD_ERROR_BAD_OBJECTS) - { - // The path needs to be duplicated as it's a const here - // which the window function doesn't like - window_object_load_error_open(strndup(path, strnlen(path, MAX_PATH)), result->object_validity); - } - return 0; + return result; } } else if (extension == FILE_EXTENSION_SC4) { - if (!rct1_load_scenario(path)) - return 0; + result = rct1_load_scenario(path); + if (result->error != PARK_LOAD_ERROR_NONE) + { + return result; + } } else { - return 0; + result->error = PARK_LOAD_ERROR_INVALID_EXTENSION; + return result; } reset_sprite_spatial_index(); @@ -141,8 +139,7 @@ sint32 scenario_load_and_play_from_path(const char *path) // This ensures that the newly loaded scenario reflects the user's // 'show real names of guests' option, now that it's a global setting peep_update_names(gConfigGeneral.show_real_names_of_guests); - - return 1; + return result; } void scenario_begin() diff --git a/src/openrct2/scenario/scenario.h b/src/openrct2/scenario/scenario.h index a2000c9bf9..13db64199b 100644 --- a/src/openrct2/scenario/scenario.h +++ b/src/openrct2/scenario/scenario.h @@ -391,7 +391,7 @@ extern uint32 gLastAutoSaveUpdate; extern const char *_scenarioFileName; park_load_result *scenario_load(const char *path); -sint32 scenario_load_and_play_from_path(const char *path); +park_load_result *scenario_load_and_play_from_path(const char *path); void scenario_begin(); void scenario_update(); diff --git a/src/openrct2/util/util.c b/src/openrct2/util/util.c index 9955cc9181..26844f5e2d 100644 --- a/src/openrct2/util/util.c +++ b/src/openrct2/util/util.c @@ -18,8 +18,10 @@ #include #include "../common.h" #include "../core/Guard.hpp" +#include "../interface/window.h" #include "../localisation/localisation.h" #include "../platform/platform.h" +#include "../title/TitleScreen.h" #include "util.h" #include "zlib.h" @@ -547,3 +549,19 @@ size_t strcatftime(char * buffer, size_t bufferSize, const char * format, const } return 0; } + +void handle_park_load_failure(park_load_result* result, const utf8* path) +{ + if (result->error == PARK_LOAD_ERROR_BAD_OBJECTS) + { + // The path needs to be duplicated as it's a const here + // which the window function doesn't like + window_object_load_error_open(strndup(path, strnlen(path, MAX_PATH)), result->object_validity); + } + else if (result->error != PARK_LOAD_ERROR_NONE) { + // If loading the SV6 or SV4 failed for a reason other than invalid objects + // the current park state will be corrupted so just go back to the title screen. + title_load(); + } + SafeFree(result); +} \ No newline at end of file diff --git a/src/openrct2/util/util.h b/src/openrct2/util/util.h index 16a1eb64a0..43e0da7261 100644 --- a/src/openrct2/util/util.h +++ b/src/openrct2/util/util.h @@ -19,6 +19,7 @@ #include #include "../common.h" +#include "../park_load_result_types.h" sint32 squaredmetres_to_squaredfeet(sint32 squaredMetres); sint32 metres_to_feet(sint32 metres); @@ -65,4 +66,5 @@ money32 add_clamp_money32(money32 value, money32 value_to_add); size_t strcatftime(char * buffer, size_t bufferSize, const char * format, const struct tm * tp); +void handle_park_load_failure(park_load_result* result, const utf8* path); #endif diff --git a/src/openrct2/windows/server_start.c b/src/openrct2/windows/server_start.c index 5f17dcc2eb..54bac250b5 100644 --- a/src/openrct2/windows/server_start.c +++ b/src/openrct2/windows/server_start.c @@ -21,6 +21,7 @@ #include "../interface/window.h" #include "../localisation/localisation.h" #include "../network/network.h" +#include "../platform/platform.h" #include "../sprites.h" #include "../title/TitleScreen.h" #include "../util/util.h" @@ -164,11 +165,13 @@ static void window_server_start_close(rct_window *w) static void window_server_start_scenarioselect_callback(const utf8 *path) { + park_load_result* result; network_set_password(_password); - if (scenario_load_and_play_from_path(path)) { + result = scenario_load_and_play_from_path(path); + if (result->error == PARK_LOAD_ERROR_NONE) { network_begin_server(gConfigNetwork.default_port, gConfigNetwork.listen_address); } else { - title_load(); + handle_park_load_failure(result, path); } } diff --git a/src/openrct2/windows/title_menu.c b/src/openrct2/windows/title_menu.c index 2b74ec9dac..a722cc8781 100644 --- a/src/openrct2/windows/title_menu.c +++ b/src/openrct2/windows/title_menu.c @@ -24,6 +24,7 @@ #include "../localisation/localisation.h" #include "../sprites.h" #include "../title/TitleScreen.h" +#include "../util/util.h" #include "dropdown.h" enum { @@ -128,9 +129,8 @@ void window_title_menu_open() static void window_title_menu_scenarioselect_callback(const utf8 *path) { - if (!scenario_load_and_play_from_path(path)) { - title_load(); - } + park_load_result *result = scenario_load_and_play_from_path(path); + handle_park_load_failure(result, path); } static void window_title_menu_mouseup(rct_window *w, rct_widgetindex widgetIndex) diff --git a/src/openrct2/windows/top_toolbar.c b/src/openrct2/windows/top_toolbar.c index bc4ee90583..c05751e896 100644 --- a/src/openrct2/windows/top_toolbar.c +++ b/src/openrct2/windows/top_toolbar.c @@ -519,9 +519,8 @@ static void window_top_toolbar_mousedown(rct_widgetindex widgetIndex, rct_window static void window_top_toolbar_scenarioselect_callback(const utf8 *path) { - if (!scenario_load_and_play_from_path(path)) { - title_load(); - } + park_load_result *result = scenario_load_and_play_from_path(path); + handle_park_load_failure(result, path); } /**