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