From 5ad8fb272ecb5149177e70fceeac27d3c1ceabd5 Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 27 Apr 2016 00:15:48 +0100 Subject: [PATCH] create new track index file format at startup --- openrct2.vcxproj | 1 + src/rct1.h | 2 +- src/rct2.c | 4 +- src/ride/track.c | 184 ++++++++++++++++++++++++++++++++++++++++++ src/ride/track.h | 5 ++ src/ride/track_list.c | 152 ++++++++++++++++++++++++++++++++++ 6 files changed, 344 insertions(+), 4 deletions(-) create mode 100644 src/ride/track_list.c diff --git a/openrct2.vcxproj b/openrct2.vcxproj index 53dad229d7..8c3e6bad9f 100644 --- a/openrct2.vcxproj +++ b/openrct2.vcxproj @@ -181,6 +181,7 @@ + diff --git a/src/rct1.h b/src/rct1.h index fa94c4b385..2855e22783 100644 --- a/src/rct1.h +++ b/src/rct1.h @@ -527,8 +527,8 @@ enum{ }; enum{ + RCT1_RIDE_MODE_REVERSE_INCLINE_LAUNCHED_SHUTTLE = 2, RCT1_RIDE_MODE_POWERED_LAUNCH = 3, - }; enum { diff --git a/src/rct2.c b/src/rct2.c index ba75263aa7..429606167b 100644 --- a/src/rct2.c +++ b/src/rct2.c @@ -158,9 +158,7 @@ int rct2_init() object_list_load(); scenario_load_list(); - - ride_list_item item = { 253, 0 }; - track_load_list(item); + track_design_index_create(); font_sprite_initialise_characters(); if (!gOpenRCT2Headless) { diff --git a/src/ride/track.c b/src/ride/track.c index b5fab55775..6d8b5fad84 100644 --- a/src/ride/track.c +++ b/src/ride/track.c @@ -5695,3 +5695,187 @@ bool track_element_is_covered(int trackElementType) return false; } } + +static bool track_design_open_from_buffer(rct_track_td6 *td6, uint8 *src, size_t srcLength); + +bool track_design_open(rct_track_td6 *td6, const utf8 *path) +{ + SDL_RWops *file = SDL_RWFromFile(path, "rb"); + if (file != NULL) { + // Read whole file into a buffer + size_t bufferLength = (size_t)SDL_RWsize(file); + uint8 *buffer = (uint8*)malloc(bufferLength); + if (buffer == NULL) { + log_error("Unable to allocate memory for track design file."); + SDL_RWclose(file); + return false; + } + SDL_RWread(file, buffer, bufferLength, 1); + SDL_RWclose(file); + + if (!sawyercoding_validate_track_checksum(buffer, bufferLength)) { + log_error("Track checksum failed."); + free(buffer); + return false; + } + + // Decode the track data + uint8 *decoded = malloc(0x10000); + size_t decodedLength = sawyercoding_decode_td6(buffer, decoded, bufferLength); + free(buffer); + decoded = realloc(decoded, decodedLength); + if (decoded == NULL) { + log_error("failed to realloc"); + } else { + track_design_open_from_buffer(td6, decoded, decodedLength); + free(decoded); + return true; + } + } + return false; +} + +static bool track_design_open_from_buffer(rct_track_td6 *td6, uint8 *src, size_t srcLength) +{ + uint8 *readPtr = src; + + // Clear top of track_design as this is not loaded from the td4 files + memset(&td6->track_spine_colour, 0, 67); + + // Read start of track_design + copy(td6, &readPtr, 32); + + uint8 version = td6->version_and_colour_scheme >> 2; + if (version > 2) { + log_error("Unsupported track design."); + return false; + } + + // In TD6 there are 32 sets of two byte vehicle colour specifiers + // In TD4 there are 12 sets so the remaining 20 need to be read + if (version == 2) { + copy(&td6->vehicle_colours[12], &readPtr, 40); + } + + copy(&td6->pad_48, &readPtr, 24); + + // In TD4 (version AA/CF) and TD6 both start actual track data at 0xA3 + if (version > 0) { + copy(&td6->track_spine_colour, &readPtr, version == 1 ? 140 : 67); + } + + // Read the actual track data to memory directly after the passed in TD6 struct + size_t elementDataLength = srcLength - (readPtr - src); + uint8 *elementData = malloc(elementDataLength); + if (elementData == NULL) { + log_error("Unable to allocate memory for TD6 element data."); + return false; + } + copy(elementData, &readPtr, elementDataLength); + td6->elements = elementData; + td6->elementsSize = elementDataLength; + + uint8 *final_track_element_location = elementData + elementDataLength; + + // TD4 files require some extra work to be recognised as TD6. + if (version < 2) { + // Set any element passed the tracks to 0xFF + if (td6->type == RIDE_TYPE_MAZE) { + rct_maze_element* maze_element = (rct_maze_element*)elementData; + while (maze_element->all != 0) { + maze_element++; + } + maze_element++; + memset(maze_element, 255, final_track_element_location - (uint8*)maze_element); + } else { + rct_track_element* track_element = (rct_track_element*)elementData; + while (track_element->type != 255) { + track_element++; + } + memset(((uint8*)track_element) + 1, 255, final_track_element_location - (uint8*)track_element); + } + + // Convert the colours from RCT1 to RCT2 + for (int i = 0; i < 32; i++) { + rct_vehicle_colour *vehicleColour = &td6->vehicle_colours[i]; + vehicleColour->body_colour = rct1_get_colour(vehicleColour->body_colour); + vehicleColour->trim_colour = rct1_get_colour(vehicleColour->trim_colour); + } + + td6->track_spine_colour_rct1 = rct1_get_colour(td6->track_spine_colour_rct1); + td6->track_rail_colour_rct1 = rct1_get_colour(td6->track_rail_colour_rct1); + td6->track_support_colour_rct1 = rct1_get_colour(td6->track_support_colour_rct1); + + for (int i = 0; i < 4; i++) { + td6->track_spine_colour[i] = rct1_get_colour(td6->track_spine_colour[i]); + td6->track_rail_colour[i] = rct1_get_colour(td6->track_rail_colour[i]); + td6->track_support_colour[i] = rct1_get_colour(td6->track_support_colour[i]); + } + + // Highest drop height is 1bit = 3/4 a meter in TD6 + // Highest drop height is 1bit = 1/3 a meter in TD4 + // Not sure if this is correct?? + td6->highest_drop_height >>= 1; + + // If it has boosters then sadly track has to be discarded. + if (td4_track_has_boosters(td6, elementData)) { + log_error("Track design contains RCT1 boosters which are not yet supported."); + free(td6->elements); + td6->elements = NULL; + return false; + } + + // Convert RCT1 ride type to RCT2 ride type + uint8 rct1RideType = td6->type; + if (rct1RideType == RCT1_RIDE_TYPE_WOODEN_ROLLER_COASTER) { + td6->type = RIDE_TYPE_WOODEN_ROLLER_COASTER; + } else if (rct1RideType == RCT1_RIDE_TYPE_STEEL_CORKSCREW_ROLLER_COASTER) { + if (td6->vehicle_type == RCT1_VEHICLE_TYPE_HYPERCOASTER_TRAIN) { + if (td6->ride_mode == RCT1_RIDE_MODE_REVERSE_INCLINE_LAUNCHED_SHUTTLE) { + td6->ride_mode = RIDE_MODE_CONTINUOUS_CIRCUIT; + } + } + } + + // All TD4s that use powered launch use the type that doesn't pass the station. + if (td6->ride_mode == RCT1_RIDE_MODE_POWERED_LAUNCH) { + td6->ride_mode = RIDE_MODE_POWERED_LAUNCH; + } + + // Convert RCT1 vehicle type to RCT2 vehicle type + rct_object_entry *vehicle_object; + if (td6->type == RIDE_TYPE_MAZE) { + vehicle_object = RCT2_ADDRESS(0x0097F66C, rct_object_entry); + } else { + int vehicle_type = td6->vehicle_type; + if (vehicle_type == RCT1_VEHICLE_TYPE_INVERTED_COASTER_TRAIN && + td6->type == RIDE_TYPE_INVERTED_ROLLER_COASTER + ) { + vehicle_type = RCT1_VEHICLE_TYPE_4_ACROSS_INVERTED_COASTER_TRAIN; + } + vehicle_object = &RCT2_ADDRESS(0x0097F0DC, rct_object_entry)[vehicle_type]; + } + memcpy(&td6->vehicle_object, vehicle_object, sizeof(rct_object_entry)); + + // Further vehicle colour fixes + for (int i = 0; i < 32; i++) { + td6->vehicle_additional_colour[i] = td6->vehicle_colours[i].trim_colour; + + // RCT1 river rapids always had black seats. + if (rct1RideType == RCT1_RIDE_TYPE_RIVER_RAPIDS) { + td6->vehicle_colours[i].trim_colour = COLOUR_BLACK; + } + } + + td6->space_required_x = 255; + td6->space_required_y = 255; + td6->lift_hill_speed_num_circuits = 5; + } + + td6->var_50 = min( + td6->var_50, + RCT2_GLOBAL(RCT2_ADDRESS_RIDE_FLAGS + 5 + (td6->type * 8), uint8) + ); + + return true; +} diff --git a/src/ride/track.h b/src/ride/track.h index 190ed0d6fa..4ab69da1a6 100644 --- a/src/ride/track.h +++ b/src/ride/track.h @@ -183,6 +183,8 @@ typedef struct { uint8 space_required_y; // 0x81 uint8 vehicle_additional_colour[32]; // 0x82 uint8 lift_hill_speed_num_circuits; // 0xA2 0bCCCL_LLLL + void *elements; // 0xA3 (data starts here in file) + size_t elementsSize; } rct_track_td6; typedef struct{ @@ -637,4 +639,7 @@ int track_get_actual_bank_3(rct_vehicle *vehicle, rct_map_element *mapElement); bool track_element_is_station(rct_map_element *trackElement); bool track_element_is_covered(int trackElementType); +bool track_design_open(rct_track_td6 *td6, const utf8 *path); +void track_design_index_create(); + #endif diff --git a/src/ride/track_list.c b/src/ride/track_list.c new file mode 100644 index 0000000000..06b221c35c --- /dev/null +++ b/src/ride/track_list.c @@ -0,0 +1,152 @@ +#include "../common.h" +#include "../config.h" +#include "../util/util.h" +#include "track.h" + +typedef struct { + uint8 ride_type; + char ride_entry[9]; + utf8 path[MAX_PATH]; +} td_index_item; + +static void track_design_index_scan(); +static int track_design_index_item_compare(const void *a, const void *b); +static void track_design_index_include(const utf8 *directory); +static void track_design_add_file(const utf8 *path); +static void track_design_add(const td_index_item *item); +static void track_design_index_dispose(); +static void track_design_index_get_path(utf8 * buffer, size_t bufferLength); + +static const uint32 TrackIndexMagicNumber = 0x58444954; +static const uint16 TrackIndexVersion = 0; + +static td_index_item *_tdIndex = NULL; +static size_t _tdIndexSize = 0; +static size_t _tdIndexCapacity = 0; + +void track_design_index_create() +{ + track_design_index_dispose(); + + log_verbose("saving track list index (tracks.idx)"); + + utf8 path[MAX_PATH]; + track_design_index_get_path(path, sizeof(path)); + + SDL_RWops *file = SDL_RWFromFile(path, "wb"); + if (file != NULL) { + track_design_index_scan(); + + SDL_RWwrite(file, &TrackIndexMagicNumber, 4, 1); + SDL_RWwrite(file, &TrackIndexVersion, 4, 1); + SDL_RWwrite(file, &_tdIndexSize, 4, 1); + SDL_RWwrite(file, _tdIndex, sizeof(td_index_item), _tdIndexSize); + SDL_RWclose(file); + track_design_index_dispose(); + } +} + +static void track_design_index_scan() +{ + utf8 directory[MAX_PATH]; + + // Get track directory from RCT2 + safe_strcpy(directory, gConfigGeneral.game_path, sizeof(directory)); + safe_strcat_path(directory, "Tracks", sizeof(directory)); + track_design_index_include(directory); + + // Get track directory from user directory + platform_get_user_directory(directory, "tracks"); + track_design_index_include(directory); + + // Sort items by ride type then by filename + qsort(_tdIndex, _tdIndexSize, sizeof(td_index_item), track_design_index_item_compare); +} + +static int track_design_index_item_compare(const void *a, const void *b) +{ + const td_index_item *tdA = (const td_index_item*)a; + const td_index_item *tdB = (const td_index_item*)b; + + if (tdA->ride_type != tdB->ride_type) { + return tdA->ride_type - tdB->ride_type; + } + + const utf8 *tdAName = path_get_filename(tdA->path); + const utf8 *tdBName = path_get_filename(tdB->path); + return strcmp(tdAName, tdBName); +} + +static void track_design_index_include(const utf8 *directory) +{ + int handle; + file_info fileInfo; + + // Scenarios in this directory + utf8 pattern[MAX_PATH]; + safe_strcpy(pattern, directory, sizeof(pattern)); + safe_strcat_path(pattern, "*.td6", sizeof(pattern)); + + handle = platform_enumerate_files_begin(pattern); + while (platform_enumerate_files_next(handle, &fileInfo)) { + utf8 path[MAX_PATH]; + safe_strcpy(path, directory, sizeof(pattern)); + safe_strcat_path(path, fileInfo.path, sizeof(pattern)); + track_design_add_file(path); + } + platform_enumerate_files_end(handle); + + // Include sub-directories + utf8 subDirectory[MAX_PATH]; + handle = platform_enumerate_directories_begin(directory); + while (platform_enumerate_directories_next(handle, subDirectory)) { + utf8 path[MAX_PATH]; + safe_strcpy(path, directory, sizeof(pattern)); + safe_strcat_path(path, subDirectory, sizeof(pattern)); + track_design_index_include(path); + } + platform_enumerate_directories_end(handle); +} + +static void track_design_add_file(const utf8 *path) +{ + rct_track_td6 td6; + if (track_design_open(&td6, path)) { + td_index_item tdIndexItem = { 0 }; + safe_strcpy(tdIndexItem.path, path, sizeof(tdIndexItem.path)); + memcpy(tdIndexItem.ride_entry, td6.vehicle_object.name, 8); + tdIndexItem.ride_type = td6.type; + track_design_add(&tdIndexItem); + + free(td6.elements); + } +} + +static void track_design_add(const td_index_item *item) +{ + size_t nextIndex = _tdIndexSize; + if (nextIndex >= _tdIndexCapacity) { + _tdIndexCapacity = max(128, _tdIndexCapacity * 2); + _tdIndex = realloc(_tdIndex, _tdIndexCapacity * sizeof(td_index_item)); + if (_tdIndex == NULL) { + log_fatal("Unable to allocate more memory."); + exit(-1); + } + } + _tdIndex[nextIndex] = *item; + _tdIndexSize++; +} + +static void track_design_index_dispose() +{ + free(_tdIndex); + _tdIndexSize = 0; + _tdIndexCapacity = 0; +} + +static void track_design_index_get_path(utf8 * buffer, size_t bufferLength) +{ + platform_get_user_directory(buffer, NULL); + safe_strcat(buffer, "tracks.idx", bufferLength); +} +