From 214bf3988bc40711d610b77d4d90c71a90ba280f Mon Sep 17 00:00:00 2001 From: rwjuk Date: Tue, 6 Jun 2017 14:34:30 +0100 Subject: [PATCH] Implement 'missing objects' window Implement 'missing objects' window Basic implementation of 'bad objects' window Add new object_load_error.c Add object_load_error.c Faffing about String stuff Stuff Get window basically displaying Proper col header for object Display object types Display file name and explanatory message Probably about time I added myself to the dev list Cleanup and comments Make bad object window work with SC6 Fix whitespace, string IDs, flip core function sense Fix spacing in string_ids.h Fix string ID snafu Fix HasNoInvalidObjects() sense Attempt to refactor this to pass data properly Move typedefs to separate header Fix up signatures Add park_load_result_types.h Clean up includes and remnants of prev implementation Split duplication into function, free invalid entries list on close Use pointer for object_validity_result param Fixup string IDs Use LoadObject() directly Use dependency injection, fix string termination Xcode fix, make helper function static Fix buffer overrun and memory leak Use SDL for clipboard functionality Fix function & variable declarations Rework editor_read_s6() to use new park load result type Update changelog for #5624 [ci skip] Fix mem leak, function signature and whitespace --- OpenRCT2.xcodeproj/project.pbxproj | 8 + contributors.md | 1 + data/language/en-GB.txt | 8 + distribution/changelog.txt | 1 + src/openrct2-ui/UiContext.cpp | 5 + src/openrct2/Context.cpp | 5 + src/openrct2/ParkImporter.h | 17 +- src/openrct2/editor.c | 4 +- src/openrct2/game.c | 25 +- src/openrct2/game.h | 9 +- src/openrct2/interface/window.h | 2 + src/openrct2/localisation/string_ids.h | 9 + src/openrct2/object/ObjectManager.cpp | 53 +++- src/openrct2/object/ObjectManager.h | 20 +- src/openrct2/park_load_result_types.h | 33 +++ src/openrct2/platform/platform.h | 1 + src/openrct2/platform/windows.c | 3 +- src/openrct2/rct1/S4Importer.cpp | 23 +- src/openrct2/rct2/S6Importer.cpp | 89 ++++-- src/openrct2/scenario/scenario.c | 12 +- src/openrct2/scenario/scenario.h | 3 +- src/openrct2/ui/DummyUiContext.cpp | 3 + src/openrct2/ui/UiContext.h | 3 + src/openrct2/windows/object_load_error.c | 328 +++++++++++++++++++++++ 24 files changed, 598 insertions(+), 67 deletions(-) create mode 100644 src/openrct2/park_load_result_types.h create mode 100644 src/openrct2/windows/object_load_error.c diff --git a/OpenRCT2.xcodeproj/project.pbxproj b/OpenRCT2.xcodeproj/project.pbxproj index 2b5311724e..dfc7179f70 100644 --- a/OpenRCT2.xcodeproj/project.pbxproj +++ b/OpenRCT2.xcodeproj/project.pbxproj @@ -471,6 +471,8 @@ F7D774AC1EC6741D00BE6EBC /* language in CopyFiles */ = {isa = PBXBuildFile; fileRef = D4EC48E41C2637710024B507 /* language */; }; F7D774AD1EC6741D00BE6EBC /* shaders in CopyFiles */ = {isa = PBXBuildFile; fileRef = D43407E11D0E14CE00C2B3D4 /* shaders */; }; F7D774AE1EC6741D00BE6EBC /* title in CopyFiles */ = {isa = PBXBuildFile; fileRef = D4EC48E51C2637710024B507 /* title */; }; + F7FFCDAA1F02FD8E0078BFFB /* object_load_error.c in Sources */ = {isa = PBXBuildFile; fileRef = F7FFCDA91F02FD8E0078BFFB /* object_load_error.c */; }; + F7FFCDAC1F02FF570078BFFB /* object_load_error.c in Sources */ = {isa = PBXBuildFile; fileRef = F7FFCDA91F02FD8E0078BFFB /* object_load_error.c */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1354,6 +1356,8 @@ F7CB864C1EEDA1A80030C877 /* WindowManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowManager.h; sourceTree = ""; }; F7D7747E1EC61E5100BE6EBC /* UiContext.macOS.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = UiContext.macOS.mm; sourceTree = ""; usesTabs = 0; }; F7D774841EC66CD700BE6EBC /* OpenRCT2-cli */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "OpenRCT2-cli"; sourceTree = BUILT_PRODUCTS_DIR; }; + F7FFCDA91F02FD8E0078BFFB /* object_load_error.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = object_load_error.c; sourceTree = ""; }; + F7FFCDAB1F02FEC00078BFFB /* park_load_result_types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = park_load_result_types.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1776,6 +1780,7 @@ F76C84371EC4E7CC00FA49E2 /* object_list.h */, F76C84381EC4E7CC00FA49E2 /* OpenRCT2.cpp */, F76C84391EC4E7CC00FA49E2 /* OpenRCT2.h */, + F7FFCDAB1F02FEC00078BFFB /* park_load_result_types.h */, F76C84511EC4E7CC00FA49E2 /* ParkImporter.cpp */, F76C84521EC4E7CC00FA49E2 /* ParkImporter.h */, F76C84641EC4E7CC00FA49E2 /* PlatformEnvironment.cpp */, @@ -2425,6 +2430,7 @@ F76C85321EC4E7CD00FA49E2 /* new_ride.c */, F76C85331EC4E7CD00FA49E2 /* news.c */, F76C85341EC4E7CD00FA49E2 /* news_options.c */, + F7FFCDA91F02FD8E0078BFFB /* object_load_error.c */, F76C85351EC4E7CD00FA49E2 /* options.c */, F76C85361EC4E7CD00FA49E2 /* park.c */, F76C85371EC4E7CD00FA49E2 /* player.c */, @@ -3014,6 +3020,7 @@ F76C888C1EC5324E00FA49E2 /* UiContext.cpp in Sources */, F76C888D1EC5324E00FA49E2 /* UiContext.Linux.cpp in Sources */, F76C888E1EC5324E00FA49E2 /* UiContext.Win32.cpp in Sources */, + F7FFCDAA1F02FD8E0078BFFB /* object_load_error.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3021,6 +3028,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F7FFCDAC1F02FF570078BFFB /* object_load_error.c in Sources */, F7CB864E1EEDA2050030C877 /* DummyWindowManager.cpp in Sources */, F775F5381EE3725C001F00E7 /* DummyAudioContext.cpp in Sources */, F775F5351EE35A89001F00E7 /* DummyUiContext.cpp in Sources */, diff --git a/contributors.md b/contributors.md index 5c906fa9d1..04dcad5afc 100644 --- a/contributors.md +++ b/contributors.md @@ -9,6 +9,7 @@ Includes all git commit authors. Aliases are GitHub user names. * Lewis Fox (LRFLEW) - Developer, macOS management * Marijn van der Werf (marijnvdwerf) - Developer * (zsilencer) - Developer +* Richard Jenkins (rwjuk) - Developer, issue management ## Implementation (RCT2) * Ted John (IntelOrca) diff --git a/data/language/en-GB.txt b/data/language/en-GB.txt index 5fefe5deaa..2126236283 100644 --- a/data/language/en-GB.txt +++ b/data/language/en-GB.txt @@ -4431,6 +4431,14 @@ STR_6119 :A cheap and easy to build roller coaster, but with a limited height STR_6120 :{BABYBLUE}New vehicle now available for {STRINGID}:{NEWLINE}{STRINGID} STR_6121 :{SMALLFONT}{BLACK}Extends the park's land rights all the way to the edges of the map STR_6122 :There are not enough roller coasters in this scenario! +STR_6123 :Error loading objects for park +STR_6124 :Object name +STR_6125 :Object type +STR_6126 :Unknown type +STR_6127 :File: {STRING} +STR_6128 :The file could not be loaded as some of the objects referenced in it are missing or corrupt. A list of these objects is given below. +STR_6129 :Copy selected item to clipboard +STR_6130 :Copy entire list to clipboard ############# # Scenarios # diff --git a/distribution/changelog.txt b/distribution/changelog.txt index edc815b9a4..1f9e5ae6ea 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -1,5 +1,6 @@ 0.0.8 (in development) ------------------------------------------------------------------------ +- Feature: [#1399 (partial), #5177] Add window that displays any missing/corrupt objects when loading a park - Feature: [#5056] Add cheat to own all land. - Feature: [#5133] Add option to display guest expenditure (as seen in RCTC). - Feature: [#5196] Add cheat to disable ride ageing. diff --git a/src/openrct2-ui/UiContext.cpp b/src/openrct2-ui/UiContext.cpp index 988feb218d..b12aea7dac 100644 --- a/src/openrct2-ui/UiContext.cpp +++ b/src/openrct2-ui/UiContext.cpp @@ -639,6 +639,11 @@ public: } } + bool SetClipboardText(const utf8* target) override + { + return (SDL_SetClipboardText(target) == 0); + } + private: void OnResize(sint32 width, sint32 height) diff --git a/src/openrct2/Context.cpp b/src/openrct2/Context.cpp index ea7da2c897..d067bd9791 100644 --- a/src/openrct2/Context.cpp +++ b/src/openrct2/Context.cpp @@ -721,4 +721,9 @@ extern "C" return nullptr; } } + + bool platform_place_string_on_clipboard(utf8* target) + { + return GetContext()->GetUiContext()->SetClipboardText(target); + } } diff --git a/src/openrct2/ParkImporter.h b/src/openrct2/ParkImporter.h index bbce6a92b9..f66a062c1a 100644 --- a/src/openrct2/ParkImporter.h +++ b/src/openrct2/ParkImporter.h @@ -18,6 +18,15 @@ #include "common.h" +#ifdef __cplusplus +extern "C" +{ +#endif +#include "park_load_result_types.h" +#ifdef __cplusplus +} +#endif + #ifdef __cplusplus #include @@ -34,10 +43,10 @@ interface IParkImporter { public: virtual ~IParkImporter() = default; - virtual void Load(const utf8 * path) abstract; - virtual void LoadSavedGame(const utf8 * path) abstract; - virtual void LoadScenario(const utf8 * path) abstract; - virtual void LoadFromStream(IStream * stream, bool isScenario) abstract; + 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 void Import() abstract; virtual bool GetDetails(scenario_index_entry * dst) abstract; }; diff --git a/src/openrct2/editor.c b/src/openrct2/editor.c index 140f30aa4f..b9ab7e7f02 100644 --- a/src/openrct2/editor.c +++ b/src/openrct2/editor.c @@ -261,14 +261,14 @@ static sint32 editor_load_landscape_from_sc4(const char *path) */ static sint32 editor_read_s6(const char *path) { - bool loadResult = false; + park_load_result* loadResult = { 0 }; const char *extension = path_get_extension(path); if (_stricmp(extension, ".sc6") == 0) { loadResult = scenario_load(path); } else if (_stricmp(extension, ".sv6") == 0) { loadResult = game_load_sv6_path(path); } - if (!loadResult) { + if (loadResult->error != PARK_LOAD_ERROR_NONE) { return 0; } diff --git a/src/openrct2/game.c b/src/openrct2/game.c index 5e1ff8993a..4c712f40c2 100644 --- a/src/openrct2/game.c +++ b/src/openrct2/game.c @@ -1094,18 +1094,20 @@ bool game_load_save(const utf8 *path) safe_strcpy(gScenarioSavePath, path, MAX_PATH); uint32 extension_type = get_file_extension_type(path); - bool result = false; + park_load_result* result = {0}; + bool load_success = false; if (extension_type == FILE_EXTENSION_SV6) { result = game_load_sv6_path(path); - if (result) + load_success = (result->error == PARK_LOAD_ERROR_NONE); + if (load_success) gFirstTimeSaving = false; } else if (extension_type == FILE_EXTENSION_SV4) { - result = rct1_load_saved_game(path); - if (result) + load_success = rct1_load_saved_game(path); + if (load_success) gFirstTimeSaving = true; } - if (result) { + if (load_success) { if (network_get_mode() == NETWORK_MODE_CLIENT) { network_close(); } @@ -1120,9 +1122,16 @@ bool game_load_save(const utf8 *path) return true; } else { - // If loading the SV6 or SV4 failed, the current park state will be corrupted - // so just go back to the title screen. - title_load(); + 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(); + } return false; } } diff --git a/src/openrct2/game.h b/src/openrct2/game.h index 1f205694b7..74986880e3 100644 --- a/src/openrct2/game.h +++ b/src/openrct2/game.h @@ -20,6 +20,7 @@ #include "rct2/addresses.h" #include "common.h" #include "scenario/scenario.h" +#include "park_load_result_types.h" enum GAME_COMMAND { GAME_COMMAND_SET_RIDE_APPEARANCE, @@ -120,6 +121,12 @@ 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); @@ -170,7 +177,7 @@ sint32 game_do_command_p(sint32 command, sint32 *eax, sint32 *ebx, sint32 *ecx, void game_log_multiplayer_command(int command, int *eax, int* ebx, int* ecx, int* edx, int* edi, int* ebp); void game_load_or_quit_no_save_prompt(); -bool game_load_sv6_path(const char * path); +park_load_result * game_load_sv6_path(const char * path); bool game_load_save(const utf8 *path); void game_load_init(); void game_pause_toggle(sint32 *eax, sint32 *ebx, sint32 *ecx, sint32 *edx, sint32 *esi, sint32 *edi, sint32 *ebp); diff --git a/src/openrct2/interface/window.h b/src/openrct2/interface/window.h index f72def0a68..72525e131b 100644 --- a/src/openrct2/interface/window.h +++ b/src/openrct2/interface/window.h @@ -477,6 +477,7 @@ enum { WC_CUSTOM_CURRENCY_CONFIG = 129, WC_DEBUG_PAINT = 130, WC_VIEW_CLIPPING = 131, + WC_OBJECT_LOAD_ERROR = 132, // Only used for colour schemes WC_STAFF = 220, @@ -766,6 +767,7 @@ rct_window *window_mapgen_open(); rct_window *window_loadsave_open(sint32 type, char *defaultName); rct_window *window_changelog_open(); void window_debug_paint_open(); +rct_window *window_object_load_error_open(utf8* path, object_validity_result* result); rct_window * window_editor_main_open(); void window_editor_bottom_toolbar_open(); diff --git a/src/openrct2/localisation/string_ids.h b/src/openrct2/localisation/string_ids.h index 54984fa6da..21048584fe 100644 --- a/src/openrct2/localisation/string_ids.h +++ b/src/openrct2/localisation/string_ids.h @@ -3791,6 +3791,15 @@ enum { STR_NOT_ENOUGH_ROLLER_COASTERS = 6122, + STR_OBJECT_LOAD_ERROR_TITLE = 6123, + STR_OBJECT_NAME = 6124, + STR_OBJECT_TYPE = 6125, + STR_UNKNOWN_OBJECT_TYPE = 6126, + STR_OBJECT_ERROR_WINDOW_FILE = 6127, + STR_OBJECT_ERROR_WINDOW_EXPLANATION = 6128, + STR_COPY_SELECTED = 6129, + STR_COPY_ALL = 6130, + // Have to include resource strings (from scenarios and objects) for the time being now that language is partially working STR_COUNT = 32768 }; diff --git a/src/openrct2/object/ObjectManager.cpp b/src/openrct2/object/ObjectManager.cpp index 9672c7b6fb..b4e77135e4 100644 --- a/src/openrct2/object/ObjectManager.cpp +++ b/src/openrct2/object/ObjectManager.cpp @@ -36,8 +36,8 @@ extern "C" class ObjectManager final : public IObjectManager { private: - IObjectRepository * _objectRepository; - Object * * _loadedObjects = nullptr; + IObjectRepository * _objectRepository; + Object * * _loadedObjects = nullptr; public: explicit ObjectManager(IObjectRepository * objectRepository) @@ -438,6 +438,51 @@ private: return entryIndex; } + rct_object_entry* DuplicateObjectEntry(const rct_object_entry* original) + { + rct_object_entry * duplicate = Memory::Allocate(sizeof(rct_object_entry)); + duplicate->checksum = original->checksum; + strncpy(duplicate->name, original->name, 8); + duplicate->flags = original->flags; + return duplicate; + } + + object_validity_result* GetInvalidObjects(const rct_object_entry * entries) override + { + uint16 invalidObjectCount = 0; + rct_object_entry * * invalidEntries = Memory::AllocateArray(OBJECT_ENTRY_COUNT); + for (sint32 i = 0; i < OBJECT_ENTRY_COUNT; i++) + { + const rct_object_entry * entry = &entries[i]; + const ObjectRepositoryItem * ori = nullptr; + if (!object_entry_is_empty(entry)) + { + ori = _objectRepository->FindObject(entry); + if (ori == nullptr) + { + invalidEntries[invalidObjectCount++] = DuplicateObjectEntry(entry); + } + else + { + Object * loadedObject = nullptr; + loadedObject = ori->LoadedObject; + if (loadedObject == nullptr) + { + loadedObject = _objectRepository->LoadObject(ori); + if (loadedObject == nullptr) + { + invalidEntries[invalidObjectCount++] = DuplicateObjectEntry(entry); + } + } + } + } + } + object_validity_result* result = Memory::Allocate(sizeof(object_validity_result)); + result->invalid_object_count = invalidObjectCount; + result->invalid_objects = invalidEntries; + return result; + } + bool GetRequiredObjects(const rct_object_entry * entries, const ObjectRepositoryItem * * requiredObjects, size_t * outNumRequiredObjects) @@ -519,14 +564,14 @@ private: return loadedObject; } - static void ReportMissingObject(const rct_object_entry * entry) + void ReportMissingObject(const rct_object_entry * entry) { utf8 objName[9] = { 0 }; Memory::Copy(objName, entry->name, 8); Console::Error::WriteLine("[%s] Object not found.", objName); } - static void ReportObjectLoadProblem(const rct_object_entry * entry) + void ReportObjectLoadProblem(const rct_object_entry * entry) { utf8 objName[9] = { 0 }; Memory::Copy(objName, entry->name, 8); diff --git a/src/openrct2/object/ObjectManager.h b/src/openrct2/object/ObjectManager.h index 2afb049203..d93f5bcda3 100644 --- a/src/openrct2/object/ObjectManager.h +++ b/src/openrct2/object/ObjectManager.h @@ -27,6 +27,7 @@ extern "C" { #endif #include "../object.h" + #include "../park_load_result_types.h" #ifdef __cplusplus } #endif @@ -41,9 +42,10 @@ interface IObjectManager { virtual ~IObjectManager() { } - virtual Object * GetLoadedObject(size_t index) abstract; - virtual Object * GetLoadedObject(const rct_object_entry * entry) abstract; - virtual uint8 GetLoadedObjectEntryIndex(const Object * object) abstract; + virtual Object * GetLoadedObject(size_t index) abstract; + virtual Object * GetLoadedObject(const rct_object_entry * entry) abstract; + virtual uint8 GetLoadedObjectEntryIndex(const Object * object) abstract; + virtual object_validity_result* GetInvalidObjects(const rct_object_entry * entries) abstract; virtual Object * LoadObject(const rct_object_entry * entry) abstract; virtual bool LoadObjects(const rct_object_entry * entries, size_t count) abstract; @@ -65,12 +67,12 @@ extern "C" { #endif -void * object_manager_get_loaded_object_by_index(size_t index); -void * object_manager_get_loaded_object(const rct_object_entry * entry); -uint8 object_manager_get_loaded_object_entry_index(const void * loadedObject); -void * object_manager_load_object(const rct_object_entry * entry); -void object_manager_unload_objects(const rct_object_entry * entries, size_t count); -void object_manager_unload_all_objects(); +void * object_manager_get_loaded_object_by_index(size_t index); +void * object_manager_get_loaded_object(const rct_object_entry * entry); +uint8 object_manager_get_loaded_object_entry_index(const void * loadedObject); +void * object_manager_load_object(const rct_object_entry * entry); +void object_manager_unload_objects(const rct_object_entry * entries, size_t count); +void object_manager_unload_all_objects(); #ifdef __cplusplus } diff --git a/src/openrct2/park_load_result_types.h b/src/openrct2/park_load_result_types.h new file mode 100644 index 0000000000..29e3f7c67f --- /dev/null +++ b/src/openrct2/park_load_result_types.h @@ -0,0 +1,33 @@ +#pragma region Copyright (c) 2014-2017 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 + +#ifndef _PARK_LOAD_RESULT_H_ +#define _PARK_LOAD_RESULT_H_ + +#include "object.h" + +typedef struct object_validity_result +{ + uint16 invalid_object_count; + rct_object_entry * * invalid_objects; + +} object_validity_result; + +typedef struct park_load_result { + uint8 error; + object_validity_result* object_validity; +} park_load_result; +#endif diff --git a/src/openrct2/platform/platform.h b/src/openrct2/platform/platform.h index dd174d9d6c..e0c82ff805 100644 --- a/src/openrct2/platform/platform.h +++ b/src/openrct2/platform/platform.h @@ -109,6 +109,7 @@ sint32 platform_enumerate_directories_begin(const utf8 *directory); bool platform_enumerate_directories_next(sint32 handle, utf8 *path); void platform_enumerate_directories_end(sint32 handle); void platform_init_window_icon(); +bool platform_place_string_on_clipboard(utf8* target); // Returns the bitmask of the GetLogicalDrives function for windows, 0 for other systems sint32 platform_get_drives(); diff --git a/src/openrct2/platform/windows.c b/src/openrct2/platform/windows.c index c3a7c88470..da62d39a70 100644 --- a/src/openrct2/platform/windows.c +++ b/src/openrct2/platform/windows.c @@ -804,7 +804,8 @@ datetime64 platform_get_datetime_now_utc() return utcNow; } -utf8* platform_get_username() { +utf8* platform_get_username() +{ static char username[UNLEN + 1]; DWORD usernameLength = UNLEN + 1; diff --git a/src/openrct2/rct1/S4Importer.cpp b/src/openrct2/rct1/S4Importer.cpp index 1d06277299..d4c77d03b5 100644 --- a/src/openrct2/rct1/S4Importer.cpp +++ b/src/openrct2/rct1/S4Importer.cpp @@ -125,16 +125,16 @@ private: uint8 _researchRideTypeUsed[128]; public: - void Load(const utf8 * path) override + park_load_result* Load(const utf8 * path) override { const utf8 * extension = Path::GetExtension(path); if (String::Equals(extension, ".sc4", true)) { - LoadScenario(path); + return LoadScenario(path); } else if (String::Equals(extension, ".sv4", true)) { - LoadSavedGame(path); + return LoadSavedGame(path); } else { @@ -142,22 +142,27 @@ public: } } - void LoadSavedGame(const utf8 * path) override + park_load_result* LoadSavedGame(const utf8 * path) override { auto fs = FileStream(path, FILE_MODE_OPEN); - LoadFromStream(&fs, false); + park_load_result* result = LoadFromStream(&fs, false); _s4Path = path; + return result; } - void LoadScenario(const utf8 * path) override + park_load_result* LoadScenario(const utf8 * path) override { auto fs = FileStream(path, FILE_MODE_OPEN); - LoadFromStream(&fs, true); + park_load_result* result = LoadFromStream(&fs, true); _s4Path = path; + return result; } - void LoadFromStream(IStream * stream, bool isScenario) override + park_load_result* LoadFromStream(IStream * stream, bool isScenario) override { + park_load_result* result = Memory::Allocate(sizeof(park_load_result)); + result->error = PARK_LOAD_ERROR_UNKNOWN; + size_t dataSize = stream->GetLength() - stream->GetPosition(); std::unique_ptr data = std::unique_ptr(stream->ReadArray(dataSize)); std::unique_ptr decodedData = std::unique_ptr(Memory::Allocate(sizeof(rct1_s4))); @@ -182,6 +187,8 @@ public: { throw Exception("Unable to decode park."); } + result->error = PARK_LOAD_ERROR_NONE; + return result; } void Import() override diff --git a/src/openrct2/rct2/S6Importer.cpp b/src/openrct2/rct2/S6Importer.cpp index e98599ad32..9ce3c36827 100644 --- a/src/openrct2/rct2/S6Importer.cpp +++ b/src/openrct2/rct2/S6Importer.cpp @@ -24,6 +24,7 @@ #include "../network/network.h" #include "../object/ObjectManager.h" #include "../object/ObjectRepository.h" +#include "../object/ObjectManager.h" #include "../ParkImporter.h" #include "../rct12/SawyerChunkReader.h" #include "../rct12/SawyerEncoding.h" @@ -80,16 +81,16 @@ public: Memory::Set(&_s6, 0, sizeof(_s6)); } - void Load(const utf8 * path) override + park_load_result* Load(const utf8 * path) override { const utf8 * extension = Path::GetExtension(path); if (String::Equals(extension, ".sc6", true)) { - LoadScenario(path); + return LoadScenario(path); } else if (String::Equals(extension, ".sv6", true)) { - LoadSavedGame(path); + return LoadSavedGame(path); } else { @@ -97,22 +98,29 @@ public: } } - void LoadSavedGame(const utf8 * path) override + park_load_result* LoadSavedGame(const utf8 * path) override { auto fs = FileStream(path, FILE_MODE_OPEN); - LoadFromStream(&fs, false); + park_load_result* result = LoadFromStream(&fs, false); _s6Path = path; + + return result; } - void LoadScenario(const utf8 * path) override + park_load_result* LoadScenario(const utf8 * path) override { auto fs = FileStream(path, FILE_MODE_OPEN); - LoadFromStream(&fs, true); + park_load_result* result = LoadFromStream(&fs, true); _s6Path = path; + + return result; } - void LoadFromStream(IStream * stream, bool isScenario) override + park_load_result* LoadFromStream(IStream * stream, bool isScenario) override { + park_load_result* result = Memory::Allocate(sizeof(park_load_result)); + result->error = PARK_LOAD_ERROR_UNKNOWN; + if (isScenario && !gConfigGeneral.allow_loading_with_incorrect_checksum && !SawyerEncoding::ValidateChecksum(stream)) { throw IOException("Invalid checksum."); @@ -166,6 +174,19 @@ public: chunkReader.ReadChunk(&_s6.map_elements, sizeof(_s6.map_elements)); chunkReader.ReadChunk(&_s6.next_free_map_element_pointer_index, 3048816); } + + object_validity_result* object_result = _objectManager->GetInvalidObjects(_s6.objects); + + 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; + } + return result; } bool GetDetails(scenario_index_entry * dst) override @@ -418,23 +439,28 @@ IParkImporter * ParkImporter::CreateS6(IObjectRepository * objectRepository, IOb extern "C" { - bool game_load_sv6_path(const char * path) + park_load_result* game_load_sv6_path(const char * path) { - bool result = false; + park_load_result* result = {}; auto s6Importer = new S6Importer(GetObjectRepository(), GetObjectManager()); try { - s6Importer->LoadSavedGame(path); - s6Importer->Import(); + result = s6Importer->LoadSavedGame(path); - game_fix_save_vars(); - sprite_position_tween_reset(); - result = true; + // We mustn't import if there's something + // wrong with the park data + if (result->error == PARK_LOAD_ERROR_NONE) + { + s6Importer->Import(); + + game_fix_save_vars(); + sprite_position_tween_reset(); + } } catch (const ObjectLoadException &) { gErrorType = ERROR_TYPE_FILE_LOAD; - gErrorStringId = STR_GAME_SAVE_FAILED; + gErrorStringId = STR_FILE_CONTAINS_INVALID_DATA; } catch (const IOException &) { @@ -448,8 +474,11 @@ extern "C" } delete s6Importer; - gScreenAge = 0; - gLastAutoSaveUpdate = AUTOSAVE_PAUSE; + if (result->error == PARK_LOAD_ERROR_NONE) + { + gScreenAge = 0; + gLastAutoSaveUpdate = AUTOSAVE_PAUSE; + } return result; } @@ -458,18 +487,20 @@ extern "C" * rct2: 0x00676053 * scenario (ebx) */ - sint32 scenario_load(const char * path) + park_load_result *scenario_load(const char * path) { - bool result = false; + park_load_result* result = {}; auto s6Importer = new S6Importer(GetObjectRepository(), GetObjectManager()); try { - s6Importer->LoadScenario(path); - s6Importer->Import(); + result = s6Importer->LoadScenario(path); + if (result->error != PARK_LOAD_ERROR_NONE) + { + s6Importer->Import(); - game_fix_save_vars(); - sprite_position_tween_reset(); - result = true; + game_fix_save_vars(); + sprite_position_tween_reset(); + } } catch (const ObjectLoadException &) { @@ -487,9 +518,11 @@ extern "C" gErrorStringId = STR_FILE_CONTAINS_INVALID_DATA; } delete s6Importer; - - gScreenAge = 0; - gLastAutoSaveUpdate = AUTOSAVE_PAUSE; + if (result->error != PARK_LOAD_ERROR_NONE) + { + gScreenAge = 0; + gLastAutoSaveUpdate = AUTOSAVE_PAUSE; + } return result; } } diff --git a/src/openrct2/scenario/scenario.c b/src/openrct2/scenario/scenario.c index 939fec7261..20a4b8abc3 100644 --- a/src/openrct2/scenario/scenario.c +++ b/src/openrct2/scenario/scenario.c @@ -93,9 +93,19 @@ sint32 scenario_load_and_play_from_path(const char *path) window_close_construction_windows(); uint32 extension = get_file_extension_type(path); + park_load_result* result; if (extension == FILE_EXTENSION_SC6) { - if (!scenario_load(path)) + 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; + } } else if (extension == FILE_EXTENSION_SC4) { if (!rct1_load_scenario(path)) diff --git a/src/openrct2/scenario/scenario.h b/src/openrct2/scenario/scenario.h index fa76197933..a2000c9bf9 100644 --- a/src/openrct2/scenario/scenario.h +++ b/src/openrct2/scenario/scenario.h @@ -21,6 +21,7 @@ #include "../management/finance.h" #include "../management/research.h" #include "../object.h" +#include "../park_load_result_types.h" #include "../rct12.h" #include "../rct2.h" #include "../rct2/addresses.h" @@ -389,7 +390,7 @@ extern uint32 gLastAutoSaveUpdate; extern const char *_scenarioFileName; -sint32 scenario_load(const char *path); +park_load_result *scenario_load(const char *path); sint32 scenario_load_and_play_from_path(const char *path); void scenario_begin(); void scenario_update(); diff --git a/src/openrct2/ui/DummyUiContext.cpp b/src/openrct2/ui/DummyUiContext.cpp index 906bf0f6aa..3b3dab1d67 100644 --- a/src/openrct2/ui/DummyUiContext.cpp +++ b/src/openrct2/ui/DummyUiContext.cpp @@ -79,6 +79,9 @@ namespace OpenRCT2 { namespace Ui // Misc bool ReadBMP(void * * outPixels, uint32 * outWidth, uint32 * outHeight, const std::string &path) override { return false; } + + // Clipboard + bool SetClipboardText(const utf8* target) override { return false; } }; IUiContext * CreateDummyUiContext() diff --git a/src/openrct2/ui/UiContext.h b/src/openrct2/ui/UiContext.h index 7ebf92ec35..49fe1ec225 100644 --- a/src/openrct2/ui/UiContext.h +++ b/src/openrct2/ui/UiContext.h @@ -136,6 +136,9 @@ namespace OpenRCT2 // HACK: This should either be implemented ourselves in libopenrct2 // or the mapgen height map code is moved to libopenrct2ui. virtual bool ReadBMP(void * * outPixels, uint32 * outWidth, uint32 * outHeight, const std::string &path) abstract; + + // Clipboard + virtual bool SetClipboardText(const utf8* target) abstract; }; IUiContext * CreateDummyUiContext(); diff --git a/src/openrct2/windows/object_load_error.c b/src/openrct2/windows/object_load_error.c new file mode 100644 index 0000000000..659c368cc7 --- /dev/null +++ b/src/openrct2/windows/object_load_error.c @@ -0,0 +1,328 @@ +#pragma region Copyright (c) 2014-2017 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 "../interface/widget.h" +#include "../interface/window.h" +#include "../localisation/localisation.h" +#include "../object/ObjectManager.h" +#include "../object.h" +#include "../platform/platform.h" +#include "../sprites.h" +#include "../util/util.h" + +enum WINDOW_OBJECT_LOAD_ERROR_WIDGET_IDX { + WIDX_BACKGROUND, + WIDX_TITLE, + WIDX_CLOSE, + WIDX_COLUMN_OBJECT_NAME, + WIDX_COLUMN_OBJECT_TYPE, + WIDX_SCROLL, + WIDX_COPY_CURRENT, + WIDX_COPY_ALL +}; + +#define WW 400 +#define WH 400 +#define LIST_ITEM_HEIGHT 10 + +rct_widget window_object_load_error_widgets[] = { + { WWT_FRAME, 0, 0, WW - 1, 0, WH - 1, STR_NONE, STR_NONE }, // Background + { WWT_CAPTION, 0, 1, WW - 2, 1, 14, STR_OBJECT_LOAD_ERROR_TITLE, STR_WINDOW_TITLE_TIP }, // Title bar + { WWT_CLOSEBOX, 0, WW - 13, WW - 3, 2, 13, STR_CLOSE_X, STR_CLOSE_WINDOW_TIP }, // Close button + { WWT_13, 0, 4, (WW - 5) / 3, 57, 68, STR_OBJECT_NAME, STR_NONE }, // 'Object name' header + { WWT_13, 0, (WW - 5) / 3 + 1, WW - 5 - 1, 57, 68, STR_OBJECT_TYPE, STR_NONE }, // 'Object type' header + { WWT_SCROLL, 0, 4, WW - 5, 68, WH - 40, SCROLL_VERTICAL, STR_NONE }, // Scrollable list area + { WWT_CLOSEBOX, 0, 20, 200, WW - 32, WW - 12, STR_COPY_SELECTED, STR_NONE }, // Copy selected btn + { WWT_CLOSEBOX, 0, 200, 370, WW - 32, WW - 12, STR_COPY_ALL, STR_NONE }, // Copy all btn + { WIDGETS_END }, +}; + +static rct_string_id get_object_type_string(const rct_object_entry *entry); +static void window_object_load_error_close(rct_window *w); +static void window_object_load_error_update(rct_window *w); +static void window_object_load_error_mouseup(rct_window *w, rct_widgetindex widgetIndex); +static void window_object_load_error_scrollgetsize(rct_window *w, sint32 scrollIndex, sint32 *width, sint32 *height); +static void window_object_load_error_scrollmouseover(rct_window *w, sint32 scrollIndex, sint32 x, sint32 y); +static void window_object_load_error_scrollmousedown(rct_window *w, sint32 scrollIndex, sint32 x, sint32 y); +static void window_object_load_error_paint(rct_window *w, rct_drawpixelinfo *dpi); +static void window_object_load_error_scrollpaint(rct_window *w, rct_drawpixelinfo *dpi, sint32 scrollIndex); + +static rct_window_event_list window_object_load_error_events = { + window_object_load_error_close, + window_object_load_error_mouseup, + NULL, + NULL, + NULL, + NULL, + window_object_load_error_update, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + window_object_load_error_scrollgetsize, + window_object_load_error_scrollmousedown, + NULL, + window_object_load_error_scrollmouseover, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + window_object_load_error_paint, + window_object_load_error_scrollpaint +}; + +rct_object_entry * * invalid_entries = NULL; +sint32 highlighted_index = -1; +utf8* file_path = NULL; + +/** +* Returns an rct_string_id that represents an rct_object_entry's type. +* +* Could possibly be moved out of the window file if other +* uses exist and a suitable location is found. +*/ +static rct_string_id get_object_type_string(const rct_object_entry *entry) +{ + rct_string_id result; + uint8 objectType = entry->flags & 0x0F; + switch (objectType) { + case OBJECT_TYPE_RIDE: + result = STR_OBJECT_SELECTION_RIDE_VEHICLES_ATTRACTIONS; + break; + case OBJECT_TYPE_SMALL_SCENERY: + result = STR_OBJECT_SELECTION_SMALL_SCENERY; + break; + case OBJECT_TYPE_LARGE_SCENERY: + result = STR_OBJECT_SELECTION_LARGE_SCENERY; + break; + case OBJECT_TYPE_WALLS: + result = STR_OBJECT_SELECTION_WALLS_FENCES; + break; + case OBJECT_TYPE_BANNERS: + result = STR_OBJECT_SELECTION_PATH_SIGNS; + break; + case OBJECT_TYPE_PATHS: + result = STR_OBJECT_SELECTION_FOOTPATHS; + break; + case OBJECT_TYPE_PATH_BITS: + result = STR_OBJECT_SELECTION_PATH_EXTRAS; + break; + case OBJECT_TYPE_SCENERY_SETS: + result = STR_OBJECT_SELECTION_SCENERY_GROUPS; + break; + case OBJECT_TYPE_PARK_ENTRANCE: + result = STR_OBJECT_SELECTION_PARK_ENTRANCE; + break; + case OBJECT_TYPE_WATER: + result = STR_OBJECT_SELECTION_WATER; + break; + case OBJECT_TYPE_SCENARIO_TEXT: + result = STR_OBJECT_SELECTION_SCENARIO_DESCRIPTION; + break; + default: + result = STR_UNKNOWN_OBJECT_TYPE; + } + return result; +} + +/** +* Returns a newline-separated string listing all object names. +* Used for placing all names on the clipboard. +*/ +static utf8* combine_object_names(rct_window *w) +{ + if (w->no_list_items > OBJECT_ENTRY_COUNT || w->no_list_items == 0) { + // Something's gone wrong, this shouldn't happen + // We don't want to allocate stupidly large amounts of memory + // for no reason, so bail + return NULL; + } + utf8* buffer; + + // No system has a newline over 2 characters + size_t line_sep_len = strnlen(PLATFORM_NEWLINE, 2); + size_t buffer_len = (w->no_list_items * (8 + line_sep_len)) + 1; + buffer = (utf8*)malloc(buffer_len); + buffer[0] = '\0'; + size_t cur_len = 0; + for (uint16 i = 0; i < w->no_list_items; i++) { + cur_len += (8 + line_sep_len); + assert(cur_len < buffer_len); + strncat(buffer, invalid_entries[i]->name, 8); + strncat(buffer, PLATFORM_NEWLINE, line_sep_len); + } + return buffer; +} + +rct_window *window_object_load_error_open(utf8* path, object_validity_result* result) +{ + rct_window* window; + + invalid_entries = result->invalid_objects; + + // Check if window is already open + window = window_bring_to_front_by_class(WC_OBJECT_LOAD_ERROR); + if (window == NULL) { + window = window_create_centred( + WW, + WH, + &window_object_load_error_events, + WC_OBJECT_LOAD_ERROR, + WF_STICK_TO_FRONT + ); + + window->widgets = window_object_load_error_widgets; + window->enabled_widgets = (1 << WIDX_CLOSE) | (1 << WIDX_COPY_CURRENT) | (1 << WIDX_COPY_ALL); + + window_init_scroll_widgets(window); + window->colours[0] = COLOUR_LIGHT_BLUE; + window->colours[1] = COLOUR_LIGHT_BLUE; + window->colours[2] = COLOUR_LIGHT_BLUE; + } + + // Refresh list items and path + window->no_list_items = result->invalid_object_count; + file_path = path; + + window_invalidate(window); + return window; +} + +static void window_object_load_error_close(rct_window *w) +{ + SafeFree(invalid_entries); +} + +static void window_object_load_error_update(rct_window *w) +{ + // Check if the mouse is hovering over the list + if (!widget_is_highlighted(w, WIDX_SCROLL)) { + highlighted_index = -1; + widget_invalidate(w, WIDX_SCROLL); + } +} + +static void window_object_load_error_mouseup(rct_window *w, rct_widgetindex widgetIndex) +{ + utf8* selected_name; + utf8* combined_list; + switch (widgetIndex) { + case WIDX_CLOSE: + window_close(w); + break; + case WIDX_COPY_CURRENT: + if (w->selected_list_item > -1) { + selected_name = strndup(invalid_entries[w->selected_list_item]->name, 8); + platform_place_string_on_clipboard(selected_name); + SafeFree(selected_name); + } + break; + case WIDX_COPY_ALL: + combined_list = combine_object_names(w); + platform_place_string_on_clipboard(combined_list); + SafeFree(combined_list); + break; + } +} + +static void window_object_load_error_scrollmouseover(rct_window *w, sint32 scrollIndex, sint32 x, sint32 y) +{ + // Highlight item that the cursor is over, or remove highlighting if none + sint32 selected_item; + selected_item = y / 10; + if (selected_item < 0 || selected_item >= w->no_list_items) + highlighted_index = -1; + else + highlighted_index = selected_item; + + widget_invalidate(w, WIDX_SCROLL); +} + +static void window_object_load_error_select_element_from_list(rct_window *w, sint32 index) +{ + if (index < 0 || index > w->no_list_items) { + w->selected_list_item = -1; + } + else { + w->selected_list_item = index; + } +} + +static void window_object_load_error_scrollmousedown(rct_window *w, sint32 scrollIndex, sint32 x, sint32 y) +{ + sint32 selected_item; + selected_item = y / 10; + window_object_load_error_select_element_from_list(w, selected_item); +} + +static void window_object_load_error_scrollgetsize(rct_window *w, sint32 scrollIndex, sint32 *width, sint32 *height) +{ + *height = w->no_list_items * 10; +} + +static void window_object_load_error_paint(rct_window *w, rct_drawpixelinfo *dpi) +{ + window_draw_widgets(w, dpi); + + // Draw explanatory message + set_format_arg(0, rct_string_id, STR_OBJECT_ERROR_WINDOW_EXPLANATION); + gfx_draw_string_left_wrapped(dpi, gCommonFormatArgs, w->x + 5, w->y + 18, WW-10, STR_BLACK_STRING, COLOUR_BLACK); + + // Draw file name + set_format_arg(0, rct_string_id, STR_OBJECT_ERROR_WINDOW_FILE); + set_format_arg(2, utf8*, file_path); + gfx_draw_string_left_clipped(dpi, STR_BLACK_STRING, gCommonFormatArgs, COLOUR_BLACK, w->x + 5, w->y + 43, WW-5); +} + +static void window_object_load_error_scrollpaint(rct_window *w, rct_drawpixelinfo *dpi, sint32 scrollIndex) +{ + + gfx_fill_rect(dpi, dpi->x, dpi->y, dpi->x + dpi->width - 1, dpi->y + dpi->height - 1, ColourMapA[w->colours[1]].mid_light); + const sint32 list_width = w->widgets[WIDX_SCROLL].right - w->widgets[WIDX_SCROLL].left; + + for (sint32 i = 0; i < w->no_list_items; i++) { + sint32 y = i * 10; + if (y > dpi->y + dpi->height) + break; + + if (y + 10 < dpi->y) + continue; + + // If hovering over item, change the color and fill the backdrop. + if (i == w->selected_list_item) // Currently selected element + gfx_fill_rect(dpi, 0, y, list_width, y + LIST_ITEM_HEIGHT - 1, ColourMapA[w->colours[1]].darker | 0x1000000); + else if (i == highlighted_index) // Hovering + gfx_fill_rect(dpi, 0, y, list_width, y + LIST_ITEM_HEIGHT - 1, ColourMapA[w->colours[1]].mid_dark | 0x1000000); + else if ((i & 1) != 0) // odd / even check + gfx_fill_rect(dpi, 0, y, list_width, y + LIST_ITEM_HEIGHT - 1, ColourMapA[w->colours[1]].lighter | 0x1000000); + + // Draw the actual object entry's name... + gfx_draw_string(dpi, strndup(invalid_entries[i]->name, 8), COLOUR_DARK_GREEN, 5, y); + + // ... and type + rct_string_id type = get_object_type_string(invalid_entries[i]); + gfx_draw_string_left(dpi, type, NULL, COLOUR_DARK_GREEN, (WW - 5) / 3 + 1, y); + } + + +}