diff --git a/OpenRCT2.xcodeproj/project.pbxproj b/OpenRCT2.xcodeproj/project.pbxproj index e093ee144f..d9147e6075 100644 --- a/OpenRCT2.xcodeproj/project.pbxproj +++ b/OpenRCT2.xcodeproj/project.pbxproj @@ -124,6 +124,7 @@ C650B21A1CCABBDD00B4D91C /* tables.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C650B2171CCABBDD00B4D91C /* tables.cpp */; }; C650B21C1CCABC4400B4D91C /* ConvertCommand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C650B21B1CCABC4400B4D91C /* ConvertCommand.cpp */; }; C6575A371D46AFBA00C3E79F /* debug_paint.c in Sources */ = {isa = PBXBuildFile; fileRef = C6575A361D46AFBA00C3E79F /* debug_paint.c */; }; + C6834A111DFDE8E300CE933A /* interop.c in Sources */ = {isa = PBXBuildFile; fileRef = C6834A0F1DFDE8E300CE933A /* interop.c */; }; C686F8AC1CDBC37E009F9BFC /* banner.c in Sources */ = {isa = PBXBuildFile; fileRef = C686F8981CDBC37E009F9BFC /* banner.c */; }; C686F8AD1CDBC37E009F9BFC /* entrance.c in Sources */ = {isa = PBXBuildFile; fileRef = C686F8991CDBC37E009F9BFC /* entrance.c */; }; C686F8AE1CDBC37E009F9BFC /* fence.c in Sources */ = {isa = PBXBuildFile; fileRef = C686F89A1CDBC37E009F9BFC /* fence.c */; }; @@ -544,6 +545,8 @@ C650B2181CCABBDD00B4D91C /* Tables.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = Tables.h; sourceTree = ""; usesTabs = 0; }; C650B21B1CCABC4400B4D91C /* ConvertCommand.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ConvertCommand.cpp; sourceTree = ""; usesTabs = 0; }; C6575A361D46AFBA00C3E79F /* debug_paint.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = debug_paint.c; sourceTree = ""; }; + C6834A0F1DFDE8E300CE933A /* interop.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = interop.c; sourceTree = ""; usesTabs = 1; }; + C6834A101DFDE8E300CE933A /* interop.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = interop.h; sourceTree = ""; }; C686F8981CDBC37E009F9BFC /* banner.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = banner.c; sourceTree = ""; }; C686F8991CDBC37E009F9BFC /* entrance.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = entrance.c; sourceTree = ""; }; C686F89A1CDBC37E009F9BFC /* fence.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fence.c; sourceTree = ""; }; @@ -1409,6 +1412,8 @@ C6B5A7CF1CDFE4CB00C9C006 /* rct2 */ = { isa = PBXGroup; children = ( + C6834A0F1DFDE8E300CE933A /* interop.c */, + C6834A101DFDE8E300CE933A /* interop.h */, C6B5A7D01CDFE4CB00C9C006 /* S6Exporter.cpp */, C6B5A7D11CDFE4CB00C9C006 /* S6Exporter.h */, C6B5A7D21CDFE4CB00C9C006 /* S6Importer.cpp */, @@ -2842,6 +2847,7 @@ C686F90E1CDBC3B7009F9BFC /* corkscrew_roller_coaster.c in Sources */, D44272A61CC81B3200D84D28 /* scenery.c in Sources */, C686F9111CDBC3B7009F9BFC /* heartline_twister_coaster.c in Sources */, + C6834A111DFDE8E300CE933A /* interop.c in Sources */, C686F9231CDBC3B7009F9BFC /* steeplechase.c in Sources */, D44271FE1CC81B3200D84D28 /* config.c in Sources */, D44272871CC81B3200D84D28 /* staff_list.c in Sources */, diff --git a/openrct2.vcxproj b/openrct2.vcxproj index 0f0b64b570..bd40bc245e 100644 --- a/openrct2.vcxproj +++ b/openrct2.vcxproj @@ -228,6 +228,7 @@ + @@ -544,6 +545,7 @@ + diff --git a/src/hook.h b/src/hook.h index c4dbdf5758..d4cee25f61 100644 --- a/src/hook.h +++ b/src/hook.h @@ -19,6 +19,8 @@ #ifndef NO_RCT2 +#include "common.h" + enum { X86_FLAG_CARRY = 1 << 0, X86_FLAG_PARITY = 1 << 2, diff --git a/src/openrct2.c b/src/openrct2.c index 9af19a5ffb..5a8cf1ac93 100644 --- a/src/openrct2.c +++ b/src/openrct2.c @@ -19,7 +19,6 @@ #include "config.h" #include "editor.h" #include "game.h" -#include "hook.h" #include "interface/chat.h" #include "interface/themes.h" #include "interface/window.h" @@ -32,31 +31,17 @@ #include "openrct2.h" #include "platform/crash.h" #include "platform/platform.h" +#include "rct2/interop.h" #include "ride/ride.h" #include "title.h" -#include "util/sawyercoding.h" #include "util/util.h" #include "version.h" #include "world/mapgen.h" -#if defined(__unix__) || defined(__MACOSX__) -#include -#include -#include -#include -#include -#include -#endif // defined(__unix__) || defined(__MACOSX__) +#define UPDATE_TIME_MS 25 // (1000 / 40fps) = 25ms int gExitCode; -#if defined(USE_MMAP) && (defined(__unix__) || defined(__MACOSX__)) && !defined(NO_RCT2) - static int fdData = -1; -#endif -#if defined(__unix__) && !defined(NO_RCT2) - static char * segments = (char *)(GOOD_PLACE_FOR_DATA_SEGMENT); -#endif - int gOpenRCT2StartupAction = STARTUP_ACTION_TITLE; utf8 gOpenRCT2StartupActionPath[512] = { 0 }; utf8 gExePath[MAX_PATH]; @@ -79,11 +64,7 @@ EVP_MD_CTX *gHashCTX = NULL; /** If set, will end the OpenRCT2 game loop. Intentially private to this module so that the flag can not be set back to 0. */ int _finished; -// Used for object movement tweening -static rct_xyz16 _spritelocations1[MAX_SPRITES], _spritelocations2[MAX_SPRITES]; - static void openrct2_loop(); -static void openrct2_setup_rct2_hooks(); void openrct2_write_full_version_info(utf8 *buffer, size_t bufferSize) { @@ -112,81 +93,12 @@ void openrct2_write_full_version_info(utf8 *buffer, size_t bufferSize) #endif } -static void openrct2_copy_files_over(const utf8 *originalDirectory, const utf8 *newDirectory, const utf8 *extension) -{ - utf8 *ch, filter[MAX_PATH], oldPath[MAX_PATH], newPath[MAX_PATH]; - int fileEnumHandle; - file_info fileInfo; - - if (!platform_ensure_directory_exists(newDirectory)) { - log_error("Could not create directory %s.", newDirectory); - return; - } - - // Create filter path - safe_strcpy(filter, originalDirectory, sizeof(filter)); - ch = strchr(filter, '*'); - if (ch != NULL) - *ch = 0; - safe_strcat_path(filter, "*", sizeof(filter)); - path_append_extension(filter, extension, sizeof(filter)); - - fileEnumHandle = platform_enumerate_files_begin(filter); - while (platform_enumerate_files_next(fileEnumHandle, &fileInfo)) { - safe_strcpy(newPath, newDirectory, sizeof(newPath)); - safe_strcat_path(newPath, fileInfo.path, sizeof(newPath)); - - safe_strcpy(oldPath, originalDirectory, sizeof(oldPath)); - ch = strchr(oldPath, '*'); - if (ch != NULL) - *ch = 0; - safe_strcat_path(oldPath, fileInfo.path, sizeof(oldPath)); - - if (!platform_file_exists(newPath)) - platform_file_copy(oldPath, newPath, false); - } - platform_enumerate_files_end(fileEnumHandle); - - fileEnumHandle = platform_enumerate_directories_begin(originalDirectory); - while (platform_enumerate_directories_next(fileEnumHandle, filter)) { - safe_strcpy(newPath, newDirectory, sizeof(newPath)); - safe_strcat_path(newPath, filter, sizeof(newPath)); - - safe_strcpy(oldPath, originalDirectory, MAX_PATH); - ch = strchr(oldPath, '*'); - if (ch != NULL) - *ch = 0; - safe_strcat_path(oldPath, filter, sizeof(oldPath)); - - if (!platform_ensure_directory_exists(newPath)) { - log_error("Could not create directory %s.", newPath); - return; - } - openrct2_copy_files_over(oldPath, newPath, extension); - } - platform_enumerate_directories_end(fileEnumHandle); -} - static void openrct2_set_exe_path() { platform_get_exe_path(gExePath, sizeof(gExePath)); log_verbose("Setting exe path to %s", gExePath); } -/** - * Copy saved games and landscapes to user directory - */ -static void openrct2_copy_original_user_files_over() -{ - utf8 path[MAX_PATH]; - - platform_get_user_directory(path, "save", sizeof(path)); - openrct2_copy_files_over((utf8*)gRCT2AddressSavedGamesPath, path, ".sv6"); - - platform_get_user_directory(path, "landscape", sizeof(path)); - openrct2_copy_files_over((utf8*)gRCT2AddressLandscapesPath, path, ".sc6"); -} - bool openrct2_initialise() { utf8 userPath[MAX_PATH]; @@ -206,7 +118,7 @@ bool openrct2_initialise() crash_init(); - if (!openrct2_setup_rct2_segment()) { + if (!rct2_interop_setup_segment()) { log_fatal("Unable to load RCT2 data sector"); return false; } @@ -263,14 +175,14 @@ bool openrct2_initialise() title_sequences_set_default(); title_sequences_load_presets(); - openrct2_setup_rct2_hooks(); + rct2_interop_setup_hooks(); if (!rct2_init()) return false; chat_init(); - openrct2_copy_original_user_files_over(); + rct2_copy_original_user_files_over(); return true; } @@ -357,27 +269,10 @@ void openrct2_dispose() #ifndef DISABLE_NETWORK EVP_MD_CTX_destroy(gHashCTX); #endif // DISABLE_NETWORK -#if defined(USE_MMAP) && (defined(__unix__) || defined(__MACOSX__)) && !defined(NO_RCT2) - munmap(segments, 12079104); - close(fdData); -#endif + rct2_interop_dispose(); platform_free(); } -/** - * Determines whether its worth tweening a sprite or not when frame smoothing is on. - */ -static bool sprite_should_tween(rct_sprite *sprite) -{ - switch (sprite->unknown.linked_list_type_offset >> 1) { - case SPRITE_LIST_VEHICLE: - case SPRITE_LIST_PEEP: - case SPRITE_LIST_UNKNOWN: - return true; - } - return false; -} - /** * Run the main game loop until the finished flag is set at 40fps (25ms interval). */ @@ -398,44 +293,33 @@ static void openrct2_loop() if (uncapTick == 0) { // Reset sprite locations uncapTick = SDL_GetTicks(); - openrct2_reset_object_tween_locations(); + sprite_position_tween_reset(); } // Limit number of updates per loop (any long pauses or debugging can make this update for a very long time) - if (currentTick - uncapTick > 25 * 60) { - uncapTick = currentTick - 25 - 1; + if (currentTick - uncapTick > UPDATE_TIME_MS * 60) { + uncapTick = currentTick - UPDATE_TIME_MS - 1; } platform_process_messages(); - while (uncapTick <= currentTick && currentTick - uncapTick > 25) { + while (uncapTick <= currentTick && currentTick - uncapTick > UPDATE_TIME_MS) { // Get the original position of each sprite - store_sprite_locations(_spritelocations1); + sprite_position_tween_store_a(); // Update the game so the sprite positions update rct2_update(); // Get the next position of each sprite - store_sprite_locations(_spritelocations2); + sprite_position_tween_store_b(); - uncapTick += 25; + uncapTick += UPDATE_TIME_MS; } // Tween the position of each sprite from the last position to the new position based on the time between the last // tick and the next tick. - float nudge = 1 - ((float)(currentTick - uncapTick) / 25); - for (uint16 i = 0; i < MAX_SPRITES; i++) { - if (!sprite_should_tween(get_sprite(i))) - continue; - - sprite_set_coordinates( - _spritelocations2[i].x + (sint16)((_spritelocations1[i].x - _spritelocations2[i].x) * nudge), - _spritelocations2[i].y + (sint16)((_spritelocations1[i].y - _spritelocations2[i].y) * nudge), - _spritelocations2[i].z + (sint16)((_spritelocations1[i].z - _spritelocations2[i].z) * nudge), - get_sprite(i) - ); - invalidate_sprite_2(get_sprite(i)); - } + float nudge = 1 - ((float)(currentTick - uncapTick) / UPDATE_TIME_MS); + sprite_position_tween_all(nudge); platform_draw(); @@ -445,21 +329,14 @@ static void openrct2_loop() secondTick = SDL_GetTicks(); } - // Restore the real positions of the sprites so they aren't left at the mid-tween positions - for (uint16 i = 0; i < MAX_SPRITES; i++) { - if (!sprite_should_tween(get_sprite(i))) - continue; - - invalidate_sprite_2(get_sprite(i)); - sprite_set_coordinates(_spritelocations2[i].x, _spritelocations2[i].y, _spritelocations2[i].z, get_sprite(i)); - } + sprite_position_tween_restore(); } else { uncapTick = 0; currentTick = SDL_GetTicks(); ticksElapsed = currentTick - lastTick; - if (ticksElapsed < 25) { - SDL_Delay(25 - ticksElapsed); - lastTick += 25; + if (ticksElapsed < UPDATE_TIME_MS) { + SDL_Delay(UPDATE_TIME_MS - ticksElapsed); + lastTick += UPDATE_TIME_MS; } else { lastTick = currentTick; } @@ -482,182 +359,3 @@ void openrct2_finish() { _finished = 1; } - -void openrct2_reset_object_tween_locations() -{ - for (uint16 i = 0; i < MAX_SPRITES; i++) { - _spritelocations1[i].x = _spritelocations2[i].x = get_sprite(i)->unknown.x; - _spritelocations1[i].y = _spritelocations2[i].y = get_sprite(i)->unknown.y; - _spritelocations1[i].z = _spritelocations2[i].z = get_sprite(i)->unknown.z; - } -} - -static void openrct2_get_segment_data_path(char * buffer, size_t bufferSize) -{ - platform_get_exe_path(buffer, bufferSize); - safe_strcat_path(buffer, "openrct2_data", bufferSize); -} - -/** - * Loads RCT2's data model and remaps the addresses. - * @returns true if the data integrity check succeeded, otherwise false. - */ -bool openrct2_setup_rct2_segment() -{ - // OpenRCT2 on Linux and macOS is wired to have the original Windows PE sections loaded - // necessary. Windows does not need to do this as OpenRCT2 runs as a DLL loaded from the Windows PE. - int len = 0x01429000 - 0x8a4000; // 0xB85000, 12079104 bytes or around 11.5MB - int err = 0; - // in some configurations err and len may be unused - UNUSED(err); - UNUSED(len); -#if defined(USE_MMAP) && (defined(__unix__) || defined(__MACOSX__)) && !defined(NO_RCT2) - #define RDATA_OFFSET 0x004A4000 - #define DATASEG_OFFSET 0x005E2000 - - // Using PE-bear I was able to figure out all the needed addresses to be filled. - // There are three sections to be loaded: .rdata, .data and .text, plus another - // one to be mapped: DATASEG. - // Out of the three, two can simply be mmapped into memory, while the third one, - // .data has a virtual size which is much completely different to its file size - // (even when taking page-alignment into consideration) - // - // The sections are as follows (dump from gdb) - // [0] 0x401000->0x6f7000 at 0x00001000: .text ALLOC LOAD READONLY CODE HAS_CONTENTS - // [1] 0x6f7000->0x8a325d at 0x002f7000: CODESEG ALLOC LOAD READONLY CODE HAS_CONTENTS - // [2] 0x8a4000->0x9a5894 at 0x004a4000: .rdata ALLOC LOAD DATA HAS_CONTENTS - // [3] 0x9a6000->0x9e2000 at 0x005a6000: .data ALLOC LOAD DATA HAS_CONTENTS - // [4] 0x1428000->0x14282bc at 0x005e2000: DATASEG ALLOC LOAD DATA HAS_CONTENTS - // [5] 0x1429000->0x1452000 at 0x005e3000: .cms_t ALLOC LOAD READONLY CODE HAS_CONTENTS - // [6] 0x1452000->0x14aaf3e at 0x0060c000: .cms_d ALLOC LOAD DATA HAS_CONTENTS - // [7] 0x14ab000->0x14ac58a at 0x00665000: .idata ALLOC LOAD READONLY DATA HAS_CONTENTS - // [8] 0x14ad000->0x14b512f at 0x00667000: .rsrc ALLOC LOAD DATA HAS_CONTENTS - // - // .data section, however, has virtual size of 0xA81C3C, and so - // 0x9a6000 + 0xA81C3C = 0x1427C3C, which after alignment to page size becomes - // 0x1428000, which can be seen as next section, DATASEG - // - // The data is now loaded into memory with a linker script, which proves to - // be more reliable, as mallocs that happen before we reach segment setup - // could have already taken the space we need. - - // TODO: UGLY, UGLY HACK! - //off_t file_size = 6750208; - - utf8 segmentDataPath[MAX_PATH]; - openrct2_get_segment_data_path(segmentDataPath, sizeof(segmentDataPath)); - fdData = open(segmentDataPath, O_RDONLY); - if (fdData < 0) - { - log_fatal("failed to load openrct2_data"); - exit(1); - } - log_warning("%p", GOOD_PLACE_FOR_DATA_SEGMENT); - segments = mmap((void *)(GOOD_PLACE_FOR_DATA_SEGMENT), len, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_PRIVATE, fdData, 0); - log_warning("%p", segments); - if ((uintptr_t)segments != GOOD_PLACE_FOR_DATA_SEGMENT) { - perror("mmap"); - return false; - } -#endif // defined(USE_MMAP) && (defined(__unix__) || defined(__MACOSX__)) - -#if defined(__unix__) && !defined(NO_RCT2) - int pageSize = getpagesize(); - int numPages = (len + pageSize - 1) / pageSize; - unsigned char *dummy = malloc(numPages); - - err = mincore((void *)segments, len, dummy); - bool pagesMissing = false; - if (err != 0) - { - err = errno; -#ifdef __LINUX__ - // On Linux ENOMEM means all requested range is unmapped - if (err != ENOMEM) - { - pagesMissing = true; - perror("mincore"); - } -#else - pagesMissing = true; - perror("mincore"); -#endif // __LINUX__ - } else { - for (int i = 0; i < numPages; i++) - { - if (dummy[i] != 1) - { - pagesMissing = true; - void *start = (void *)segments + i * pageSize; - void *end = (void *)segments + (i + 1) * pageSize - 1; - log_warning("required page %p - %p is not in memory!", start, end); - } - } - } - free(dummy); - if (pagesMissing) - { - log_error("At least one of required pages was not found in memory. This can cause segfaults later on."); - } -#if !defined(USE_MMAP) - // section: text - err = mprotect((void *)0x401000, 0x8a4000 - 0x401000, PROT_READ | PROT_EXEC | PROT_WRITE); - if (err != 0) - { - perror("mprotect"); - } -#endif // !defined(USE_MMAP) - // section: rw data - err = mprotect((void *)segments, 0x01429000 - 0x8a4000, PROT_READ | PROT_WRITE); - if (err != 0) - { - perror("mprotect"); - } -#endif // defined(__unix__) - -#if defined(USE_MMAP) && defined(__WINDOWS__) - segments = VirtualAlloc((void *)(GOOD_PLACE_FOR_DATA_SEGMENT), len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); - if ((uintptr_t)segments != GOOD_PLACE_FOR_DATA_SEGMENT) { - log_error("VirtualAlloc, segments = %p, GetLastError = 0x%x", segments, GetLastError()); - return false; - } - - utf8 segmentDataPath[MAX_PATH]; - openrct2_get_segment_data_path(segmentDataPath, sizeof(segmentDataPath)); - SDL_RWops * rw = SDL_RWFromFile(segmentDataPath, "rb"); - if (rw == NULL) - { - log_error("failed to load file"); - return false; - } - if (SDL_RWread(rw, segments, len, 1) != 1) { - log_error("Unable to read chunk header!"); - return false; - } - SDL_RWclose(rw); -#endif // defined(USE_MMAP) && defined(__WINDOWS__) - -#if !defined(NO_RCT2) && defined(USE_MMAP) - // Check that the expected data is at various addresses. - // Start at 0x9a6000, which is start of .data, to skip the region containing addresses to DLL - // calls, which can be changed by windows/wine loader. - const uint32 c1 = sawyercoding_calculate_checksum((const uint8*)(segments + (uintptr_t)(0x009A6000 - 0x8a4000)), 0x009E0000 - 0x009A6000); - const uint32 c2 = sawyercoding_calculate_checksum((const uint8*)(segments + (uintptr_t)(0x01428000 - 0x8a4000)), 0x014282BC - 0x01428000); - const uint32 exp_c1 = 10114815; - const uint32 exp_c2 = 23564; - if (c1 != exp_c1 || c2 != exp_c2) { - log_warning("c1 = %u, expected %u, match %d", c1, exp_c1, c1 == exp_c1); - log_warning("c2 = %u, expected %u, match %d", c2, exp_c2, c2 == exp_c2); - return false; - } -#endif - return true; -} - -/** - * Setup hooks to allow RCT2 to call OpenRCT2 functions instead. - */ -static void openrct2_setup_rct2_hooks() -{ - // None for now -} diff --git a/src/openrct2.h b/src/openrct2.h index 67eb264628..28d40ab2b1 100644 --- a/src/openrct2.h +++ b/src/openrct2.h @@ -60,8 +60,6 @@ bool openrct2_initialise(); void openrct2_launch(); void openrct2_dispose(); void openrct2_finish(); -void openrct2_reset_object_tween_locations(); -bool openrct2_setup_rct2_segment(); int cmdline_run(const char **argv, int argc); diff --git a/src/peep/peep.c b/src/peep/peep.c index d43b495af7..93a7f12887 100644 --- a/src/peep/peep.c +++ b/src/peep/peep.c @@ -1978,7 +1978,7 @@ bool peep_pickup_place(rct_peep* peep, int x, int y, int z, bool apply) peep->action_sprite_image_offset = 0; peep->action_sprite_type = 0; peep->var_C4 = 0; - openrct2_reset_object_tween_locations(); + sprite_position_tween_reset(); if (peep->type == PEEP_TYPE_GUEST) { peep->action_sprite_type = 0xFF; @@ -12245,7 +12245,7 @@ void peep_update_name_sort(rct_peep *peep) finish_peep_sort: // This is required at the moment because this function reorders peeps in the sprite list - openrct2_reset_object_tween_locations(); + sprite_position_tween_reset(); } void peep_sort() diff --git a/src/rct2.c b/src/rct2.c index ea8bbb4119..afb697f26c 100644 --- a/src/rct2.c +++ b/src/rct2.c @@ -499,3 +499,72 @@ uint32 get_file_extension_type(const utf8 *path) if (strcicmp(extension, ".td6") == 0) return FILE_EXTENSION_TD6; return FILE_EXTENSION_UNKNOWN; } + +static void rct2_copy_files_over(const utf8 *originalDirectory, const utf8 *newDirectory, const utf8 *extension) +{ + utf8 *ch, filter[MAX_PATH], oldPath[MAX_PATH], newPath[MAX_PATH]; + int fileEnumHandle; + file_info fileInfo; + + if (!platform_ensure_directory_exists(newDirectory)) { + log_error("Could not create directory %s.", newDirectory); + return; + } + + // Create filter path + safe_strcpy(filter, originalDirectory, sizeof(filter)); + ch = strchr(filter, '*'); + if (ch != NULL) + *ch = 0; + safe_strcat_path(filter, "*", sizeof(filter)); + path_append_extension(filter, extension, sizeof(filter)); + + fileEnumHandle = platform_enumerate_files_begin(filter); + while (platform_enumerate_files_next(fileEnumHandle, &fileInfo)) { + safe_strcpy(newPath, newDirectory, sizeof(newPath)); + safe_strcat_path(newPath, fileInfo.path, sizeof(newPath)); + + safe_strcpy(oldPath, originalDirectory, sizeof(oldPath)); + ch = strchr(oldPath, '*'); + if (ch != NULL) + *ch = 0; + safe_strcat_path(oldPath, fileInfo.path, sizeof(oldPath)); + + if (!platform_file_exists(newPath)) + platform_file_copy(oldPath, newPath, false); + } + platform_enumerate_files_end(fileEnumHandle); + + fileEnumHandle = platform_enumerate_directories_begin(originalDirectory); + while (platform_enumerate_directories_next(fileEnumHandle, filter)) { + safe_strcpy(newPath, newDirectory, sizeof(newPath)); + safe_strcat_path(newPath, filter, sizeof(newPath)); + + safe_strcpy(oldPath, originalDirectory, MAX_PATH); + ch = strchr(oldPath, '*'); + if (ch != NULL) + *ch = 0; + safe_strcat_path(oldPath, filter, sizeof(oldPath)); + + if (!platform_ensure_directory_exists(newPath)) { + log_error("Could not create directory %s.", newPath); + return; + } + rct2_copy_files_over(oldPath, newPath, extension); + } + platform_enumerate_directories_end(fileEnumHandle); +} + +/** + * Copy saved games and landscapes to user directory + */ +void rct2_copy_original_user_files_over() +{ + utf8 path[MAX_PATH]; + + platform_get_user_directory(path, "save", sizeof(path)); + rct2_copy_files_over((utf8*)gRCT2AddressSavedGamesPath, path, ".sv6"); + + platform_get_user_directory(path, "landscape", sizeof(path)); + rct2_copy_files_over((utf8*)gRCT2AddressLandscapesPath, path, ".sc6"); +} diff --git a/src/rct2.h b/src/rct2.h index 3f450067e3..71435ff079 100644 --- a/src/rct2.h +++ b/src/rct2.h @@ -145,6 +145,7 @@ void rct2_quit(); bool rct2_open_file(const char *path); uint32 get_file_extension_type(const utf8 *path); +void rct2_copy_original_user_files_over(); #ifdef __cplusplus } diff --git a/src/rct2/S6Importer.cpp b/src/rct2/S6Importer.cpp index 5a555d6fac..58af4d7250 100644 --- a/src/rct2/S6Importer.cpp +++ b/src/rct2/S6Importer.cpp @@ -385,7 +385,7 @@ extern "C" s6Importer->LoadSavedGame(rw); s6Importer->Import(); - openrct2_reset_object_tween_locations(); + sprite_position_tween_reset(); result = true; } catch (ObjectLoadException) @@ -412,7 +412,7 @@ extern "C" s6Importer->LoadSavedGame(path); s6Importer->Import(); - openrct2_reset_object_tween_locations(); + sprite_position_tween_reset(); result = true; } catch (ObjectLoadException) @@ -452,7 +452,7 @@ extern "C" s6Importer->LoadScenario(path); s6Importer->Import(); - openrct2_reset_object_tween_locations(); + sprite_position_tween_reset(); result = true; } catch (ObjectLoadException) @@ -486,7 +486,7 @@ extern "C" s6Importer->LoadSavedGame(rw); s6Importer->Import(); - openrct2_reset_object_tween_locations(); + sprite_position_tween_reset(); result = true; } catch (ObjectLoadException) diff --git a/src/rct2/interop.c b/src/rct2/interop.c new file mode 100644 index 0000000000..d8b952a3fb --- /dev/null +++ b/src/rct2/interop.c @@ -0,0 +1,223 @@ +#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 "../common.h" + +#if defined(__WINDOWS__) + #define WIN32_LEAN_AND_MEAN + #include +#endif + +#if defined(__unix__) || defined(__MACOSX__) + #include + #include + #include + #include + #include + #include +#endif // defined(__unix__) || defined(__MACOSX__) + +#include "../addresses.h" +#include "../hook.h" +#include "../openrct2.h" +#include "../util/sawyercoding.h" +#include "../util/util.h" +#include "interop.h" + +#if defined(USE_MMAP) && (defined(__unix__) || defined(__MACOSX__)) && !defined(NO_RCT2) + static int fdData = -1; +#endif +#if !defined(NO_RCT2) + static char * segments = (char *)(GOOD_PLACE_FOR_DATA_SEGMENT); +#endif + +static void rct2_interop_get_segment_data_path(char * buffer, size_t bufferSize) +{ + platform_get_exe_path(buffer, bufferSize); + safe_strcat_path(buffer, "openrct2_data", bufferSize); +} + +/** + * Loads RCT2's data model and remaps the addresses. + * @returns true if the data integrity check succeeded, otherwise false. + */ +bool rct2_interop_setup_segment() +{ + // OpenRCT2 on Linux and macOS is wired to have the original Windows PE sections loaded + // necessary. Windows does not need to do this as OpenRCT2 runs as a DLL loaded from the Windows PE. + int len = 0x01429000 - 0x8a4000; // 0xB85000, 12079104 bytes or around 11.5MB + int err = 0; + // in some configurations err and len may be unused + UNUSED(err); + UNUSED(len); +#if defined(USE_MMAP) && (defined(__unix__) || defined(__MACOSX__)) && !defined(NO_RCT2) + #define RDATA_OFFSET 0x004A4000 + #define DATASEG_OFFSET 0x005E2000 + + // Using PE-bear I was able to figure out all the needed addresses to be filled. + // There are three sections to be loaded: .rdata, .data and .text, plus another + // one to be mapped: DATASEG. + // Out of the three, two can simply be mmapped into memory, while the third one, + // .data has a virtual size which is much completely different to its file size + // (even when taking page-alignment into consideration) + // + // The sections are as follows (dump from gdb) + // [0] 0x401000->0x6f7000 at 0x00001000: .text ALLOC LOAD READONLY CODE HAS_CONTENTS + // [1] 0x6f7000->0x8a325d at 0x002f7000: CODESEG ALLOC LOAD READONLY CODE HAS_CONTENTS + // [2] 0x8a4000->0x9a5894 at 0x004a4000: .rdata ALLOC LOAD DATA HAS_CONTENTS + // [3] 0x9a6000->0x9e2000 at 0x005a6000: .data ALLOC LOAD DATA HAS_CONTENTS + // [4] 0x1428000->0x14282bc at 0x005e2000: DATASEG ALLOC LOAD DATA HAS_CONTENTS + // [5] 0x1429000->0x1452000 at 0x005e3000: .cms_t ALLOC LOAD READONLY CODE HAS_CONTENTS + // [6] 0x1452000->0x14aaf3e at 0x0060c000: .cms_d ALLOC LOAD DATA HAS_CONTENTS + // [7] 0x14ab000->0x14ac58a at 0x00665000: .idata ALLOC LOAD READONLY DATA HAS_CONTENTS + // [8] 0x14ad000->0x14b512f at 0x00667000: .rsrc ALLOC LOAD DATA HAS_CONTENTS + // + // .data section, however, has virtual size of 0xA81C3C, and so + // 0x9a6000 + 0xA81C3C = 0x1427C3C, which after alignment to page size becomes + // 0x1428000, which can be seen as next section, DATASEG + // + // The data is now loaded into memory with a linker script, which proves to + // be more reliable, as mallocs that happen before we reach segment setup + // could have already taken the space we need. + + // TODO: UGLY, UGLY HACK! + //off_t file_size = 6750208; + + utf8 segmentDataPath[MAX_PATH]; + rct2_interop_get_segment_data_path(segmentDataPath, sizeof(segmentDataPath)); + fdData = open(segmentDataPath, O_RDONLY); + if (fdData < 0) + { + log_fatal("failed to load openrct2_data"); + exit(1); + } + log_warning("%p", GOOD_PLACE_FOR_DATA_SEGMENT); + segments = mmap((void *)(GOOD_PLACE_FOR_DATA_SEGMENT), len, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_PRIVATE, fdData, 0); + log_warning("%p", segments); + if ((uintptr_t)segments != GOOD_PLACE_FOR_DATA_SEGMENT) { + perror("mmap"); + return false; + } +#endif // defined(USE_MMAP) && (defined(__unix__) || defined(__MACOSX__)) + +#if defined(__unix__) && !defined(NO_RCT2) + int pageSize = getpagesize(); + int numPages = (len + pageSize - 1) / pageSize; + unsigned char *dummy = malloc(numPages); + + err = mincore((void *)segments, len, dummy); + bool pagesMissing = false; + if (err != 0) + { + err = errno; +#ifdef __LINUX__ + // On Linux ENOMEM means all requested range is unmapped + if (err != ENOMEM) + { + pagesMissing = true; + perror("mincore"); + } +#else + pagesMissing = true; + perror("mincore"); +#endif // __LINUX__ + } else { + for (int i = 0; i < numPages; i++) + { + if (dummy[i] != 1) + { + pagesMissing = true; + void *start = (void *)segments + i * pageSize; + void *end = (void *)segments + (i + 1) * pageSize - 1; + log_warning("required page %p - %p is not in memory!", start, end); + } + } + } + free(dummy); + if (pagesMissing) + { + log_error("At least one of required pages was not found in memory. This can cause segfaults later on."); + } +#if !defined(USE_MMAP) + // section: text + err = mprotect((void *)0x401000, 0x8a4000 - 0x401000, PROT_READ | PROT_EXEC | PROT_WRITE); + if (err != 0) + { + perror("mprotect"); + } +#endif // !defined(USE_MMAP) + // section: rw data + err = mprotect((void *)segments, 0x01429000 - 0x8a4000, PROT_READ | PROT_WRITE); + if (err != 0) + { + perror("mprotect"); + } +#endif // defined(__unix__) + +#if defined(USE_MMAP) && defined(__WINDOWS__) + segments = VirtualAlloc((void *)(GOOD_PLACE_FOR_DATA_SEGMENT), len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if ((uintptr_t)segments != GOOD_PLACE_FOR_DATA_SEGMENT) { + log_error("VirtualAlloc, segments = %p, GetLastError = 0x%x", segments, GetLastError()); + return false; + } + + utf8 segmentDataPath[MAX_PATH]; + rct2_interop_get_segment_data_path(segmentDataPath, sizeof(segmentDataPath)); + SDL_RWops * rw = SDL_RWFromFile(segmentDataPath, "rb"); + if (rw == NULL) + { + log_error("failed to load file"); + return false; + } + if (SDL_RWread(rw, segments, len, 1) != 1) { + log_error("Unable to read chunk header!"); + return false; + } + SDL_RWclose(rw); +#endif // defined(USE_MMAP) && defined(__WINDOWS__) + +#if !defined(NO_RCT2) && defined(USE_MMAP) + // Check that the expected data is at various addresses. + // Start at 0x9a6000, which is start of .data, to skip the region containing addresses to DLL + // calls, which can be changed by windows/wine loader. + const uint32 c1 = sawyercoding_calculate_checksum((const uint8*)(segments + (uintptr_t)(0x009A6000 - 0x8a4000)), 0x009E0000 - 0x009A6000); + const uint32 c2 = sawyercoding_calculate_checksum((const uint8*)(segments + (uintptr_t)(0x01428000 - 0x8a4000)), 0x014282BC - 0x01428000); + const uint32 exp_c1 = 10114815; + const uint32 exp_c2 = 23564; + if (c1 != exp_c1 || c2 != exp_c2) { + log_warning("c1 = %u, expected %u, match %d", c1, exp_c1, c1 == exp_c1); + log_warning("c2 = %u, expected %u, match %d", c2, exp_c2, c2 == exp_c2); + return false; + } +#endif + return true; +} + +/** + * Setup hooks to allow RCT2 to call OpenRCT2 functions instead. + */ +void rct2_interop_setup_hooks() +{ + // None for now +} + +void rct2_interop_dispose() +{ +#if defined(USE_MMAP) && (defined(__unix__) || defined(__MACOSX__)) && !defined(NO_RCT2) + munmap(segments, 12079104); + close(fdData); +#endif +} diff --git a/src/rct2/interop.h b/src/rct2/interop.h new file mode 100644 index 0000000000..5f7c598cd7 --- /dev/null +++ b/src/rct2/interop.h @@ -0,0 +1,24 @@ +#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 + +#ifndef _RCT2_INTEROP_H_ +#define _RCT2_INTEROP_H_ + +bool rct2_interop_setup_segment(); +void rct2_interop_setup_hooks(); +void rct2_interop_dispose(); + +#endif diff --git a/src/world/sprite.c b/src/world/sprite.c index ab596eaa9e..f40e65caf3 100644 --- a/src/world/sprite.c +++ b/src/world/sprite.c @@ -38,24 +38,15 @@ static rct_sprite *_spriteList = RCT2_ADDRESS(RCT2_ADDRESS_SPRITE_LIST, rct_spri uint16 gSpriteSpatialIndex[0x10001]; +static rct_xyz16 _spritelocations1[MAX_SPRITES]; +static rct_xyz16 _spritelocations2[MAX_SPRITES]; + rct_sprite *get_sprite(size_t sprite_idx) { openrct2_assert(sprite_idx < MAX_SPRITES, "Tried getting sprite %u", sprite_idx); return &_spriteList[sprite_idx]; } -void store_sprite_locations(rct_xyz16 * sprite_locations) -{ - for (uint16 i = 0; i < MAX_SPRITES; i++) { - // skip going through `get_sprite` to not get stalled on assert, - // this can get very expensive for busy parks with uncap FPS option on - const rct_sprite *sprite = &_spriteList[i]; - sprite_locations[i].x = sprite->unknown.x; - sprite_locations[i].y = sprite->unknown.y; - sprite_locations[i].z = sprite->unknown.z; - } -} - uint16 sprite_get_first_in_quadrant(int x, int y) { int offset = ((x & 0x1FE0) << 3) | (y >> 5); @@ -701,3 +692,87 @@ void litter_remove_at(int x, int y, int z) spriteIndex = nextSpriteIndex; } } + +/** + * Determines whether its worth tweening a sprite or not when frame smoothing is on. + */ +static bool sprite_should_tween(rct_sprite *sprite) +{ + switch (sprite->unknown.linked_list_type_offset >> 1) { + case SPRITE_LIST_VEHICLE: + case SPRITE_LIST_PEEP: + case SPRITE_LIST_UNKNOWN: + return true; + } + return false; +} + +static void store_sprite_locations(rct_xyz16 * sprite_locations) +{ + for (uint16 i = 0; i < MAX_SPRITES; i++) { + // skip going through `get_sprite` to not get stalled on assert, + // this can get very expensive for busy parks with uncap FPS option on + const rct_sprite *sprite = &_spriteList[i]; + sprite_locations[i].x = sprite->unknown.x; + sprite_locations[i].y = sprite->unknown.y; + sprite_locations[i].z = sprite->unknown.z; + } +} + +void sprite_position_tween_store_a() +{ + store_sprite_locations(_spritelocations1); +} + +void sprite_position_tween_store_b() +{ + store_sprite_locations(_spritelocations2); +} + +void sprite_position_tween_all(float nudge) +{ + for (uint16 i = 0; i < MAX_SPRITES; i++) { + rct_sprite * sprite = get_sprite(i); + if (sprite_should_tween(sprite)) { + rct_xyz16 posA = _spritelocations1[i]; + rct_xyz16 posB = _spritelocations2[i]; + + sprite_set_coordinates( + posB.x + (sint16)((posA.x - posB.x) * nudge), + posB.y + (sint16)((posA.y - posB.y) * nudge), + posB.z + (sint16)((posA.z - posB.z) * nudge), + sprite + ); + invalidate_sprite_2(sprite); + } + } +} + +/** + * Restore the real positions of the sprites so they aren't left at the mid-tween positions + */ +void sprite_position_tween_restore() +{ + for (uint16 i = 0; i < MAX_SPRITES; i++) { + rct_sprite * sprite = get_sprite(i); + if (sprite_should_tween(sprite)) { + invalidate_sprite_2(sprite); + + rct_xyz16 pos = _spritelocations2[i]; + sprite_set_coordinates(pos.x, pos.y, pos.z, sprite); + } + } +} + +void sprite_position_tween_reset() +{ + for (uint16 i = 0; i < MAX_SPRITES; i++) { + rct_sprite * sprite = get_sprite(i); + _spritelocations1[i].x = + _spritelocations2[i].x = sprite->unknown.x; + _spritelocations1[i].y = + _spritelocations2[i].y = sprite->unknown.y; + _spritelocations1[i].z = + _spritelocations2[i].z = sprite->unknown.z; + } +} diff --git a/src/world/sprite.h b/src/world/sprite.h index cf411c2bdb..16f8f27520 100644 --- a/src/world/sprite.h +++ b/src/world/sprite.h @@ -400,7 +400,6 @@ enum { }; rct_sprite *get_sprite(size_t sprite_idx); -void store_sprite_locations(rct_xyz16 *sprite_locations); // rct2: 0x00982708 extern rct_sprite_entry g_sprite_entries[48]; @@ -432,6 +431,11 @@ void litter_remove_at(int x, int y, int z); void sprite_misc_explosion_cloud_create(int x, int y, int z); void sprite_misc_explosion_flare_create(int x, int y, int z); uint16 sprite_get_first_in_quadrant(int x, int y); +void sprite_position_tween_store_a(); +void sprite_position_tween_store_b(); +void sprite_position_tween_all(float nudge); +void sprite_position_tween_restore(); +void sprite_position_tween_reset(); /////////////////////////////////////////////////////////////// // Balloon diff --git a/test/testpaint/testpaint.vcxproj b/test/testpaint/testpaint.vcxproj index 047bd39467..c0191e40cf 100644 --- a/test/testpaint/testpaint.vcxproj +++ b/test/testpaint/testpaint.vcxproj @@ -17,7 +17,6 @@ {57E60BA1-FB76-4316-909E-C1449C142327} testpaint - DynamicLibrary @@ -30,18 +29,6 @@ true MultiByte - - - - - - - - - - - - $(SolutionDir)bin\testpaint\ $(SolutionDir)obj\$(ProjectName)\$(Configuration)\