OpenRCT2/src/ride/track_design_index.c

366 lines
10 KiB
C

#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"
#include "../config.h"
#include "../game.h"
#include "../interface/window.h"
#include "../localisation/string_ids.h"
#include "../util/util.h"
#include "track.h"
#include "track_design.h"
#pragma pack(push, 1)
typedef struct td_index_item {
uint8 ride_type;
char ride_entry[9];
utf8 path[MAX_PATH];
} td_index_item;
// NOTE: this is our own struct and should not get packed, but it is stored in a file
// so removing packing from it would require refactoring file access
assert_struct_size(td_index_item, 1 + 9 + 260);
#pragma pack(pop)
static bool track_design_index_read_header(SDL_RWops *file, uint32 *tdidxCount);
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 design 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, sizeof(TrackIndexMagicNumber), 1);
SDL_RWwrite(file, &TrackIndexVersion, sizeof(TrackIndexVersion), 1);
SDL_RWwrite(file, &_tdIndexSize, sizeof(uint32), 1);
SDL_RWwrite(file, _tdIndex, sizeof(td_index_item), _tdIndexSize);
SDL_RWclose(file);
track_design_index_dispose();
}
}
size_t track_design_index_get_count_for_ride(uint8 rideType, const char *entry)
{
log_verbose("reading track design index (tracks.idx)");
utf8 path[MAX_PATH];
track_design_index_get_path(path, sizeof(path));
// Return list
size_t refsCount = 0;
SDL_RWops *file = SDL_RWFromFile(path, "rb");
if (file != NULL) {
uint32 tdidxCount;
if (!track_design_index_read_header(file, &tdidxCount)) {
SDL_RWclose(file);
return 0;
}
for (uint32 i = 0; i < tdidxCount; i++) {
td_index_item tdItem;
SDL_RWread(file, &tdItem, sizeof(td_index_item), 1);
if (tdItem.ride_type != rideType) continue;
if (entry != NULL && _strcmpi(entry, tdItem.ride_entry) != 0) continue;
refsCount++;
}
SDL_RWclose(file);
}
return refsCount;
}
size_t track_design_index_get_for_ride(track_design_file_ref **tdRefs, uint8 rideType, const char *entry)
{
log_verbose("reading track design index (tracks.idx)");
utf8 path[MAX_PATH];
track_design_index_get_path(path, sizeof(path));
// Return list
size_t refsCount = 0;
size_t refsCapacity = 0;
track_design_file_ref *refs = NULL;
SDL_RWops *file = SDL_RWFromFile(path, "rb");
if (file != NULL) {
uint32 tdidxCount;
if (!track_design_index_read_header(file, &tdidxCount)) {
SDL_RWclose(file);
return 0;
}
for (uint32 i = 0; i < tdidxCount; i++) {
td_index_item tdItem;
SDL_RWread(file, &tdItem, sizeof(td_index_item), 1);
if (tdItem.ride_type != rideType) continue;
if (entry != NULL && _strcmpi(entry, tdItem.ride_entry) != 0) continue;
size_t nextIndex = refsCount;
if (nextIndex >= refsCapacity) {
refsCapacity = max(8, refsCapacity * 2);
refs = realloc(refs, refsCapacity * sizeof(track_design_file_ref));
if (refs == NULL) {
log_fatal("Unable to allocate more memory.");
exit(-1);
}
}
refs[nextIndex].name = track_design_get_name_from_path(tdItem.path);
refs[nextIndex].path = _strdup(tdItem.path);
refsCount++;
}
SDL_RWclose(file);
}
*tdRefs = realloc(refs, refsCount * sizeof(track_design_file_ref));
return refsCount;
}
utf8 *track_design_get_name_from_path(const utf8 *path)
{
const char *filename = path_get_filename(path);
const char *lastDot = strrchr(filename, '.');
size_t nameLength;
if (lastDot == NULL) {
nameLength = strlen(filename);
} else {
nameLength = (size_t)(lastDot - filename);
}
return strndup(filename, nameLength);
}
/**
*
* rct2: 0x006D3664
*/
bool track_design_index_rename(const utf8 *path, const utf8 *newName)
{
if (str_is_null_or_empty(newName)) {
gGameCommandErrorText = STR_CANT_RENAME_TRACK_DESIGN;
return false;
}
if (!filename_valid_characters(newName)) {
gGameCommandErrorText = STR_NEW_NAME_CONTAINS_INVALID_CHARACTERS;
return false;
}
utf8 newPath[MAX_PATH];
const char *lastPathSep = strrchr(path, *PATH_SEPARATOR);
if (lastPathSep == NULL) {
gGameCommandErrorText = STR_CANT_RENAME_TRACK_DESIGN;
return false;
}
size_t directoryLength = (size_t)(lastPathSep - path + 1);
memcpy(newPath, path, directoryLength);
safe_strcpy(newPath + directoryLength, newName, sizeof(newPath) - directoryLength);
path_append_extension(newPath, ".td6", sizeof(newPath));
if (!platform_file_move(path, newPath)) {
gGameCommandErrorText = STR_ANOTHER_FILE_EXISTS_WITH_NAME_OR_FILE_IS_WRITE_PROTECTED;
return false;
}
track_design_index_create();
rct_window *trackListWindow = window_find_by_class(WC_TRACK_DESIGN_LIST);
if (trackListWindow != NULL) {
trackListWindow->track_list.reload_track_designs = true;
}
return true;
}
/**
*
* rct2: 0x006D3761
*/
bool track_design_index_delete(const utf8 *path)
{
if (!platform_file_delete(path)) {
gGameCommandErrorText = STR_FILE_IS_WRITE_PROTECTED_OR_LOCKED;
return false;
}
track_design_index_create();
rct_window *trackListWindow = window_find_by_class(WC_TRACK_DESIGN_LIST);
if (trackListWindow != NULL) {
trackListWindow->track_list.reload_track_designs = true;
}
return true;
}
/**
*
* rct2: 0x006D40B2
*/
bool track_design_index_install(const utf8 *srcPath, const utf8 *destPath)
{
if (!platform_file_copy(srcPath, destPath, false)) {
return false;
}
track_design_index_create();
rct_window *trackListWindow = window_find_by_class(WC_TRACK_DESIGN_LIST);
if (trackListWindow != NULL) {
trackListWindow->track_list.reload_track_designs = true;
}
return true;
}
static bool track_design_index_read_header(SDL_RWops *file, uint32 *tdidxCount)
{
uint32 tdidxMagicNumber;
uint16 tdidxVersion;
SDL_RWread(file, &tdidxMagicNumber, sizeof(tdidxMagicNumber), 1);
SDL_RWread(file, &tdidxVersion, sizeof(tdidxVersion), 1);
SDL_RWread(file, tdidxCount, sizeof(uint32), 1);
if (tdidxMagicNumber != TrackIndexMagicNumber) {
log_error("invalid track index file");
return false;
}
if (tdidxVersion != TrackIndexVersion) {
log_error("unsupported track index file version");
return false;
}
return true;
}
static void track_design_index_scan()
{
utf8 directory[MAX_PATH];
// Get track directory from RCT2
safe_strcpy(directory, gRCT2AddressAppPath, 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, "track", sizeof(directory));
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 _stricmp(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 = track_design_open(path);
if (td6 != NULL) {
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);
track_design_dispose(td6);
}
}
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()
{
SafeFree(_tdIndex);
_tdIndexSize = 0;
_tdIndexCapacity = 0;
}
static void track_design_index_get_path(utf8 * buffer, size_t bufferLength)
{
platform_get_user_directory(buffer, NULL, bufferLength);
safe_strcat_path(buffer, "tracks.idx", bufferLength);
}